Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement parser command for JSON output #9

Merged
merged 4 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"))
}
Loading