Skip to content

Commit

Permalink
feat: Implement parser command for JSON output (#9)
Browse files Browse the repository at this point in the history
* feat: Add IsValidFileSystemSource func

* feat: Implement parsing command

* feat: Add verbose logging to parse command

* chore: go mod tidy
  • Loading branch information
MaikelVeen authored May 9, 2024
1 parent dedcc35 commit 37011bc
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 13 deletions.
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,10 @@ go.work
glass

# WIP
docs/how_to_guides/*
docs/how_to_guides/*

# Default parser output
/out

# Vscode
.vscode
2 changes: 1 addition & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ linters:
- exhaustive # checks exhaustiveness of enum switch statements
- exportloopref # checks for pointers to enclosing loop variables
- fatcontext # detects nested contexts in loops
- forbidigo # forbids identifiers
#- forbidigo # forbids identifiers
- funlen # tool for detection of long functions
- gocheckcompilerdirectives # validates go compiler directive comments (//go:)
#- gochecknoglobals # checks that no global variables exist
Expand Down
74 changes: 72 additions & 2 deletions cmd/parse.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
package cmd

import (
"encoding/json"
"errors"
"fmt"
"os"

"github.com/glass-cms/glasscms/item"
"github.com/glass-cms/glasscms/parser"
"github.com/glass-cms/glasscms/sourcer"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tidwall/pretty"
)

const (
Expand Down Expand Up @@ -32,6 +41,67 @@ func NewParseCommand() *ParseCommand {
return c
}

func (c *ParseCommand) Execute(_ *cobra.Command, _ []string) error {
return nil
func (c *ParseCommand) Execute(_ *cobra.Command, args []string) error {
sourcePath := args[0]
if err := sourcer.IsValidFileSystemSource(sourcePath); err != nil {
return err
}

fileSystemSourcer, err := sourcer.NewFileSystemSourcer(sourcePath)
if err != nil {
return err
}

// Iterate over the source files and parse them.
var items []*item.Item
for {
var src sourcer.Source
src, err = fileSystemSourcer.Next()
if errors.Is(err, sourcer.ErrDone) {
break
}

if err != nil {
return err
}

var i *item.Item
i, err = parser.Parse(src)
if err != nil {
return err
}

items = append(items, i)
}

// Write the parsed items to the output destination.
output := viper.GetString(ArgOutput)
return writeItems(items, output)
}

func writeItems(items []*item.Item, output string) error {
// TODO: Make configurable if all items should be written to a single file or multiple files.
// TODO: Make content type configurable.
// TODO: Make the filename configurable.
itemsJSON, err := json.Marshal(items)
if err != nil {
return err
}

// Print the JSON to the console if verbose mode is enabled.
if viper.GetBool(ArgsVerbose) {
j := pretty.Pretty(itemsJSON)
fmt.Println(string(pretty.Color(j, nil)))
}

// If the output directory does not exist, create it.
if _, err = os.Stat(output); os.IsNotExist(err) {
if err = os.MkdirAll(output, 0755); err != nil {
return err
}
}

// Combine the output path with the filename.
fn := output + "/output.json"
return os.WriteFile(fn, itemsJSON, 0600)
}
46 changes: 46 additions & 0 deletions cmd/parse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cmd_test

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"testing"

"github.com/glass-cms/glasscms/cmd"
"github.com/glass-cms/glasscms/item"
"github.com/stretchr/testify/require"
)

func Test_ParseCommand(t *testing.T) {
t.Parallel()

tempDir, err := os.MkdirTemp("", "glasscms")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDir)

command := cmd.NewParseCommand()
command.SetArgs([]string{"../docs/commands", fmt.Sprintf("--%s", cmd.ArgOutput), tempDir})

err = command.Command.Execute()
require.NoError(t, err)

// Assert that the output directory contains the expected JSON file.
// And that the file is not empty.

// Read the JSON file.
var items []*item.Item
file, err := os.Open(filepath.Join(tempDir, "output.json"))
if err != nil {
t.Fatal(err)
}

err = json.NewDecoder(file).Decode(&items)
if err != nil {
t.Fatal(err)
}

require.NotEmpty(t, items)
}
15 changes: 15 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import (
)

const (
ArgsVerbose = "verbose"
ArgsVerboseShorthand = "v"

defaultConfigFilename = "config"
envPrefix = "GLASS"
)
Expand All @@ -31,8 +34,20 @@ func Execute() {
}

func init() {
//
// Add commands to the root command.
//
rootCmd.AddCommand(NewParseCommand().Command)
rootCmd.AddCommand(NewDocsCommand().Command)

//
// Register flags.
//

pflags := rootCmd.PersistentFlags()

pflags.BoolP(ArgsVerbose, ArgsVerboseShorthand, false, "Enable verbose output")
_ = viper.BindPFlag(ArgsVerbose, pflags.Lookup(ArgsVerbose))
}

func initializeConfig(cmd *cobra.Command) error {
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ go 1.22.0
require (
github.com/djherbis/times v1.6.0
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.18.2
github.com/stretchr/testify v1.9.0
github.com/tidwall/pretty v1.2.1
golang.org/x/text v0.15.0
gopkg.in/yaml.v3 v3.0.1
)

Expand All @@ -26,12 +29,10 @@ require (
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.15.0
gopkg.in/ini.v1 v1.67.0 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
Expand Down
10 changes: 5 additions & 5 deletions item/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package item
import "time"

type Item struct {
Title string
Content string
CreateTime time.Time
UpdateTime time.Time
Properties map[string]any
Name string `json:"name"`
Content string `json:"content"`
CreateTime time.Time `json:"create_time"`
UpdateTime time.Time `json:"update_time"`
Properties map[string]any `json:"properties"`
}
2 changes: 1 addition & 1 deletion parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func Parse(src sourcer.Source) (*item.Item, error) {
content := string(parts[2])

return &item.Item{
Title: src.Name(),
Name: src.Name(),
Content: content,
CreateTime: src.CreatedAt(),
UpdateTime: src.ModifiedAt(),
Expand Down
2 changes: 1 addition & 1 deletion parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestParse(t *testing.T) {
// Assert
require.NoError(t, err)
assert.NotNil(t, item)
assert.Equal(t, "test", item.Title)
assert.Equal(t, "test", item.Name)
assert.Equal(t, "# Test\n", item.Content)
assert.Equal(t, map[string]interface{}{"title": "Test"}, item.Properties)
}
19 changes: 19 additions & 0 deletions sourcer/file_system.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package sourcer

import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
)

var ErrInvalidFileSystemSource = errors.New("invalid file system source")

var _ DataSourcer = &FileSystemSourcer{}

// FileSystemSourcer is a DataSourcer that reads files from the file system.
Expand Down Expand Up @@ -61,3 +65,18 @@ func (s *FileSystemSourcer) Remaining() int {
func (s *FileSystemSourcer) Size() int {
return len(s.files)
}

// IsValidFileSystemSource checks if the given path exist and is a directory.
func IsValidFileSystemSource(fp string) error {
fileInfo, err := os.Stat(fp)
if err != nil {
// Wrap the error to provide more context.
return fmt.Errorf("%w: %w", ErrInvalidFileSystemSource, err)
}

if !fileInfo.IsDir() {
return fmt.Errorf("%w: %s is not a directory", ErrInvalidFileSystemSource, fp)
}

return nil
}
14 changes: 14 additions & 0 deletions sourcer/file_system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/glass-cms/glasscms/sourcer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type testFile struct {
Expand Down Expand Up @@ -169,3 +170,16 @@ func TestFileSystemSourcer_Size(t *testing.T) {
})
}
}

func TestIsValidFileSystemSource(t *testing.T) {
t.Parallel()

fp, err := createTempDirWithFiles([]testFile{{content: "file 1", depth: 0, pattern: "*.md"}})
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(fp)

require.NoError(t, sourcer.IsValidFileSystemSource(fp))
require.Error(t, sourcer.IsValidFileSystemSource("non-existent"))
}

0 comments on commit 37011bc

Please sign in to comment.