GenGo

Static site generator written in Golang using Pandoc.
Log | Files | Refs | README | LICENSE

main.go (6968B)


      1 /* This is a project of a new static site generator */
      2 /* Eventually, this might replace SSG for me */
      3 
      4 /*****************************************************************/
      5 /* DONE: Basic functionality for converting markdown/org to html */
      6 /* DONE: Recursively mirroring dir structure of source files     */
      7 /* DONE: Prepend and append header and footer on HTML files      */
      8 /* DONE: Ignore non-valid file types and directories in the src  */
      9 /* DONE: Make the template file not be static                    */
     10 /* TODO: RSS generator built in                                  */
     11 /* TODO: Sitemap generator                                       */
     12 /* TODO: Add some better exit codes that actually make sense     */
     13 /*****************************************************************/
     14 /* OPTIONAL ARGUMENTS:                                           */
     15 /* TODO -j: Set how many concurrent jobs can run    | DIFFICULT  */
     16 /* DONE -h: Display help                                         */
     17 /* TODO -r: Do not generate RSS feed                             */
     18 /* TODO -s: Do not generate sitemap                              */
     19 /* DONE -t: Use template file for HTML                           */
     20 /* DONE -v: Show program version number                          */
     21 /*****************************************************************/
     22 
     23 package main
     24 
     25 import (
     26 	"fmt" // For formatting some outputs
     27 	"os"      // File system and arguments
     28 	"os/exec" // For running OS operations
     29 	"path"    // For removing and detecting file extensions
     30 	"regexp"  // For matching directories
     31 )
     32 
     33 /* Version Number */
     34 const version = "0.0.4: Almost"
     35 
     36 /* This is a test function to try to copy a directory */
     37 func DirSetup(source string, destination, invalidDir, template string) error {
     38 
     39 	srcInfo, err := os.Stat(source)
     40 	if os.IsNotExist(err) {
     41 		fmt.Fprintf(os.Stderr, "No such directory \"%v\" found!\n", source)
     42 		// Should probably change the exit codes at some point
     43 		os.Exit(1)
     44 	}
     45 	if err != nil {
     46 		return err
     47 	}
     48 
     49 	err = os.MkdirAll(destination, srcInfo.Mode())
     50 	if err != nil {
     51 		return err
     52 	}
     53 
     54 	entries, err := os.ReadDir(source)
     55 	if err != nil {
     56 		return err
     57 	}
     58 
     59 	/* Based around solution presented here: */
     60 	/* https://gistlib.com/go/copy-a-directory-in-go  */
     61 	for _, entry := range entries {
     62 		srcPath := source + "/" + entry.Name()
     63 		dstPath := destination + "/" + entry.Name()
     64 
     65 		/* Ignore specified directories */
     66 		re := regexp.MustCompile(invalidDir)
     67 		if re.MatchString(destination) {
     68 			fmt.Println("Skipping directory:", srcPath)
     69 			return nil
     70 		}
     71 
     72 		if entry.IsDir() {
     73 			err = DirSetup(srcPath, dstPath, invalidDir, template)
     74 			if err != nil {
     75 				return err
     76 			}
     77 		} else {
     78 			err = ConvertFile(source, entry.Name(), destination, template)
     79 			if err != nil {
     80 				return err
     81 			}
     82 			/* Creating the RSS file that will be appended for each article */
     83 			//rssFile, err := os.OpenFile(destination + "/" + "rss.xml", os.O_CREATE|os.O_WRONLY, 0664)
     84 			if err != nil {
     85 				return err
     86 			}
     87 
     88 			rssFile := destination + "/" + "rss.xml"
     89 			openRSS, err := os.OpenFile(destination + "/" + "rss.xml", os.O_CREATE|os.O_WRONLY, 0664)
     90 			if err != nil {
     91 				panic(err)
     92 			}
     93 			rssHeader := "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\"\n<channel>\n"
     94 			_, err = openRSS.WriteString(rssHeader)
     95 			if err != nil {
     96 				panic(err)
     97 			}
     98 			rssGen(source, entry.Name(), rssFile)
     99 		}
    100 	}
    101 
    102 	return nil
    103 }
    104 
    105 func rssGen(filepath, filename, destination string) {
    106 
    107 	cmd := exec.Command("pandoc", "-thtml",  filepath + "/" + filename)
    108 	rssFile, err := os.OpenFile(destination, os.O_APPEND|os.O_WRONLY, 0664)
    109 	if err != nil {
    110 		panic(err)
    111 	}
    112 
    113 	output, err := cmd.Output()
    114 	if err != nil {
    115 		fmt.Println("Error:", err)
    116 		panic(err)
    117 	}
    118 
    119 	_, err = rssFile.WriteString("<article>\n")
    120 	if err != nil {
    121 		panic(err)
    122 	}
    123 	_, err = rssFile.WriteString(string(output))
    124 	if err != nil {
    125 		panic(err)
    126 	}
    127 	_, err = rssFile.WriteString("</article>\n")
    128 	if err != nil {
    129 		panic(err)
    130 	}
    131 
    132 }
    133 
    134 func RemoveExt(filename string) string {
    135 	return filename[:len(filename)-len(path.Ext(filename))]
    136 }
    137 
    138 /* This function converts the markdown files using pandoc and allows for a user defined template     */
    139 /* with pandoc's `--template` flag. A default should be included as an example, and will potentially */
    140 /* need to be maintained over time as pandoc changes. The function will then put the output string   */
    141 /* Into the corresponding file */
    142 
    143 func ConvertFile(filepath, filename, destination, template string) error {
    144 
    145 	validFile := path.Ext(filename)
    146 
    147 	/* Skipping invalid file types      */
    148 	/* TODO: Add support for .org files */
    149 	if validFile != ".md" {
    150 		fmt.Println("Skipping file:", filename)
    151 		return nil
    152 	}
    153 
    154 	fmt.Println("Converting following file to HTML:", filename)
    155 	oFile := RemoveExt(filename)
    156 	cmd := exec.Command("pandoc", "-s", "--template", template, filepath + "/" + filename)
    157 
    158 	/* Changing execution command if template is NULL */
    159 	if template == os.DevNull {
    160 		cmd = exec.Command("pandoc", filepath + "/" + filename)
    161 	}
    162 
    163 	output, err := cmd.Output()
    164 	if err != nil {
    165 		fmt.Println("Error:", err)
    166 		return err
    167 	}
    168 	file, err := os.Create(destination + "/" + oFile + ".html")
    169 	if err != nil {
    170 		panic(err)
    171 	}
    172 	defer file.Close()
    173 
    174 	_, err = file.WriteString(string(output))
    175 	if err != nil {
    176 		panic(err)
    177 	}
    178 
    179 	return nil
    180 }
    181 
    182 func CallHelp() {
    183 	fmt.Println("Usage: gengo [OPTION]... SOURCE DEST")
    184 	/* Redundant newline is purposeful */
    185 	fmt.Println("Automatically convert valid files into HTML using Pandoc.\n")
    186 	fmt.Println("    -h, --help: Print this help screen")
    187 	fmt.Println("    -i, --ignore: Don't process directories that match a string")
    188 	fmt.Println("    -t, --template: Use existing HTML template (Pandoc formatting)")
    189 	fmt.Println("    -v, --version: Prints version information for gengo")
    190 }
    191 
    192 func ArgParser (argument[] string) {
    193 
    194 	cArgs := len(argument)
    195 	/* Setting some default values in case user does not want to use these options */
    196 	ignoreDir := os.DevNull
    197 	template := os.DevNull
    198 	var srcDir, destDir string
    199 
    200 	for i := 0; i < cArgs; i++ {
    201 		switch argument[i] {
    202 		case "-h", "--help":
    203 			CallHelp()
    204 			os.Exit(0)
    205 		case "-i", "--ignore":
    206 			ignoreDir = argument[i + 1]
    207 			i++
    208 		case "-t", "--template":
    209 			template = argument[i + 1]
    210 			i++
    211 		case "-v", "--version":
    212 			println("Version:", version)
    213 			os.Exit(0)
    214 		default:
    215 			/* Fail if less than two arguments are passed */
    216 			/* Must have at least source and dest for application to work */
    217 			if (len(argument) < 2) {
    218 				println("Not enough arguments!")
    219 				CallHelp()
    220 				os.Exit(1)
    221 			}
    222 			srcDir = argument[i]
    223 			destDir = argument[i + 1]
    224 			i++
    225 		}
    226 	}
    227 	DirSetup(srcDir, destDir, ignoreDir, template)
    228 }
    229 
    230 func main() {
    231 	arguments := os.Args[1:]
    232 
    233 	/* Fail if no arguments are passed */
    234 	if (len(arguments) == 0) {
    235 		println("Not enough arguments!")
    236 		CallHelp()
    237 		os.Exit(1)
    238 	}
    239 
    240 	/* Parse the various options to figure out what we are doing */
    241 	ArgParser(arguments)
    242 }