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  }