diff --git a/.gitignore b/.gitignore index e47826c..c32300b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ !/internal/repository/ /repository.yaml /blox -/_build/ \ No newline at end of file +/_build/ +cli/blox/blox \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml index 537e07f..0f7883c 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -2,7 +2,7 @@ project_name: blox builds: - id: blox - main: ./main.go + main: ./cli/blox/main.go binary: blox # Default is `-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser`. ldflags: diff --git a/main.go b/cli/blox/main.go similarity index 100% rename from main.go rename to cli/blox/main.go diff --git a/cmd/blox_build.go b/cmd/blox_build.go index a7dcebf..9b2faae 100644 --- a/cmd/blox_build.go +++ b/cmd/blox_build.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strings" + "github.com/cueblox/blox" "github.com/cueblox/blox/internal/cuedb" "github.com/cueblox/blox/internal/encoding/markdown" "github.com/goccy/go-yaml" @@ -25,16 +26,12 @@ var buildCmd = &cobra.Command{ Use: "build", Short: "Validate and build your data", Run: func(cmd *cobra.Command, args []string) { - // This can happen at a global Cobra level, if I knew how - config, err := cuedb.NewRuntime() + engine, err := cuedb.NewEngine() cobra.CheckErr(err) - cobra.CheckErr(config.LoadConfig()) - - runtime, err := cuedb.NewRuntime() - cobra.CheckErr(err) - + cfg, err := blox.NewConfig(cuedb.BaseConfig) // Load Schemas! - schemaDir, err := config.GetString("schema_dir") + schemaDir, err := cfg.GetString("schema_dir") + pterm.Debug.Printf("\t\tSchema Directory: %s\n", schemaDir) cobra.CheckErr(err) err = filepath.WalkDir(schemaDir, func(path string, d fs.DirEntry, err error) error { @@ -46,8 +43,9 @@ var buildCmd = &cobra.Command{ if err != nil { return err } + pterm.Debug.Printf("\t\tLoading Schema: %s\n", path) - err = runtime.RegisterSchema(string(bb)) + err = engine.RegisterSchema(string(bb)) if err != nil { return err } @@ -55,12 +53,13 @@ var buildCmd = &cobra.Command{ return nil }) cobra.CheckErr(err) + pterm.Debug.Println("\t\tBuilding Models") - cobra.CheckErr(buildModels(&config, &runtime)) + cobra.CheckErr(buildModels(engine, cfg)) if referentialIntegrity { pterm.Info.Println("Checking Referential Integrity") - err = runtime.ReferentialIntegrity() + err = engine.ReferentialIntegrity() if err != nil { pterm.Error.Println(err) } else { @@ -68,13 +67,13 @@ var buildCmd = &cobra.Command{ } } - output, err := runtime.GetOutput() + output, err := engine.GetOutput() cobra.CheckErr(err) jso, err := output.MarshalJSON() cobra.CheckErr(err) - buildDir, err := config.GetString("build_dir") + buildDir, err := cfg.GetString("build_dir") cobra.CheckErr(err) err = os.MkdirAll(buildDir, 0755) cobra.CheckErr(err) @@ -86,15 +85,17 @@ var buildCmd = &cobra.Command{ }, } -func buildModels(config *cuedb.Runtime, runtime *cuedb.Runtime) error { +func buildModels(engine *cuedb.Engine, cfg *blox.Config) error { var errors error pterm.Info.Println("Validating ...") - for _, dataSet := range runtime.GetDataSets() { + for _, dataSet := range engine.GetDataSets() { + pterm.Debug.Printf("\t\tDataset: %s\n", dataSet.ID()) + // We're using the Or variant of GetString because we know this call can't // fail, as the config isn't valid without. - dataSetDirectory := fmt.Sprintf("%s/%s", config.GetStringOr("data_dir", ""), dataSet.GetDataDirectory()) + dataSetDirectory := fmt.Sprintf("%s/%s", cfg.GetStringOr("data_dir", ""), dataSet.GetDataDirectory()) err := os.MkdirAll(dataSetDirectory, 0755) if err != nil { @@ -148,7 +149,7 @@ func buildModels(config *cuedb.Runtime, runtime *cuedb.Runtime) error { record := make(map[string]interface{}) record[slug] = istruct - err = runtime.Insert(dataSet, record) + err = engine.Insert(dataSet, record) if err != nil { return multierror.Append(err) } diff --git a/cmd/remote_get.go b/cmd/remote_get.go index 8550dcc..9838874 100644 --- a/cmd/remote_get.go +++ b/cmd/remote_get.go @@ -7,6 +7,7 @@ import ( "os" "path" + "github.com/cueblox/blox" "github.com/cueblox/blox/internal/cuedb" "github.com/cueblox/blox/internal/repository" "github.com/spf13/cobra" @@ -43,12 +44,9 @@ to quickly create a Cobra application.`, } } } - config, err := cuedb.NewRuntime() - cobra.CheckErr(err) - cobra.CheckErr(config.LoadConfig()) - cobra.CheckErr(err) - schemaDir := config.GetStringOr("schema_dir", "schema") + cfg, err := blox.NewConfig(cuedb.BaseConfig) + schemaDir := cfg.GetStringOr("schema_dir", "schema") err = os.MkdirAll(schemaDir, 0755) cobra.CheckErr(err) diff --git a/cmd/repo_build.go b/cmd/repo_build.go index 61cf24b..f52a843 100644 --- a/cmd/repo_build.go +++ b/cmd/repo_build.go @@ -19,6 +19,7 @@ to quickly create a Cobra application.`, Run: func(cmd *cobra.Command, args []string) { repo, err := repository.GetRepository() cobra.CheckErr(err) + pterm.Info.Println("Building Repository") cobra.CheckErr(repo.Build()) pterm.Success.Println("Build Complete") }, diff --git a/config.go b/config.go new file mode 100644 index 0000000..8708ef5 --- /dev/null +++ b/config.go @@ -0,0 +1,76 @@ +package blox + +import ( + "fmt" + "io/ioutil" + + "cuelang.org/go/cue" + "github.com/pterm/pterm" +) + +type Config struct { + runtime *Runtime +} + +// New setup a new config type with +// base as the defaults +func NewConfig(base string) (*Config, error) { + r, err := NewRuntimeWithBase(base) + if err != nil { + return nil, err + } + config := &Config{ + runtime: r, + } + + return config, nil +} + +// LoadConfig opens the configuration file +// specified in `path` and validates it against +// the configuration provided in when the `Engine` +// was initialized with `New()` +func (r *Config) LoadConfig(path string) error { + pterm.Debug.Printf("\t\tLoading config: %s\n", path) + cueConfig, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + return r.LoadConfigString(string(cueConfig)) +} + +func (r *Config) LoadConfigString(cueConfig string) error { + cueInstance, err := r.runtime.CueRuntime.Compile("", cueConfig) + if err != nil { + return err + } + + cueValue := cueInstance.Value() + + r.runtime.Database = r.runtime.Database.Unify(cueValue) + if err = r.runtime.Database.Validate(); err != nil { + return err + } + + return nil +} +func (r *Config) GetString(key string) (string, error) { + keyValue := r.runtime.Database.LookupPath(cue.ParsePath(key)) + + if keyValue.Exists() { + return keyValue.String() + } + + return "", fmt.Errorf("couldn't find key '%s'", key) +} + +func (r *Config) GetStringOr(key string, def string) string { + cueValue, err := r.GetString(key) + + if err != nil { + return def + } + + return cueValue +} diff --git a/internal/cuedb/config_test.go b/config_test.go similarity index 54% rename from internal/cuedb/config_test.go rename to config_test.go index e1b1021..dcc20ad 100644 --- a/internal/cuedb/config_test.go +++ b/config_test.go @@ -1,4 +1,4 @@ -package cuedb +package blox import ( "testing" @@ -6,21 +6,35 @@ import ( "github.com/stretchr/testify/assert" ) +const base = `{ + data_dir: string + schema_dir: string | *"schemas" + build_dir: string | *"_build" +} +` + func TestGetString(t *testing.T) { - runtime, err := NewRuntime() + runtime, err := NewConfig(base) if err != nil { t.Fatal("Failed to create a NewRuntime, which should never happen") } - err = runtime.loadConfigString(`{ + err = runtime.LoadConfigString(`{ data_dir: "my-data-dir" }`) assert.Equal(t, nil, err) + // test non-existent key _, err = runtime.GetString("data_dir_non_exist") assert.NotEqual(t, nil, err) + // test defaulted key + schDir, err := runtime.GetString("schema_dir") + assert.Equal(t, nil, err) + assert.Equal(t, "schemas", schDir) + + // test parsed key configString, err := runtime.GetString("data_dir") assert.Equal(t, nil, err) assert.Equal(t, "my-data-dir", configString) diff --git a/internal/config/config.cue b/internal/config/config.cue deleted file mode 100644 index 3d70b76..0000000 --- a/internal/config/config.cue +++ /dev/null @@ -1,5 +0,0 @@ -{ - data_dir: string - schema_dir: string | *"schemas" - build_dir: string | *"_build" -} diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 index 604fcce..0000000 --- a/internal/config/config.go +++ /dev/null @@ -1,8 +0,0 @@ -package config - -import ( - _ "embed" -) - -//go:embed config.cue -var BaseConfig string diff --git a/internal/cuedb/config.go b/internal/cuedb/config.go deleted file mode 100644 index 293710e..0000000 --- a/internal/cuedb/config.go +++ /dev/null @@ -1,55 +0,0 @@ -package cuedb - -import ( - "fmt" - "io/ioutil" - - "cuelang.org/go/cue" -) - -const defaultConfigName = "blox.cue" - -func (r *Runtime) LoadConfig() error { - cueConfig, err := ioutil.ReadFile(defaultConfigName) - if err != nil { - return err - } - - return r.loadConfigString(string(cueConfig)) -} - -func (r *Runtime) loadConfigString(cueConfig string) error { - cueInstance, err := r.cueRuntime.Compile("", cueConfig) - if err != nil { - return err - } - - cueValue := cueInstance.Value() - - r.database = r.database.Unify(cueValue) - if err = r.database.Validate(); err != nil { - return err - } - - return nil -} - -func (r *Runtime) GetString(key string) (string, error) { - keyValue := r.database.LookupPath(cue.ParsePath(key)) - - if keyValue.Exists() { - return keyValue.String() - } - - return "", fmt.Errorf("Couldn't find key '%s'", key) -} - -func (r *Runtime) GetStringOr(key string, def string) string { - cueValue, err := r.GetString(key) - - if err != nil { - return def - } - - return cueValue -} diff --git a/internal/cuedb/dataset.go b/internal/cuedb/dataset.go new file mode 100644 index 0000000..4b4755d --- /dev/null +++ b/internal/cuedb/dataset.go @@ -0,0 +1,68 @@ +package cuedb + +import ( + "fmt" + + "cuelang.org/go/cue" +) + +// Can't use schemaField, yet. +const SchemaMetadataCue = `{ + _schema: { + namespace: string + name: string + } +}` + +type SchemaMetadata struct { + Namespace string + Name string +} + +// Can't use dataSetField, yet. +const DataSetMetadataCue = `{ + _dataset: { + plural: string + supportedExtensions: [...string] + } +}` + +type DataSetMetadata struct { + Plural string + SupportedExtensions []string +} + +type DataSet struct { + name string + schemaMetadata SchemaMetadata + cuePath cue.Path + metadata DataSetMetadata +} + +// AddDataMap +func (d *DataSet) GetDataMapCue() string { + return fmt.Sprintf(`{ + %s: %s: _ + %s: %s: [ID=string]: %s.%s & {id: (ID)} + }`, + d.GetInlinePath(), d.name, + dataPathRoot, d.metadata.Plural, d.cuePath.String(), d.name, + ) +} +func (d *DataSet) CueDataPath() cue.Path { + return cue.ParsePath(fmt.Sprintf("%s.%s", dataPathRoot, d.metadata.Plural)) +} + +func (d *DataSet) IsSupportedExtension(ext string) bool { + for _, val := range d.metadata.SupportedExtensions { + if val == ext { + return true + } + } + + return false +} + +func (d *DataSet) GetSupportedExtensions() []string { + return d.metadata.SupportedExtensions +} diff --git a/internal/cuedb/runtime.go b/internal/cuedb/engine.go similarity index 59% rename from internal/cuedb/runtime.go rename to internal/cuedb/engine.go index b3f5359..3f5b914 100644 --- a/internal/cuedb/runtime.go +++ b/internal/cuedb/engine.go @@ -5,6 +5,7 @@ import ( "strings" "cuelang.org/go/cue" + "github.com/cueblox/blox" "github.com/hashicorp/go-multierror" "github.com/pterm/pterm" ) @@ -16,43 +17,17 @@ const ( schemaField = "_schema" ) -type Runtime struct { - cueRuntime *cue.Runtime - database cue.Value - dataSets map[string]DataSet -} - -// Can't use schemaField, yet. -const SchemaMetadataCue = `{ - _schema: { - namespace: string - name: string - } +const BaseConfig = `{ + data_dir: string + schema_dir: string | *"schemas" + build_dir: string | *"_build" }` -type SchemaMetadata struct { - Namespace string - Name string -} - -// Can't use dataSetField, yet. -const DataSetMetadataCue = `{ - _dataset: { - plural: string - supportedExtensions: [...string] - } -}` - -type DataSetMetadata struct { - Plural string - SupportedExtensions []string -} - -type DataSet struct { - name string - schemaMetadata SchemaMetadata - cuePath cue.Path - metadata DataSetMetadata +type Engine struct { + // configuration + // embedded runtime database + *blox.Runtime + dataSets map[string]DataSet } // RecordBaseCue is the "Base" configuration that blox @@ -61,32 +36,28 @@ type DataSet struct { const RecordBaseCue = `{ id: string }` +const DefaultConfigName = "blox.cue" -// NewRuntime setups a new database for DataSets to be registered, -// and data inserted. -func NewRuntime() (Runtime, error) { - var cueRuntime cue.Runtime - cueInstance, err := cueRuntime.Compile("", "") - - if nil != err { - return Runtime{}, err +func NewEngine() (*Engine, error) { + r, err := blox.NewRuntime() + if err != nil { + return nil, err } - runtime := Runtime{ - cueRuntime: &cueRuntime, - database: cueInstance.Value(), - dataSets: make(map[string]DataSet), + runtime := &Engine{ + Runtime: r, + dataSets: make(map[string]DataSet), } return runtime, nil } -func (r *Runtime) CountDataSets() int { +func (r *Engine) CountDataSets() int { return len(r.dataSets) } -func (r *Runtime) extractSchemaMetadata(schema cue.Value) (SchemaMetadata, error) { - cueInstance, err := r.cueRuntime.Compile("", SchemaMetadataCue) +func (r *Engine) extractSchemaMetadata(schema cue.Value) (SchemaMetadata, error) { + cueInstance, err := r.CueRuntime.Compile("", SchemaMetadataCue) if err != nil { pterm.Debug.Println("Failed to compile Cue") return SchemaMetadata{}, err @@ -109,8 +80,8 @@ func (r *Runtime) extractSchemaMetadata(schema cue.Value) (SchemaMetadata, error return schemaMetadata, nil } -func (r *Runtime) extractDataSetMetadata(schema cue.Value) (DataSetMetadata, error) { - cueInstance, err := r.cueRuntime.Compile("", DataSetMetadataCue) +func (r *Engine) extractDataSetMetadata(schema cue.Value) (DataSetMetadata, error) { + cueInstance, err := r.CueRuntime.Compile("", DataSetMetadataCue) if err != nil { pterm.Debug.Println("Failed to compile Cue") return DataSetMetadata{}, err @@ -133,11 +104,11 @@ func (r *Runtime) extractDataSetMetadata(schema cue.Value) (DataSetMetadata, err return dataSetMetadata, nil } -func (r *Runtime) GetDataSets() map[string]DataSet { +func (r *Engine) GetDataSets() map[string]DataSet { return r.dataSets } -func (r *Runtime) GetDataSet(name string) (DataSet, error) { +func (r *Engine) GetDataSet(name string) (DataSet, error) { cueName := strings.ToLower(name) if !strings.HasPrefix(cueName, "#") { cueName = fmt.Sprintf("#%s", strings.ToLower(name)) @@ -147,7 +118,7 @@ func (r *Runtime) GetDataSet(name string) (DataSet, error) { return dataSet, nil } - return DataSet{}, fmt.Errorf("Couldn't find DataSet with name %s", name) + return DataSet{}, fmt.Errorf("couldn't find DataSet with name %s", name) } func (d *DataSet) ID() string { @@ -174,19 +145,8 @@ func (d *DataSet) GetInlinePath() string { return strings.TrimPrefix(inlinePath, ": ") } -// AddDataMap -func (d *DataSet) GetDataMapCue() string { - return fmt.Sprintf(`{ - %s: %s: _ - %s: %s: [ID=string]: %s.%s & {id: (ID)} - }`, - d.GetInlinePath(), d.name, - dataPathRoot, d.metadata.Plural, d.cuePath.String(), d.name, - ) -} - -func (r *Runtime) RegisterSchema(cueString string) error { - cueInstance, err := r.cueRuntime.Compile("", cueString) +func (r *Engine) RegisterSchema(cueString string) error { + cueInstance, err := r.CueRuntime.Compile("", cueString) if nil != err { return err } @@ -197,13 +157,12 @@ func (r *Runtime) RegisterSchema(cueString string) error { if err != nil { return err } - schemaPath := cue.ParsePath(fmt.Sprintf(`%s."%s"."%s"`, schemaPathRoot, schemaMetadata.Namespace, schemaMetadata.Name)) // First, Unify whatever schemas the users want. We'll // do our best to extract whatever information from // it we require - r.database = r.database.FillPath(schemaPath, cueValue) + r.Database = r.Database.FillPath(schemaPath, cueValue) // Find DataSets and register fields, err := cueValue.Fields(cue.Definitions(true)) @@ -212,13 +171,14 @@ func (r *Runtime) RegisterSchema(cueString string) error { } // Base Record Constraints - baseRecordInstance, err := r.cueRuntime.Compile("", RecordBaseCue) + baseRecordInstance, err := r.CueRuntime.Compile("", RecordBaseCue) if err != nil { return err } baseRecordValue := baseRecordInstance.Value() for fields.Next() { + pterm.Debug.Println("\t\t\tNext field") if !fields.IsDefinition() { // Only Definitions can be registered as DataSets continue @@ -226,9 +186,12 @@ func (r *Runtime) RegisterSchema(cueString string) error { dataSetMetadata, err := r.extractDataSetMetadata(fields.Value()) if err != nil { + + pterm.Debug.Printf("\t\t\t%v\n", err) // No dataset metadata, skip continue } + pterm.Debug.Printf("\t\t\t%s\n", fields.Value()) dataSet := DataSet{ schemaMetadata: schemaMetadata, @@ -242,21 +205,21 @@ func (r *Runtime) RegisterSchema(cueString string) error { } // Compile our BaseModel - r.database = r.database.FillPath(dataSet.GetDefinitionPath(), baseRecordValue) - if err = r.database.Validate(); err != nil { + r.Database = r.Database.FillPath(dataSet.GetDefinitionPath(), baseRecordValue) + if err = r.Database.Validate(); err != nil { return err } r.dataSets[strings.ToLower(dataSet.name)] = dataSet - inst, err := r.cueRuntime.Compile("", dataSet.GetDataMapCue()) + inst, err := r.CueRuntime.Compile("", dataSet.GetDataMapCue()) if err != nil { return err } - r.database = r.database.FillPath(cue.Path{}, inst.Value()) + r.Database = r.Database.FillPath(cue.Path{}, inst.Value()) - if err := r.database.Validate(); nil != err { + if err := r.Database.Validate(); nil != err { return err } } @@ -264,14 +227,10 @@ func (r *Runtime) RegisterSchema(cueString string) error { return nil } -func (d *DataSet) CueDataPath() cue.Path { - return cue.ParsePath(fmt.Sprintf("%s.%s", dataPathRoot, d.metadata.Plural)) -} - -func (r *Runtime) Insert(dataSet DataSet, record map[string]interface{}) error { - r.database = r.database.FillPath(dataSet.CueDataPath(), record) +func (r *Engine) Insert(dataSet DataSet, record map[string]interface{}) error { + r.Database = r.Database.FillPath(dataSet.CueDataPath(), record) - err := r.database.Validate() + err := r.Database.Validate() if nil != err { return err } @@ -280,16 +239,16 @@ func (r *Runtime) Insert(dataSet DataSet, record map[string]interface{}) error { } // MarshalJSON returns the database encoded in JSON format -func (r *Runtime) MarshalJSON() ([]byte, error) { - return r.database.LookupPath(cue.ParsePath(dataPathRoot)).MarshalJSON() +func (r *Engine) MarshalJSON() ([]byte, error) { + return r.Database.LookupPath(cue.ParsePath(dataPathRoot)).MarshalJSON() } // ReferentialIntegrity checks the relationships between // the records in the content database -func (r *Runtime) ReferentialIntegrity() error { +func (r *Engine) ReferentialIntegrity() error { for _, dataSet := range r.GetDataSets() { // Walk each field and look for _id labels - val := r.database.LookupPath(dataSet.GetDefinitionPath()) + val := r.Database.LookupPath(dataSet.GetDefinitionPath()) fields, err := val.Fields(cue.All()) if err != nil { @@ -308,17 +267,17 @@ func (r *Runtime) ReferentialIntegrity() error { optional = "?" } - inst, err := r.cueRuntime.Compile("", fmt.Sprintf("{data: _\n%s: %s: %s%s: or([ for k, _ in data.%s {k}])}", dataSet.GetInlinePath(), dataSet.name, fields.Label(), optional, foreignTable.GetDataDirectory())) + inst, err := r.CueRuntime.Compile("", fmt.Sprintf("{data: _\n%s: %s: %s%s: or([ for k, _ in data.%s {k}])}", dataSet.GetInlinePath(), dataSet.name, fields.Label(), optional, foreignTable.GetDataDirectory())) if err != nil { return err } - r.database = r.database.FillPath(cue.Path{}, inst.Value()) + r.Database = r.Database.FillPath(cue.Path{}, inst.Value()) } } } - err := r.database.Validate() + err := r.Database.Validate() if err != nil { return multierror.Prefix(err, "Referential Integrity Failed") } @@ -326,33 +285,19 @@ func (r *Runtime) ReferentialIntegrity() error { return nil } -func (r *Runtime) GetOutput() (cue.Value, error) { +func (r *Engine) GetOutput() (cue.Value, error) { if len(r.GetDataSets()) == 0 { return cue.Value{}, fmt.Errorf("No DataSets to generate output") } for _, dataSet := range r.GetDataSets() { - inst, err := r.cueRuntime.Compile("", fmt.Sprintf("{%s: %s: _\noutput: %s: [ for key, val in %s.%s {val}]}", dataPathRoot, dataSet.metadata.Plural, dataSet.metadata.Plural, dataPathRoot, dataSet.metadata.Plural)) + inst, err := r.CueRuntime.Compile("", fmt.Sprintf("{%s: %s: _\noutput: %s: [ for key, val in %s.%s {val}]}", dataPathRoot, dataSet.metadata.Plural, dataSet.metadata.Plural, dataPathRoot, dataSet.metadata.Plural)) if err != nil { return cue.Value{}, err } - r.database = r.database.FillPath(cue.Path{}, inst.Value()) + r.Database = r.Database.FillPath(cue.Path{}, inst.Value()) } - return r.database.LookupPath(cue.ParsePath("output")), nil -} - -func (d *DataSet) IsSupportedExtension(ext string) bool { - for _, val := range d.metadata.SupportedExtensions { - if val == ext { - return true - } - } - - return false -} - -func (d *DataSet) GetSupportedExtensions() []string { - return d.metadata.SupportedExtensions + return r.Database.LookupPath(cue.ParsePath("output")), nil } diff --git a/internal/cuedb/runtime_test.go b/internal/cuedb/engine_test.go similarity index 88% rename from internal/cuedb/runtime_test.go rename to internal/cuedb/engine_test.go index 8013551..7c7e0b2 100644 --- a/internal/cuedb/runtime_test.go +++ b/internal/cuedb/engine_test.go @@ -2,17 +2,32 @@ package cuedb import ( "fmt" + "path" "strings" "testing" "cuelang.org/go/cue" + "github.com/cueblox/blox" "github.com/stretchr/testify/assert" ) +func testConfig(t *testing.T) *blox.Config { + c, err := blox.NewConfig(BaseConfig) + if err != nil { + t.Error(err) + } + err = c.LoadConfig(path.Join("..", "..", "blox.cue")) + if err != nil { + t.Error() + } + return c +} func TestNewRuntime(t *testing.T) { - runtime, err := NewRuntime() + //cfg := testConfig(t) + runtime, err := NewEngine() if err != nil { + fmt.Println(err) t.Fatal("Failed to create a NewRuntime, which should never happen") } @@ -27,13 +42,13 @@ func TestExtractSchemaMetadata(t *testing.T) { name: "TestSchema" } }` - runtime, err := NewRuntime() + runtime, err := NewEngine() if err != nil { t.Fatal("Failed to create a NewRuntime, which should never happen") } - cueInstance, err := runtime.cueRuntime.Compile("", schemaCue) + cueInstance, err := runtime.CueRuntime.Compile("", schemaCue) assert.Equal(t, nil, err) schemaMetdata, err := runtime.extractSchemaMetadata(cueInstance.Value()) @@ -52,13 +67,13 @@ func TestExtractDataSetMetadata(t *testing.T) { supportedExtensions: ["txt", "tar.gz"] } }` - runtime, err := NewRuntime() + runtime, err := NewEngine() if err != nil { t.Fatal("Failed to create a NewRuntime, which should never happen") } - cueInstance, err := runtime.cueRuntime.Compile("", dataSetCue) + cueInstance, err := runtime.CueRuntime.Compile("", dataSetCue) assert.Equal(t, nil, err) dataSetMetdata, err := runtime.extractDataSetMetadata(cueInstance.Value()) @@ -94,7 +109,7 @@ func TestRegisterSchema(t *testing.T) { sport: string } }` - runtime, err := NewRuntime() + runtime, err := NewEngine() if err != nil { t.Fatal("Failed to create a NewRuntime, which should never happen") @@ -132,7 +147,7 @@ func TestGetDataSets(t *testing.T) { sport: string } }` - runtime, err := NewRuntime() + runtime, err := NewEngine() if err != nil { t.Fatal("Failed to create a NewRuntime, which should never happen") @@ -188,7 +203,7 @@ func TestInsert(t *testing.T) { sport: string } }` - runtime, err := NewRuntime() + runtime, err := NewEngine() if err != nil { t.Fatal("Failed to create a NewRuntime, which should never happen") @@ -206,7 +221,7 @@ func TestInsert(t *testing.T) { recordOne := map[string]interface{}{"david": map[string]string{"name": "David"}} assert.Equal(t, nil, runtime.Insert(dataSet, recordOne)) - nameValueOne := runtime.database.LookupPath(cue.ParsePath("data.OnePlural.david.name")) + nameValueOne := runtime.Database.LookupPath(cue.ParsePath("data.OnePlural.david.name")) nameValueOneStr, err := nameValueOne.String() assert.Equal(t, nil, err) assert.Equal(t, "David", nameValueOneStr) @@ -214,7 +229,7 @@ func TestInsert(t *testing.T) { recordTwo := map[string]interface{}{"brian": map[string]string{"name": "Brian"}} assert.Equal(t, nil, runtime.Insert(dataSet, recordTwo)) - nameValueTwo := runtime.database.LookupPath(cue.ParsePath("data.OnePlural.brian.name")) + nameValueTwo := runtime.Database.LookupPath(cue.ParsePath("data.OnePlural.brian.name")) nameValueTwoStr, err := nameValueTwo.String() assert.Equal(t, nil, err) assert.Equal(t, "Brian", nameValueTwoStr) diff --git a/internal/repository/repository.go b/internal/repository/repository.go index 61a9e8c..11c7761 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -6,13 +6,12 @@ import ( "encoding/json" "fmt" "io/fs" - "io/ioutil" "os" "path" "path/filepath" "strings" - "cuelang.org/go/cue" + "github.com/cueblox/blox" "github.com/otiai10/copy" "github.com/pterm/pterm" ) @@ -20,79 +19,6 @@ import ( //go:embed config.cue var BaseConfig string -// Database stores information about the -// repository -type Database struct { - runtime *cue.Runtime - db cue.Value - config *cue.Value -} - -// NewDatabase creates a "world" struct to store -// records -func NewDatabase() (Database, error) { - var cueRuntime cue.Runtime - cueInstance, err := cueRuntime.Compile("", "") - - if nil != err { - return Database{}, err - } - - database := Database{ - runtime: &cueRuntime, - db: cueInstance.Value(), - } - - err = database.LoadConfig() - if nil != err { - return Database{}, err - } - - return database, nil -} - -func (d *Database) LoadConfig() error { - configInstance, err := d.runtime.Compile("", BaseConfig) - if err != nil { - return err - } - - configValue := configInstance.Value() - - localConfig, err := ioutil.ReadFile("repository.cue") - if err != nil { - return err - } - - localConfigInstance, err := d.runtime.Compile("", localConfig) - if err != nil { - return err - } - - mergedConfig := configValue.Unify(localConfigInstance.Value()) - if err = mergedConfig.Validate(cue.Concrete(true)); err != nil { - return err - } - - d.config = &mergedConfig - - return nil -} - -func (d *Database) GetConfigString(key string) (string, error) { - value, err := d.config.LookupField(key) - if err != nil { - return "", err - } - - str, err := value.Value.String() - if err != nil { - return "", err - } - - return str, nil -} - // Repository is a group of schemas type Repository struct { Root string @@ -105,19 +31,27 @@ type Repository struct { // described by the repository.cue file in the // current directory func GetRepository() (*Repository, error) { - sdb, err := NewDatabase() + // initialize config engine with defaults + cfg, err := blox.NewConfig(BaseConfig) if err != nil { return nil, err } - build_dir, err := sdb.GetConfigString("output_dir") + // load user config + cfg.LoadConfig("repository.cue") + + build_dir, err := cfg.GetString("output_dir") + pterm.Debug.Printf("\t\tBuild Directory: %s\n", build_dir) if err != nil { return nil, err } - namespace, err := sdb.GetConfigString("namespace") + namespace, err := cfg.GetString("namespace") + + pterm.Debug.Printf("\t\tNamespace: %s\n", namespace) if err != nil { return nil, err } - reporoot, err := sdb.GetConfigString("repository_root") + reporoot, err := cfg.GetString("repository_root") + pterm.Debug.Printf("\t\tRepository Root: %s\n", reporoot) if err != nil { return nil, err } @@ -142,7 +76,7 @@ func NewRepository(namespace, output, root string) (*Repository, error) { Output: output, } // create the repository directory - pterm.Debug.Printf("\t\tCreating repository root directory at %s", root) + pterm.Debug.Printf("\t\tCreating repository root directory at %s\n", root) err := r.createRoot() if err != nil { return nil, err @@ -206,7 +140,6 @@ func (r *Repository) load() error { // not a dir, must be file // we only care about files in // version directories - fmt.Println("file", len(paths), path) if len(paths) == 4 { if d.Name() == "schema.cue" { bb, err := os.ReadFile(path) @@ -317,7 +250,7 @@ func (r *Repository) AddVersion(schema string) error { // Build serializes the Repository object // into a json file in the `Output` directory. func (r *Repository) Build() error { - pterm.Debug.Printf("\t\tBuilding repository to %s", r.Output) + pterm.Debug.Printf("\t\tBuilding repository to %s\n", r.Output) buildDir := path.Join(r.Root, r.Output) buildFile := path.Join(buildDir, "manifest.json") @@ -335,7 +268,7 @@ func (r *Repository) Build() error { if err != nil { return err } - pterm.Debug.Printf("\t\tManifest written to %s", buildFile) + pterm.Debug.Printf("\t\tManifest written to %s\n", buildFile) return nil } diff --git a/runtime.go b/runtime.go new file mode 100644 index 0000000..0d0b620 --- /dev/null +++ b/runtime.go @@ -0,0 +1,45 @@ +package blox + +import ( + "cuelang.org/go/cue" +) + +type Runtime struct { + CueRuntime *cue.Runtime + Database cue.Value +} + +// NewRuntime creates a new runtime engine +func NewRuntime() (*Runtime, error) { + var cueRuntime cue.Runtime + cueInstance, err := cueRuntime.Compile("", "") + + if nil != err { + return &Runtime{}, err + } + + runtime := &Runtime{ + CueRuntime: &cueRuntime, + Database: cueInstance.Value(), + } + + return runtime, nil +} + +// NewRuntimeWithBase creates a new runtime engine +// with the cue provided in `base` as the initial cue values +func NewRuntimeWithBase(base string) (*Runtime, error) { + var cueRuntime cue.Runtime + cueInstance, err := cueRuntime.Compile("", base) + + if nil != err { + return &Runtime{}, err + } + + runtime := &Runtime{ + CueRuntime: &cueRuntime, + Database: cueInstance.Value(), + } + + return runtime, nil +}