Óscar M. Lage
6a56a2f9f2
This feature allows triggering the execution of the `build_action` command specified in the `.ini` file when adding `build: true` to the frontmatter of the modified file. Upon detecting this tag, the specified command will be executed automatically, facilitating the automation of build processes in response to specific file modifications
433 lines
11 KiB
Go
433 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
"github.com/gosimple/slug"
|
|
"gopkg.in/ini.v1"
|
|
)
|
|
|
|
func main() {
|
|
|
|
// Read the config file
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
config, err := ini.Load(home + "/.config/obs2hugo/obs2hugo.ini")
|
|
if err != nil {
|
|
config, err = ini.Load(home + "/.config/obs2hugo.ini")
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
}
|
|
// Hugo build action
|
|
buildAction := config.Section("").Key("build_action").String()
|
|
|
|
// Watcher directories
|
|
watcherDirs := config.Section("").Key("watcher_dirs").String()
|
|
|
|
// Hugo content directory
|
|
destDirs := config.Section("").Key("hugo_dirs").String()
|
|
|
|
watcherDirsList := strings.Split(watcherDirs, ":")
|
|
destDirsList := strings.Split(destDirs, ":")
|
|
|
|
if len(watcherDirsList) != len(destDirsList) {
|
|
fmt.Println("Error: watcher_dirs and hugo_dirs must have the same number of elements")
|
|
return
|
|
}
|
|
|
|
// Creating watcher
|
|
watcher, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
fmt.Println("Error creating the watcher:", err)
|
|
return
|
|
}
|
|
defer watcher.Close()
|
|
|
|
// Add directories to the watcher
|
|
for _, dir := range watcherDirsList {
|
|
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
fmt.Println("Error accessing to the directory:", err)
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
return watcher.Add(path)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
fmt.Println("Error adding directory to the watcher:", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Watcher event loop
|
|
for {
|
|
select {
|
|
case event := <-watcher.Events:
|
|
switch {
|
|
case event.Op&fsnotify.Create == fsnotify.Create:
|
|
fmt.Println("New file:", event.Name)
|
|
// TBD
|
|
case event.Op&fsnotify.Write == fsnotify.Write:
|
|
fmt.Println("File modified:", event.Name)
|
|
triggerDir := getTriggerDirectory(event.Name, watcherDirsList)
|
|
if triggerDir == "" {
|
|
fmt.Println("Error: Unable to determine trigger directory for event:", event.Name)
|
|
continue
|
|
}
|
|
newDestDir := getDestinationDirectory(triggerDir, watcherDirsList, destDirsList)
|
|
if newDestDir == "" {
|
|
fmt.Println("Error: Unable to determine destination directory for trigger directory:", triggerDir)
|
|
continue
|
|
}
|
|
processModifiedFile(event.Name, triggerDir, newDestDir)
|
|
// Check if the file contains the "build: true" tag in the frontmatter
|
|
hasBuildTag, err := hasBuildTag(event.Name)
|
|
if err != nil {
|
|
fmt.Println("Error checking build tag in frontmatter:", err)
|
|
continue
|
|
}
|
|
|
|
// If the file contains the "build: true" tag, execute the build action specified in the .ini file
|
|
if hasBuildTag && buildAction != "" {
|
|
// Remove the "build: true" tag from the frontmatter
|
|
err = removeBuildTag(event.Name)
|
|
if err != nil {
|
|
fmt.Println("Error removing build tag from frontmatter:", err)
|
|
return
|
|
}
|
|
// Execute build action
|
|
err := executeBuildAction(buildAction)
|
|
if err != nil {
|
|
fmt.Println("Error executing build action:", err)
|
|
continue
|
|
}
|
|
}
|
|
case event.Op&fsnotify.Remove == fsnotify.Remove:
|
|
fmt.Println("File deleted:", event.Name)
|
|
// TBD
|
|
case event.Op&fsnotify.Rename == fsnotify.Rename:
|
|
fmt.Println("File renamed:", event.Name)
|
|
// TBD
|
|
}
|
|
case err := <-watcher.Errors:
|
|
fmt.Println("Watcher error:", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Function to determine which watcher directory triggered the event
|
|
func getTriggerDirectory(eventPath string, dirs []string) string {
|
|
for _, dir := range dirs {
|
|
if strings.HasPrefix(eventPath, dir) {
|
|
return dir
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Function to determine the destination directory for the trigger directory
|
|
func getDestinationDirectory(triggerDir string, watcherDirsList, destDirsList []string) string {
|
|
for i, dir := range watcherDirsList {
|
|
if dir == triggerDir {
|
|
return destDirsList[i]
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func processModifiedFile(filePath, directory, destDir string) {
|
|
// Slugified file name without extension
|
|
fileName := strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath))
|
|
slug := slug.Make(fileName)
|
|
|
|
// Creating destination directory
|
|
newDir := filepath.Join(destDir, slug)
|
|
err := os.MkdirAll(newDir, os.ModePerm)
|
|
if err != nil {
|
|
fmt.Println("Error creating directory:", err)
|
|
return
|
|
}
|
|
|
|
// Copy modified file to the new destination
|
|
newFilePath := filepath.Join(newDir, "index.md")
|
|
|
|
// Check if the file already has frontmatter
|
|
hasFrontmatter, err := checkFrontmatter(filePath)
|
|
if err != nil {
|
|
fmt.Println("Error checking frontmatter:", err)
|
|
return
|
|
}
|
|
|
|
// If the file doesn't have frontmatter, add it
|
|
if !hasFrontmatter {
|
|
err = addFrontmatter(filePath, fileName)
|
|
if err != nil {
|
|
fmt.Println("Error adding frontmatter:", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
err = copyFile(filePath, newFilePath)
|
|
if err != nil {
|
|
fmt.Println("Error copying file:", err)
|
|
return
|
|
}
|
|
|
|
// Move images to the gallery directory
|
|
err = moveImagesToGallery(filePath, newDir, directory)
|
|
if err != nil {
|
|
fmt.Println("Error moving images to the gallery:", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Function to check if the file already has frontmatter
|
|
func checkFrontmatter(filePath string) (bool, error) {
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if line == "---" {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// Function to add frontmatter to the file
|
|
func addFrontmatter(filePath, title string) error {
|
|
// Read the content of the original file
|
|
content, err := ioutil.ReadFile(filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create a new temporary file with the frontmatter added
|
|
tempFilePath := filePath + ".tmp"
|
|
frontmatter := fmt.Sprintf("---\ntitle: %s\ndate: %s\ndraft: false\ntags: micropost\n---\n\n", title, time.Now().Format("2006-01-02 15:04:05 -0700"))
|
|
err = ioutil.WriteFile(tempFilePath, []byte(frontmatter+string(content)), os.ModePerm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Replace the original file with the temporary file
|
|
fmt.Println("Rename:", tempFilePath, filePath)
|
|
err = os.Rename(tempFilePath, filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Function to check if the file contains the "build: true" tag in the frontmatter
|
|
func hasBuildTag(filePath string) (bool, error) {
|
|
// Open the file
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer file.Close()
|
|
|
|
// Read the file line by line
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
|
|
// Check for the start of the frontmatter
|
|
if line == "---" {
|
|
var frontmatter []string
|
|
|
|
// Read the frontmatter
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if line == "---" {
|
|
break
|
|
}
|
|
frontmatter = append(frontmatter, line)
|
|
}
|
|
|
|
// Check if the frontmatter contains the "build: true" tag
|
|
for _, tag := range frontmatter {
|
|
if strings.Contains(tag, "build: true") {
|
|
return true, nil
|
|
}
|
|
}
|
|
break // We're done checking frontmatter, so break out of the loop
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// Function to execute the build action specified in the .ini file
|
|
func executeBuildAction(action string) error {
|
|
// Split the action into command and arguments
|
|
parts := strings.Fields(action)
|
|
cmd := parts[0]
|
|
args := parts[1:]
|
|
|
|
// Execute the command
|
|
out, err := exec.Command(cmd, args...).Output()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Print the command output
|
|
fmt.Println(string(out))
|
|
|
|
return nil
|
|
}
|
|
|
|
func copyFile(src, dst string) error {
|
|
sourceFile, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer sourceFile.Close()
|
|
|
|
newFile, err := os.Create(dst)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer newFile.Close()
|
|
|
|
_, err = io.Copy(newFile, sourceFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func removeBuildTag(filePath string) error {
|
|
// Read file content
|
|
content, err := ioutil.ReadFile(filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
contentStr := string(content)
|
|
|
|
// Find frontmatter
|
|
start := strings.Index(contentStr, "---")
|
|
end := strings.Index(contentStr[start+3:], "---")
|
|
|
|
// Verify if frontmatter exists
|
|
if start != -1 && end != -1 {
|
|
frontmatter := contentStr[start : start+3+end+3]
|
|
|
|
// Look for "build: true" in the frontmatter
|
|
buildTagIndex := strings.Index(frontmatter, "build: true")
|
|
if buildTagIndex != -1 {
|
|
// Delete "build: true" from frontmatter
|
|
frontmatter = strings.ReplaceAll(frontmatter, "build: true\n", "")
|
|
|
|
contentStr = strings.Replace(contentStr, string(content[start:start+3+end+3]), frontmatter, 1)
|
|
|
|
err := ioutil.WriteFile(filePath, []byte(contentStr), 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func moveImagesToGallery(filePath, destDir, directory string) error {
|
|
// Routes
|
|
destFilePath := filepath.Join(destDir, "index.md")
|
|
slugDir := slug.Make(strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath)))
|
|
galleryDir := filepath.Join(destDir, "gallery")
|
|
|
|
// Read file content
|
|
content, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Regexp to find the markdown images
|
|
re := regexp.MustCompile(`!\[\[([^]]+)\]\]`)
|
|
matches := re.FindAllStringSubmatch(string(content), -1)
|
|
|
|
// Create gallery dir if it doesn't exist
|
|
if _, err := os.Stat(galleryDir); os.IsNotExist(err) {
|
|
err := os.Mkdir(galleryDir, os.ModePerm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Move every image found
|
|
for _, match := range matches {
|
|
imagePath := match[1]
|
|
imageName := filepath.Base(imagePath)
|
|
ext := filepath.Ext(imageName)
|
|
slugName := slug.Make(strings.TrimSuffix(imageName, ext))
|
|
imageSrc := filepath.Join(directory, imagePath)
|
|
imageDest := filepath.Join(galleryDir, slugName+ext)
|
|
// Open source file
|
|
srcFile, err := os.Open(imageSrc)
|
|
if err != nil {
|
|
fmt.Printf("Error opening source file %s: %s\n", imageSrc, err)
|
|
continue
|
|
}
|
|
defer srcFile.Close()
|
|
|
|
// Create destination file
|
|
destFile, err := os.Create(imageDest)
|
|
if err != nil {
|
|
fmt.Printf("Error creating destination file %s: %s\n", imageDest, err)
|
|
continue
|
|
}
|
|
defer destFile.Close()
|
|
|
|
// Copy source to destination
|
|
_, err = io.Copy(destFile, srcFile)
|
|
if err != nil {
|
|
fmt.Printf("Error copying file %s to %s: %s\n", imageSrc, imageDest, err)
|
|
continue
|
|
}
|
|
|
|
// Modify link text
|
|
newImageMarkdown := fmt.Sprintf("![%s](%s/gallery/%s)", strings.TrimSuffix(slugName, ext), slugDir, slugName+ext)
|
|
content = bytes.Replace(content, []byte(match[0]), []byte(newImageMarkdown), -1)
|
|
|
|
fmt.Printf("Image %s was copied to %s\n", imageSrc, imageDest)
|
|
}
|
|
|
|
// Replace [gallery] pattern
|
|
content = bytes.Replace(content, []byte("[gallery]"), []byte("{{< gallery match=\"gallery/*\" sortOrder=\"asc\" rowHeight=\"150\" margins=\"5\" thumbnailResizeOptions=\"600x600 q90 Lanczos\" previewType=\"blur\" embedPreview=\"true\" >}}"), -1)
|
|
|
|
// Write markdown file new content
|
|
err = os.WriteFile(destFilePath, content, os.ModePerm)
|
|
if err != nil {
|
|
fmt.Printf("Error writing file content to the file: %s: %s\n", filePath, err)
|
|
}
|
|
|
|
return nil
|
|
}
|