Skip to content

Commit

Permalink
feat(lib): qri.get_body function for updates
Browse files Browse the repository at this point in the history
  • Loading branch information
dustmop committed Jun 4, 2018
1 parent 92be46b commit 9be0e4a
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 13 deletions.
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
command: >
go get -v -d -u
github.com/jstemmer/go-junit-report
github.com/qri-io/cafs
github.com/qri-io/dataset
github.com/google/skylark
github.com/google/skylark/repl
Expand Down
5 changes: 3 additions & 2 deletions lib/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,9 @@ func Marshal(data interface{}) (v skylark.Value, err error) {
v = skylark.Bool(x)
case string:
v = skylark.String(x)
case int:
v = skylark.MakeInt(x)
case float64:
// TODO - ints?
v = skylark.Float(x)
case []interface{}:
var elems = make([]skylark.Value, len(x))
Expand All @@ -149,4 +150,4 @@ func Marshal(data interface{}) (v skylark.Value, err error) {
v = dict
}
return
}
}
96 changes: 96 additions & 0 deletions lib/qri/entry_writer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package qri

import (
"fmt"

"github.com/google/skylark"
"github.com/qri-io/skytf/lib"
"github.com/qri-io/dataset"
"github.com/qri-io/dataset/dsio"
"github.com/qri-io/jsonschema"
)

// SkylarkEntryWriter creates a skylark.Value as an EntryWriter
type SkylarkEntryWriter struct {
IsDict bool
Struct *dataset.Structure
Object skylark.Value
}

// WriteEntry adds an entry to the underlying skylark.Value
func (w *SkylarkEntryWriter) WriteEntry(ent dsio.Entry) error {
if w.IsDict {
dict := w.Object.(*skylark.Dict)
key, err := lib.Marshal(ent.Key)
if err != nil {
return err
}
val, err := lib.Marshal(ent.Value)
if err != nil {
return err
}
dict.Set(key, val)
} else {
list := w.Object.(*skylark.List)
val, err := lib.Marshal(ent.Value)
if err != nil {
return err
}
list.Append(val)
}
return nil
}

// Structure returns the EntryWriter's dataset structure
func (w *SkylarkEntryWriter) Structure() *dataset.Structure {
return w.Struct
}

// Close is a no-op
func (w *SkylarkEntryWriter) Close() error {
return nil
}

// Value returns the underlying skylark.Value
func (w *SkylarkEntryWriter) Value() skylark.Value {
return w.Object
}

// NewSkylarkEntryWriter returns a new SkylarkEntryWriter
func NewSkylarkEntryWriter(st *dataset.Structure) (*SkylarkEntryWriter, error) {
mode, err := schemaScanMode(st.Schema)
if err != nil {
return nil, err
}
if mode == smObject {
return &SkylarkEntryWriter{IsDict: true, Struct: st, Object: &skylark.Dict{}}, nil
}
return &SkylarkEntryWriter{IsDict: false, Struct: st, Object: &skylark.List{}}, nil
}

// TODO: Refactor everything below this so that jsonschema returns this in a simple way
type scanMode int

const (
smArray scanMode = iota
smObject
)

// schemaScanMode determines weather the top level is an array or object
func schemaScanMode(sc *jsonschema.RootSchema) (scanMode, error) {
if vt, ok := sc.Validators["type"]; ok {
// TODO - lol go PR jsonschema to export access to this instead of this
// silly validation hack
obj := []jsonschema.ValError{}
arr := []jsonschema.ValError{}
vt.Validate("", map[string]interface{}{}, &obj)
vt.Validate("", []interface{}{}, &arr)
if len(obj) == 0 {
return smObject, nil
} else if len(arr) == 0 {
return smArray, nil
}
}
err := fmt.Errorf("invalid schema. root must be either an array or object type")
return smArray, err
}
37 changes: 31 additions & 6 deletions lib/qri/qri.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (

"github.com/google/skylark"
"github.com/google/skylark/skylarkstruct"
"github.com/qri-io/cafs"
"github.com/qri-io/dataset"
"github.com/qri-io/dataset/dsio"
"github.com/qri-io/skytf/lib"
)

Expand All @@ -14,24 +16,26 @@ import (
const ModuleName = "qri.sky"

// NewModule creates a new qri module instance
func NewModule(ds *dataset.Dataset, secrets map[string]interface{}) *Module {
return &Module{ds: ds, secrets: secrets}
func NewModule(ds *dataset.Dataset, secrets map[string]interface{}, infile cafs.File) *Module {
return &Module{ds: ds, secrets: secrets, infile: infile}
}

// Module encapsulates state for a qri skylark module
type Module struct {
ds *dataset.Dataset
secrets map[string]interface{}
data skylark.Iterable
infile cafs.File
}

// Load creates a skylark module from a module instance
func (m *Module) Load() (skylark.StringDict, error) {
st := skylarkstruct.FromStringDict(skylarkstruct.Default, skylark.StringDict{
"commit": skylark.NewBuiltin("commit", m.Commit),
"set_meta": skylark.NewBuiltin("set_meta", m.SetMeta),
"get_config": skylark.NewBuiltin("get_config", m.GetConfig),
"get_secret": skylark.NewBuiltin("get_secret", m.GetSecret),
"commit": skylark.NewBuiltin("commit", m.Commit),
"set_meta": skylark.NewBuiltin("set_meta", m.SetMeta),
"get_body": skylark.NewBuiltin("get_body", m.GetBody),
"get_config": skylark.NewBuiltin("get_config", m.GetConfig),
"get_secret": skylark.NewBuiltin("get_secret", m.GetSecret),
})

return skylark.StringDict{"qri": st}, nil
Expand Down Expand Up @@ -71,6 +75,27 @@ func (m *Module) GetConfig(thread *skylark.Thread, _ *skylark.Builtin, args skyl
return lib.Marshal(m.ds.Transform.Config)
}

// GetBody returns the body of the dataset we're transforming
func (m *Module) GetBody(thread *skylark.Thread, _ *skylark.Builtin, args skylark.Tuple, kwargs []skylark.Tuple) (skylark.Value, error) {
if m.infile == nil {
return skylark.None, fmt.Errorf("qri.get_body failed: no DataFile")
}
rr, err := dsio.NewEntryReader(m.ds.Structure, m.infile)
if err != nil {
return skylark.None, fmt.Errorf("error allocating data reader: %s", err)
}
w, err := NewSkylarkEntryWriter(m.ds.Structure)
if err != nil {
return skylark.None, fmt.Errorf("error allocating skylark entry writer: %s", err)
}

err = dsio.Copy(rr, w)
if err != nil {
return skylark.None, err
}
return w.Value(), nil
}

// SetMeta sets a dataset meta field
func (m *Module) SetMeta(thread *skylark.Thread, _ *skylark.Builtin, args skylark.Tuple, kwargs []skylark.Tuple) (skylark.Value, error) {
var keyx, valx skylark.Value
Expand Down
2 changes: 1 addition & 1 deletion lib/qri/qri_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestNewModule(t *testing.T) {
func newLoader(ds *dataset.Dataset) func(thread *skylark.Thread, module string) (skylark.StringDict, error) {
return func(thread *skylark.Thread, module string) (skylark.StringDict, error) {
if module == ModuleName {
return NewModule(ds, nil).Load()
return NewModule(ds, nil, nil).Load()
}

return nil, fmt.Errorf("invalid module")
Expand Down
5 changes: 3 additions & 2 deletions transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/google/skylark"
"github.com/google/skylark/resolve"
"github.com/qri-io/cafs"
"github.com/qri-io/dataset"
"github.com/qri-io/dataset/dsio"
"github.com/qri-io/skytf/lib"
Expand All @@ -34,7 +35,7 @@ func DefaultExecOpts(o *ExecOpts) {
// ExecFile executes a transformation against a skylark file located at filepath, giving back an EntryReader of resulting data
// ExecFile modifies the given dataset pointer. At bare minimum it will set transformation details, but skylark scripts can modify
// many parts of the dataset pointer, including meta, structure, and transform
func ExecFile(ds *dataset.Dataset, filename string, opts ...func(o *ExecOpts)) (dsio.EntryReader, error) {
func ExecFile(ds *dataset.Dataset, filename string, infile cafs.File, opts ...func(o *ExecOpts)) (dsio.EntryReader, error) {
var (
scriptdata []byte
err error
Expand Down Expand Up @@ -70,7 +71,7 @@ func ExecFile(ds *dataset.Dataset, filename string, opts ...func(o *ExecOpts)) (
ds.Transform.Script = bytes.NewReader(scriptdata)

// allocate skyqri module here so we can get data back post-execution
m := skyqri.NewModule(ds, o.Secrets)
m := skyqri.NewModule(ds, o.Secrets, infile)

thread := &skylark.Thread{Load: newLoader(ds, m)}

Expand Down
4 changes: 2 additions & 2 deletions transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

func TestExecFile(t *testing.T) {
ds := &dataset.Dataset{}
er, err := ExecFile(ds, "testdata/tf.sky")
er, err := ExecFile(ds, "testdata/tf.sky", nil)
if err != nil {
t.Error(err.Error())
return
Expand All @@ -34,7 +34,7 @@ func TestExecFile(t *testing.T) {

func TestExecFile2(t *testing.T) {
ds := &dataset.Dataset{}
_, err := ExecFile(ds, "testdata/fetch.sky")
_, err := ExecFile(ds, "testdata/fetch.sky", nil)
if err != nil {
t.Error(err.Error())
return
Expand Down

0 comments on commit 9be0e4a

Please sign in to comment.