go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/projects/blogctl/pkg/cmd/init.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package cmd 9 10 import ( 11 "bufio" 12 "fmt" 13 "io" 14 "os" 15 "path/filepath" 16 17 "github.com/urfave/cli/v2" 18 "go.charczuk.com/sdk/slant" 19 20 "go.charczuk.com/projects/blogctl/pkg/config" 21 "go.charczuk.com/projects/blogctl/pkg/constants" 22 "go.charczuk.com/projects/blogctl/pkg/engine" 23 ) 24 25 // Init returns the init command. 26 func Init() *cli.Command { 27 return &cli.Command{ 28 Name: "init", 29 Usage: "Initialize a new blog", 30 ArgsUsage: "[NAME]", 31 Action: func(ctx *cli.Context) error { 32 name := ctx.Args().First() 33 if name == "" { 34 return fmt.Errorf("must provide a [NAME]") 35 } 36 37 slant.Print(os.Stdout, "blogctl") 38 fmt.Println("Initializing a new blog") 39 fmt.Println("Please provide the fields for the `config.yml`") 40 fmt.Println("They will be prompted as `Field (explanation) [default value]`") 41 42 // create a new config 43 cfg := new(config.Config) 44 fields := cfg.Fields() 45 var value string 46 for _, field := range fields { 47 if field.Default != "" { 48 value = Promptf("%s [default %s]: ", field.Prompt, field.Default) 49 } else { 50 value = Promptf("%s: ", field.Prompt) 51 } 52 if value != "" { 53 *field.FieldReference = value 54 } else { 55 *field.FieldReference = field.Default 56 } 57 } 58 59 if err := engine.MakeDir(name); err != nil { 60 return err 61 } 62 if err := engine.MakeDir(filepath.Join(name, cfg.PostsPathOrDefault())); err != nil { 63 return err 64 } 65 if err := engine.MakeDir(filepath.Join(name, cfg.PagesPathOrDefault())); err != nil { 66 return err 67 } 68 if err := engine.MakeDir(filepath.Join(name, cfg.PartialsPathOrDefault())); err != nil { 69 return err 70 } 71 if err := engine.MakeDir(filepath.Join(name, cfg.StaticsPathOrDefault())); err != nil { 72 return err 73 } 74 if err := engine.MakeDir(filepath.Join(name, cfg.StaticsPathOrDefault(), "css")); err != nil { 75 return err 76 } 77 if err := engine.WriteYAML(filepath.Join(name, ctx.String(config.FlagConfig)), cfg); err != nil { 78 return err 79 } 80 81 /* write individual files */ 82 if err := engine.WriteFile(filepath.Join(name, cfg.PartialsPathOrDefault(), "header.html"), []byte(headerHTML)); err != nil { 83 return err 84 } 85 if err := engine.WriteFile(filepath.Join(name, cfg.PartialsPathOrDefault(), "footer.html"), []byte(footerHTML)); err != nil { 86 return err 87 } 88 if err := engine.WriteFile(filepath.Join(name, cfg.PagesPathOrDefault(), constants.FileIndex), []byte(indexHTML)); err != nil { 89 return err 90 } 91 if err := engine.WriteFile(filepath.Join(name, cfg.ImagePostTemplateOrDefault()), []byte(imageHTML)); err != nil { 92 return err 93 } 94 if err := engine.WriteFile(filepath.Join(name, cfg.TextPostTemplateOrDefault()), []byte(textHTML)); err != nil { 95 return err 96 } 97 if err := engine.WriteFile(filepath.Join(name, cfg.TagTemplateOrDefault()), []byte(tagHTML)); err != nil { 98 return err 99 } 100 if err := engine.WriteFile(filepath.Join(name, cfg.StaticsPathOrDefault(), "css/site.css"), []byte(siteCSS)); err != nil { 101 return err 102 } 103 return nil 104 }, 105 } 106 } 107 108 const ( 109 headerHTML = `{{ define "header" }} 110 <!DOCTYPE html> 111 <html lang="en"> 112 <head> 113 <meta charset="utf-8"> 114 <title>{{ .TitleOrDefault }}</title> 115 <meta name="author" content="{{ .Config.Author }}"> 116 <meta name="description" content="{{ .Config.Description }}"> 117 <meta name="viewport" content="width=device-width, initial-scale=1"> 118 <link rel="stylesheet" href="/css/site.css"> 119 </head> 120 <body> 121 <div class="content"> 122 {{ end }}` 123 124 footerHTML = `{{ define "footer" }} 125 </div> 126 </body> 127 </html> 128 {{ end }}` 129 130 indexHTML = `{{ template "header" . }} 131 {{ range $index, $post := .Posts }} 132 <div class="post"> 133 <img src="{{ $post.ImageSourceSmall }}" /> 134 </div> 135 {{ else }} 136 <h2>No Posts.</h2> 137 {{ end }} 138 {{ template "footer" . }}` 139 140 imageHTML = `{{ template "header" . }} 141 <div class="image post"> 142 <img src="{{ .Post.ImageSourceLarge}}" /> 143 </div> 144 {{ template "footer" . }}` 145 146 textHTML = `{{ template "header" . }} 147 <div class="text post"> 148 {{ render_post .Post }} 149 </div> 150 {{ template "footer" . }}` 151 152 tagHTML = `{{ template "header" . }} 153 <div class="tag"> 154 {{ range $index, $post := .Tag.Posts }} 155 <div class="post"> 156 <img src="{{$post.ImageSourceSmall}}" /> 157 </div> 158 {{ else }} 159 <h2>No Posts For Tag.</h2> 160 {{ end }} 161 </div> 162 {{ template "footer" . }}` 163 164 siteCSS = `body { font-family: 'sans-serif'; margin: 0; padding: 0; } 165 166 .post { display: inline-block; } 167 168 .post img { 169 width: auto; 170 max-width: calc(100vw - 20px); 171 height: auto; 172 max-height: calc(100vh - 20px); 173 }` 174 ) 175 176 // Prompt gives a prompt and reads input until newlines. 177 func Prompt(prompt string) string { 178 return PromptFrom(os.Stdout, os.Stdin, prompt) 179 } 180 181 // Promptf gives a prompt of a given format and args and reads input until newlines. 182 func Promptf(format string, args ...interface{}) string { 183 return PromptFrom(os.Stdout, os.Stdin, fmt.Sprintf(format, args...)) 184 } 185 186 // PromptFrom gives a prompt and reads input until newlines from a given set of streams. 187 func PromptFrom(stdout io.Writer, stdin io.Reader, prompt string) string { 188 fmt.Fprint(stdout, prompt) 189 190 scanner := bufio.NewScanner(stdin) 191 var output string 192 if scanner.Scan() { 193 output = scanner.Text() 194 } 195 return output 196 }