- Timestamp:
- Dec 12, 2023, 2:09:17 PM (18 months ago)
- Location:
- trunk/cmd/aya
- Files:
-
- 7 added
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/cmd/aya/main.go
r79 r80 1 // $TheSupernovaDuo: marisa.chaotic.ninja/aya/cmd/aya, v 0.7.0 2023-12-11 17:22:51+0000, yakumo_izuru Exp $1 // $TheSupernovaDuo: marisa.chaotic.ninja/aya/cmd/aya, v1.0.0 2023-12-12 13:44:23+0000, yakumo_izuru Exp $ 2 2 package main 3 3 4 4 import ( 5 "bytes"6 5 "fmt" 7 "io"8 6 "os" 9 "os/exec"10 "path/filepath"11 7 "strings" 12 "time"13 8 14 "gopkg.in/yaml.v3"15 9 "marisa.chaotic.ninja/aya" 16 10 ) … … 23 17 24 18 type Vars map[string]string 25 26 // renameExt renames extension (if any) from oldext to newext27 // If oldext is an empty string - extension is extracted automatically.28 // If path has no extension - new extension is appended29 func renameExt(path, oldext, newext string) string {30 if oldext == "" {31 oldext = filepath.Ext(path)32 }33 if oldext == "" || strings.HasSuffix(path, oldext) {34 return strings.TrimSuffix(path, oldext) + newext35 }36 return path37 }38 39 // globals returns list of global OS environment variables that start40 // with AYA_ prefix as Vars, so the values can be used inside templates41 func globals() Vars {42 vars := Vars{}43 for _, e := range os.Environ() {44 pair := strings.Split(e, "=")45 if strings.HasPrefix(pair[0], "AYA_") {46 vars[strings.ToLower(pair[0][3:])] = pair[1]47 }48 }49 return vars50 }51 52 // run executes a command or a script. Vars define the command environment,53 // each aya var is converted into OS environemnt variable with AYA_ prefix54 // prepended. Additional variable $AYA contains path to the aya binary. Command55 // stderr is printed to aya stderr, command output is returned as a string.56 func run(vars Vars, cmd string, args ...string) (string, error) {57 // First check if partial exists (.html)58 if b, err := os.ReadFile(filepath.Join(AYADIR, cmd+".html")); err == nil {59 return string(b), nil60 }61 62 var errbuf, outbuf bytes.Buffer63 c := exec.Command(cmd, args...)64 env := []string{"AYA=" + os.Args[0], "AYA_OUTDIR=" + PUBDIR}65 env = append(env, os.Environ()...)66 for k, v := range vars {67 env = append(env, "AYA_"+strings.ToUpper(k)+"="+v)68 }69 c.Env = env70 c.Stdout = &outbuf71 c.Stderr = &errbuf72 73 err := c.Run()74 75 if errbuf.Len() > 0 {76 fmt.Println("ERROR:", errbuf.String())77 }78 if err != nil {79 return "", err80 }81 return string(outbuf.Bytes()), nil82 }83 84 // getVars returns list of variables defined in a text file and actual file85 // content following the variables declaration. Header is separated from86 // content by an empty line. Header can be either YAML or JSON.87 // If no empty newline is found - file is treated as content-only.88 func getVars(path string, globals Vars) (Vars, string, error) {89 b, err := os.ReadFile(path)90 if err != nil {91 return nil, "", err92 }93 s := string(b)94 95 // Pick some default values for content-dependent variables96 v := Vars{}97 title := strings.Replace(strings.Replace(path, "_", " ", -1), "-", " ", -1)98 v["title"] = strings.ToTitle(title)99 v["description"] = ""100 v["file"] = path101 v["url"] = path[:len(path)-len(filepath.Ext(path))] + ".html"102 v["output"] = filepath.Join(PUBDIR, v["url"])103 104 // Override default values with globals105 for name, value := range globals {106 v[name] = value107 }108 109 // Add layout if none is specified110 if _, ok := v["layout"]; !ok {111 v["layout"] = "layout.html"112 }113 114 delim := "\n---\n"115 if sep := strings.Index(s, delim); sep == -1 {116 return v, s, nil117 } else {118 header := s[:sep]119 body := s[sep+len(delim):]120 121 vars := Vars{}122 if err := yaml.Unmarshal([]byte(header), &vars); err != nil {123 fmt.Println("ERROR: failed to parse header", err)124 return nil, "", err125 } else {126 // Override default values + globals with the ones defines in the file127 for key, value := range vars {128 v[key] = value129 }130 }131 if strings.HasPrefix(v["url"], "./") {132 v["url"] = v["url"][2:]133 }134 return v, body, nil135 }136 }137 138 // Render expanding aya plugins and variables139 func render(s string, vars Vars) (string, error) {140 delimOpen := "{{"141 delimClose := "}}"142 143 out := &bytes.Buffer{}144 for {145 if from := strings.Index(s, delimOpen); from == -1 {146 out.WriteString(s)147 return out.String(), nil148 } else {149 if to := strings.Index(s, delimClose); to == -1 {150 return "", fmt.Errorf("Closing delimiter not found")151 } else {152 out.WriteString(s[:from])153 cmd := s[from+len(delimOpen) : to]154 s = s[to+len(delimClose):]155 m := strings.Fields(cmd)156 if len(m) == 1 {157 if v, ok := vars[m[0]]; ok {158 out.WriteString(v)159 continue160 }161 }162 if res, err := run(vars, m[0], m[1:]...); err == nil {163 out.WriteString(res)164 } else {165 fmt.Println(err)166 }167 }168 }169 }170 171 }172 173 // This function passes the files to build to their corresponding functions174 // As far as I'm aware, Markdown has three possible filename extensions,175 // but .md is the most common one known.176 func build(path string, w io.Writer, vars Vars) error {177 ext := filepath.Ext(path)178 if ext == ".md" || ext == ".mkd" || ext == ".markdown" {179 return buildMarkdown(path, w, vars)180 } else if ext == ".htm" || ext == ".html" || ext == ".xht" || ext == ".xhtml" {181 return buildHTML(path, w, vars)182 } else if ext == ".amber" {183 return buildAmber(path, w, vars)184 } else if ext == ".gcss" {185 return buildGCSS(path, w)186 } else {187 return buildRaw(path, w)188 }189 }190 191 // Build everything and store it on PUBDIR192 // If boolean watch is true, it keeps on going193 // every time you modify something.194 func buildAll(watch bool) {195 lastModified := time.Unix(0, 0)196 modified := false197 198 vars := globals()199 for {200 os.Mkdir(PUBDIR, 0755)201 filepath.Walk(".", func(path string, info os.FileInfo, err error) error {202 // ignore hidden files and directories203 if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") {204 return nil205 }206 // inform user about fs walk errors, but continue iteration207 if err != nil {208 fmt.Println("error:", err)209 return nil210 }211 212 if info.IsDir() {213 os.Mkdir(filepath.Join(PUBDIR, path), 0755)214 return nil215 } else if info.ModTime().After(lastModified) {216 if !modified {217 // First file in this build cycle is about to be modified218 run(vars, "prehook")219 modified = true220 }221 fmt.Println("build:", path)222 return build(path, nil, vars)223 }224 return nil225 })226 if modified {227 // At least one file in this build cycle has been modified228 run(vars, "posthook")229 modified = false230 }231 if !watch {232 break233 }234 lastModified = time.Now()235 time.Sleep(1 * time.Second)236 }237 }238 19 239 20 // Initialize the environment … … 295 76 } 296 77 case "version": 297 fmt.Printf("%v\n", aya. FullVersion())78 fmt.Printf("%v\n", aya.PrintVersion()) 298 79 os.Exit(0) 299 80 case "watch":
Note:
See TracChangeset
for help on using the changeset viewer.