M builder.go => builder.go +7 -2
@@ 174,6 174,13 @@ func Build(root string, args *Args) (*Config, error) {
}
}
}
+ // 9b. Generate the metadata files
+ if cfg.BuildMetadataGeneration.Enabled {
+ err = fb.HandleBuildMetadata()
+ if err != nil {
+ return cfg, err
+ }
+ }
// 9. Run all post-build commands, if applicable
if !fb.RunPostBuildCommands() {
@@ 268,7 275,5 @@ func (f *FsBuilder) RunPostBuildCommands() bool {
domain.L.Infof("- [%s] %s", cmd, afterLabel)
}
- println()
-
return true
}
M config.go => config.go +3 -0
@@ 42,6 42,9 @@ func LoadConfig(path string) (*Config, error) {
CommandTimeout: 5,
},
Redirects: map[string]Redirect{},
+ BuildMetadataGeneration: BuildMetadataGeneration{
+ OutputFolder: "/.meta",
+ },
}
mergo.Merge(&outputCfg, cfg, mergo.WithOverride)
if len(outputCfg.PostBuildCommands) > 0 {
M domain/index.go => domain/index.go +1 -0
@@ 19,6 19,7 @@ type Link struct {
}
// Index holds the list of all files, found links, and derived dead links.
+// It only handles relative / "internal" links, not links to other domains
type Index struct {
Files map[string]ObjectType
DeadLinks []*Link
M handler.go => handler.go +41 -2
@@ 304,6 304,8 @@ func (f *FsBuilder) HandleMediaCompression(media *domain.ContentFile, optimizati
ext := filepath.Ext(to)[1:]
if len(optimizationParameters.Format) != 0 && ext != optimizationParameters.Format {
+ // This should not be done here
+ // ideally the index should not be modified anymore in the handler step
newFrom := strings.TrimSuffix(from, ext) + optimizationParameters.Format
to = strings.TrimSuffix(to, ext) + optimizationParameters.Format
delete(f.Index.Files, from)
@@ 342,7 344,7 @@ func (f *FsBuilder) HandleFile(file *domain.ContentFile) error {
domain.L.Debugf("Copying file from `%s` to `%s`", from, to)
fileSrc, err := os.Open(from)
if err != nil {
- domain.L.Debugf("Failed to find source file at %s", from)
+ domain.L.Errorf("Failed to find source file at %s", from)
return err
}
defer fileSrc.Close()
@@ 351,7 353,7 @@ func (f *FsBuilder) HandleFile(file *domain.ContentFile) error {
dest, err := os.Create(to)
if err != nil {
- domain.L.Debugf("Failed to create dest file at %s", to)
+ domain.L.Errorf("Failed to create destination file at %s", to)
return err
}
defer dest.Close()
@@ 359,3 361,40 @@ func (f *FsBuilder) HandleFile(file *domain.ContentFile) error {
_, err = io.Copy(dest, src)
return err
}
+
+func (f *FsBuilder) HandleBuildMetadata() error {
+ outputPath := filepath.Join(f.Config.BuildMetadataGeneration.OutputFolder, "metrics")
+ buildTime := time.Now().Unix()
+
+ metricsText := strings.Join([]string{
+ "# HELP last_build A placeholder value of 1, its timestamp marks the time at which this file was last built",
+ fmt.Sprintf("last_build 1 %d", buildTime),
+ "",
+ "# HELP files The count of files in the capsule",
+ "# TYPE files counter",
+ fmt.Sprintf("files %d", len(f.Index.Files)),
+ "",
+ "# HELP internal_links The count of internal links between pages and files in the capsule",
+ "# TYPE internal_links counter",
+ fmt.Sprintf("internal_links{match=\"found\"} %d", len(f.Index.FoundLinks)),
+ fmt.Sprintf("internal_links{match=\"redirects\"} %d", len(f.Config.Redirects)),
+ fmt.Sprintf("internal_links{match=\"dead\"} %d", len(f.Index.DeadLinks)),
+ "",
+ }, "\n")
+
+ // TODO: move mkdir perms to constant
+ metricsPath := f.GetPath(f.Config.BuildMetadataGeneration.OutputFolder, true)
+ err := os.MkdirAll(metricsPath, 0755)
+
+ if err != nil {
+ return err
+ }
+
+ f.HandleFile(&domain.ContentFile{
+ Path: outputPath,
+ RelativePath: outputPath,
+ Raw: []byte(metricsText),
+ Kind: domain.ContentFileTypeRaw,
+ })
+ return err
+}
M shell.nix => shell.nix +4 -1
@@ 1,4 1,7 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
- nativeBuildInputs = with pkgs; [ go python3Full imagemagick ];
+ nativeBuildInputs = with pkgs; [
+ go gopls delve
+ python3Full imagemagick
+ ];
}
M types.go => types.go +20 -8
@@ 58,6 58,19 @@ type PostBuildConfig struct {
CommandTimeout time.Duration `toml:"command_timeout"`
}
+// BuildMetadataGeneration allows configuring the generation of various build-related metadata made to simplify
+// subsequent builds or offer simple ways to keep track of build stats (incl. issues)
+type BuildMetadataGeneration struct {
+ Enabled bool `toml:"enabled"`
+ // Absolute path relative to the capsule root, this folder will contain the build metadata files.
+ // Try to dedicate a path to it to avoid cases where cap could overwrite some files you made
+ // (defaults to `/.meta/`)
+ OutputFolder string `toml:"output_folder"`
+}
+
+// Redirect stores a redirection to another page alongside the title to give the redirection (e.g. the new page title)
+// of the format "~notebook/recipe.gmi" => "~notebook/recipes/index.gmi" for example
+// all paths are relative to the capsule root
type Redirect struct {
To string `toml:"to"`
Title string `toml:"title"`
@@ 65,14 78,13 @@ type Redirect struct {
// Config stores the capsule's configuration, including metadata, media optimization info, post-build commands, etc.
type Config struct {
- Capsule CapsuleConfig
- Thumbnails OptimizationParameters
- PostBuildCommands []string `toml:"post_build_commands"`
- PostBuild PostBuildConfig `toml:"post_build"`
- FoldersToIgnore []string `toml:"ignore"`
- // of the format "~notebook/recipe.gmi" = "~notebook/recipes/index.gmi" for example
- // all paths are relative to the capsule root
- Redirects map[string]Redirect `toml:"redirects"`
+ Capsule CapsuleConfig
+ Thumbnails OptimizationParameters
+ PostBuildCommands []string `toml:"post_build_commands"`
+ PostBuild PostBuildConfig `toml:"post_build"`
+ FoldersToIgnore []string `toml:"ignore"`
+ Redirects map[string]Redirect `toml:"redirects"`
+ BuildMetadataGeneration BuildMetadataGeneration `toml:"build_metadata"`
}
// WatchCMD contains the subcommand's flags and arguments