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 }