Skip to content

Commit

Permalink
yaml-seam: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidGamba committed Feb 15, 2023
1 parent 6506b5c commit 8eb238c
Show file tree
Hide file tree
Showing 4 changed files with 290 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ ffind/ffind
grepp/grepp
joinlines/joinlines
password-cache/cmd/password-cache/password-cache
reverseproxy/reverseproxy
webserve/webserve
yaml-parse/yaml-parse
reverseproxy/reverseproxy
yaml-seam/yaml-seam
10 changes: 10 additions & 0 deletions yaml-seam/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/DavidGamba/dgtools/yaml-seam

go 1.19

require (
github.com/DavidGamba/dgtools/trees v0.1.0 // indirect
github.com/DavidGamba/dgtools/yamlutils v0.0.0-20230124052542-bdc68694a034 // indirect
github.com/DavidGamba/go-getoptions v0.26.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
9 changes: 9 additions & 0 deletions yaml-seam/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
github.com/DavidGamba/dgtools/trees v0.1.0 h1:8FllYXayVpLNomlhGUFEl0gfUxymOrqTkNyYh7QARHE=
github.com/DavidGamba/dgtools/trees v0.1.0/go.mod h1:H9UAZh3Frqm446RrXST+rQXl4diaTMX7TLwP4f98zK0=
github.com/DavidGamba/dgtools/yamlutils v0.0.0-20230124052542-bdc68694a034 h1:9hpuPH/f0yqt/1VzH7dCUN1pKvnK6HbnXTs2uRXAd+k=
github.com/DavidGamba/dgtools/yamlutils v0.0.0-20230124052542-bdc68694a034/go.mod h1:lsTVYmXRS1b7lsExi1SbsTwFTraqlrezQq+N/NaQpUQ=
github.com/DavidGamba/go-getoptions v0.26.0 h1:3Hy0o6xTYs9icM5uPNqqck5PyZm5lZWRIDH/n5SV2uw=
github.com/DavidGamba/go-getoptions v0.26.0/go.mod h1:qLaLSYeQ8sUVOfKuu5JT5qKKS3OCwyhkYSJnoG+ggmo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
269 changes: 269 additions & 0 deletions yaml-seam/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
// This file is part of dgtools.
//
// # Copyright (C) 2019-2023 David Gamba Rios
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package main

import (
"context"
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"

"github.com/DavidGamba/dgtools/yamlutils"
"github.com/DavidGamba/go-getoptions"
)

// BuildMetadata - Provides the metadata part of the version information.
var BuildMetadata = "dev"

const semVersion = "0.1.0"

var Logger = log.New(os.Stderr, "", log.LstdFlags)

func main() {
os.Exit(program(os.Args))
}

func program(args []string) int {
opt := getoptions.New()
opt.Self("", `Parses YAML input passed from file(s) or piped to STDIN and allows to split it or combine it.
Source: https://github.com/DavidGamba/dgtools`)
opt.Bool("quiet", false, opt.GetEnv("QUIET"))
opt.Bool("version", false, opt.Alias("V"))
opt.Bool("silent", false, opt.Description("Don't print full context errors."))

read := opt.NewCommand("read", "read a multi document yaml file")
read.HelpSynopsisArgs("<file>...")
read.Bool("-", false, opt.Description("Read from STDIN"))
read.StringSlice("key", 1, 99, opt.Alias("k"), opt.ArgName("key/index"),
opt.Description(`Key or index to descend to.
Multiple keys allow to descend further.
Indexes are positive integers.`))
read.SetCommandFn(ReadRun)

split := opt.NewCommand("split", "split a multi document YAML file")
split.HelpSynopsisArgs("<file>...")
split.Bool("-", false, opt.Description("Read from STDIN"))
split.Bool("force", false, opt.Description("Apply split"))
split.String("dir", "", opt.Description("Output directory to write files to. Defaults to same as source."))
split.String("output-prefix", "", opt.Alias("prefix"), opt.Description("Output Filename prefix"))
split.SetCommandFn(SplitRun)

join := opt.NewCommand("join", "join multiple YAML files into a single multi document one")
join.HelpSynopsisArgs("<file>...")
join.String("output", "", opt.Required(), opt.Description("Output file"))
join.SetCommandFn(JoinRun)

opt.HelpCommand("help", opt.Alias("?"))
remaining, err := opt.Parse(args[1:])
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
return 1
}
if opt.Called("version") {
fmt.Printf("Version: %s+%s\n", semVersion, BuildMetadata)
return 0
}
if opt.Called("quiet") {
Logger.SetOutput(io.Discard)
}
Logger.Println(remaining)

ctx, cancel, done := getoptions.InterruptContext()
defer func() { cancel(); <-done }()

err = opt.Dispatch(ctx, remaining)
if err != nil {
if errors.Is(err, getoptions.ErrorHelpCalled) {
return 1
}
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
return 1
}
return 0
}

func readInput(ctx context.Context, useStdIn bool, file string) ([]*yamlutils.YML, error) {
// Check if stdin is pipe p or device D
statStdin, _ := os.Stdin.Stat()
stdinIsDevice := (statStdin.Mode() & os.ModeDevice) != 0

var err error
var ymlList []*yamlutils.YML
if !stdinIsDevice && useStdIn {
Logger.Printf("Reading from STDIN\n")
reader := os.Stdin
ymlList, err = yamlutils.NewFromReader(reader)
if err != nil {
return ymlList, fmt.Errorf("reading yaml from STDIN: %w", err)
}
} else {
Logger.Printf("Reading from file: %s\n", file)
ymlList, err = yamlutils.NewFromFile(file)
if err != nil {
return ymlList, fmt.Errorf("reading yaml file: %w", err)
}
}
return ymlList, nil
}

func ReadRun(ctx context.Context, opt *getoptions.GetOpt, args []string) error {
keys := opt.Value("key").([]string)
useStdIn := opt.Value("-").(bool)

if len(args) < 1 && !useStdIn {
fmt.Fprintf(os.Stderr, "ERROR: missing <file> or STDIN input '-'\n")
fmt.Fprintf(os.Stderr, "%s", opt.Help(getoptions.HelpSynopsis))
return getoptions.ErrorHelpCalled
}

var xpaths [][]string
for _, k := range keys {
var xpath []string
k = strings.TrimLeft(k, "/")
xpath = append(xpath, strings.Split(k, "/")...)
Logger.Printf("path: '%s'\n", strings.Join(xpath, ","))
xpaths = append(xpaths, xpath)
}

errorCount := 0
files := args
if useStdIn {
files = append(files, "-")
}
for _, file := range files {
sIn := false
if file == "-" {
sIn = true
}
ymlList, err := readInput(ctx, sIn, file)
if err != nil {
return fmt.Errorf("failed to read input: %w", err)
}

for i, yml := range ymlList {
fmt.Printf("# %s %02d\n", file, i+1)
for _, xpath := range xpaths {
str, err := yml.GetString(false, xpath)
if err != nil {
errorCount++
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
if !opt.Called("silent") {
fmt.Fprintf(os.Stderr, ">\t%s\n", strings.ReplaceAll(str, "\n", "\n>\t"))
}
continue
}
str = strings.TrimSpace(str)
fmt.Println(str)
}
}
}

if errorCount > 0 {
return fmt.Errorf("found %d errors when reading documents", errorCount)
}

return nil
}

func SplitRun(ctx context.Context, opt *getoptions.GetOpt, args []string) error {
dir := opt.Value("dir").(string)
force := opt.Value("force").(bool)
useStdIn := opt.Value("-").(bool)
oPrefix := opt.Value("output-prefix").(string)

if len(args) < 1 && !useStdIn {
fmt.Fprintf(os.Stderr, "ERROR: missing <file> or STDIN input '-'\n")
fmt.Fprintf(os.Stderr, "%s", opt.Help(getoptions.HelpSynopsis))
return getoptions.ErrorHelpCalled
}
if useStdIn && !opt.Called("output-prefix") {
fmt.Fprintf(os.Stderr, "ERROR: --output-prefix <string> is required when reading from STDIN '-'\n")
fmt.Fprintf(os.Stderr, "%s", opt.Help(getoptions.HelpSynopsis))
return getoptions.ErrorHelpCalled
}

file := "-"
if len(args) > 0 {
file = args[0]
}

if oPrefix == "" {
oPrefix = strings.TrimSuffix(file, ".yaml")
oPrefix = strings.TrimSuffix(oPrefix, ".yml")
}
if oPrefix == "" {
oPrefix = "stdin"
}
outputDir := filepath.Dir(oPrefix)
if dir != "" {
outputDir = dir
}
if force {
_ = os.MkdirAll(outputDir, 0755)
}

oPrefix = filepath.Base(oPrefix)

tformat := `%[1]s-%02[2]d.yaml`

ymlList, err := readInput(ctx, useStdIn, file)
if err != nil {
return fmt.Errorf("failed to read input: %w", err)
}

for i, yml := range ymlList {
filename := fmt.Sprintf(tformat, oPrefix, i+1)
filename = filepath.Join(outputDir, filename)
fmt.Printf("%s\n", filename)
if force {
str, err := yml.GetString(false, []string{})
if err != nil {
return fmt.Errorf("failed to read document %d: %w", i+1, err)
}
os.WriteFile(filename, []byte(str), 0640)
}
}

if !force {
fmt.Fprintf(os.Stderr, "WARNING: Running in Dry Run mode, use --force to apply changes\n")
}

return nil
}

func JoinRun(ctx context.Context, opt *getoptions.GetOpt, args []string) error {
output := opt.Value("output").(string)

if len(args) < 1 {
fmt.Fprintf(os.Stderr, "ERROR: missing <file>\n")
fmt.Fprintf(os.Stderr, "%s", opt.Help(getoptions.HelpSynopsis))
return getoptions.ErrorHelpCalled
}

fh, err := os.Create(output)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
}

for i, file := range args {
if i > 0 {
fh.WriteString("\n---\n")
}
contents, err := os.ReadFile(file)
if err != nil {
return fmt.Errorf("failed to read file '%s': %w", file, err)
}
fh.Write(contents)
}
return nil
}

0 comments on commit 8eb238c

Please sign in to comment.