Changeset 80 in code for trunk/cmd


Ignore:
Timestamp:
Dec 12, 2023, 2:09:17 PM (18 months ago)
Author:
Izuru Yakumo
Message:

A good time to finally release a stable version

Signed-off-by: Izuru Yakumo <yakumo.izuru@…>

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, v0.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 $
    22package main
    33
    44import (
    5         "bytes"
    65        "fmt"
    7         "io"
    86        "os"
    9         "os/exec"
    10         "path/filepath"
    117        "strings"
    12         "time"
    138
    14         "gopkg.in/yaml.v3"
    159        "marisa.chaotic.ninja/aya"
    1610)
     
    2317
    2418type Vars map[string]string
    25 
    26 // renameExt renames extension (if any) from oldext to newext
    27 // If oldext is an empty string - extension is extracted automatically.
    28 // If path has no extension - new extension is appended
    29 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) + newext
    35         }
    36         return path
    37 }
    38 
    39 // globals returns list of global OS environment variables that start
    40 // with AYA_ prefix as Vars, so the values can be used inside templates
    41 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 vars
    50 }
    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_ prefix
    54 // prepended.  Additional variable $AYA contains path to the aya binary. Command
    55 // 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), nil
    60         }
    61 
    62         var errbuf, outbuf bytes.Buffer
    63         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 = env
    70         c.Stdout = &outbuf
    71         c.Stderr = &errbuf
    72 
    73         err := c.Run()
    74 
    75         if errbuf.Len() > 0 {
    76                 fmt.Println("ERROR:", errbuf.String())
    77         }
    78         if err != nil {
    79                 return "", err
    80         }
    81         return string(outbuf.Bytes()), nil
    82 }
    83 
    84 // getVars returns list of variables defined in a text file and actual file
    85 // content following the variables declaration. Header is separated from
    86 // 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, "", err
    92         }
    93         s := string(b)
    94 
    95         // Pick some default values for content-dependent variables
    96         v := Vars{}
    97         title := strings.Replace(strings.Replace(path, "_", " ", -1), "-", " ", -1)
    98         v["title"] = strings.ToTitle(title)
    99         v["description"] = ""
    100         v["file"] = path
    101         v["url"] = path[:len(path)-len(filepath.Ext(path))] + ".html"
    102         v["output"] = filepath.Join(PUBDIR, v["url"])
    103 
    104         // Override default values with globals
    105         for name, value := range globals {
    106                 v[name] = value
    107         }
    108 
    109         // Add layout if none is specified
    110         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, nil
    117         } 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, "", err
    125                 } else {
    126                         // Override default values + globals with the ones defines in the file
    127                         for key, value := range vars {
    128                                 v[key] = value
    129                         }
    130                 }
    131                 if strings.HasPrefix(v["url"], "./") {
    132                         v["url"] = v["url"][2:]
    133                 }
    134                 return v, body, nil
    135         }
    136 }
    137 
    138 // Render expanding aya plugins and variables
    139 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(), nil
    148                 } 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                                                 continue
    160                                         }
    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 functions
    174 // 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 PUBDIR
    192 // If boolean watch is true, it keeps on going
    193 // every time you modify something.
    194 func buildAll(watch bool) {
    195         lastModified := time.Unix(0, 0)
    196         modified := false
    197 
    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 directories
    203                         if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") {
    204                                 return nil
    205                         }
    206                         // inform user about fs walk errors, but continue iteration
    207                         if err != nil {
    208                                 fmt.Println("error:", err)
    209                                 return nil
    210                         }
    211 
    212                         if info.IsDir() {
    213                                 os.Mkdir(filepath.Join(PUBDIR, path), 0755)
    214                                 return nil
    215                         } else if info.ModTime().After(lastModified) {
    216                                 if !modified {
    217                                         // First file in this build cycle is about to be modified
    218                                         run(vars, "prehook")
    219                                         modified = true
    220                                 }
    221                                 fmt.Println("build:", path)
    222                                 return build(path, nil, vars)
    223                         }
    224                         return nil
    225                 })
    226                 if modified {
    227                         // At least one file in this build cycle has been modified
    228                         run(vars, "posthook")
    229                         modified = false
    230                 }
    231                 if !watch {
    232                         break
    233                 }
    234                 lastModified = time.Now()
    235                 time.Sleep(1 * time.Second)
    236         }
    237 }
    23819
    23920// Initialize the environment
     
    29576                }
    29677        case "version":
    297                 fmt.Printf("%v\n", aya.FullVersion())
     78                fmt.Printf("%v\n", aya.PrintVersion())
    29879                os.Exit(0)
    29980        case "watch":
Note: See TracChangeset for help on using the changeset viewer.