Skip to content

Commit

Permalink
fix(export): Overhaul export command
Browse files Browse the repository at this point in the history
Many changes to the export command to get it working more closely in line with
the recent rfc https://github.com/qri-io/rfcs/blob/master/text/0021-export_behavior.md.

Changes include:

* Export as a single file yaml
* Respect `use` when exporting
* Derive the filename when -o flag isn't used, using the repo name and timestamp
* Derive the format if -o is used, by looking at the file extension
* Calculate absolute path for where export should write, without uglyfying console output
* Introduce new base.ReadEntries utility for turning EntryReader into body
* Switch default export format to single file json

Some things that are broken now, to be fixed later:

* --zipped is broken for now
* Raw block "native" export format
* Needs tests
  • Loading branch information
dustmop committed Feb 11, 2019
1 parent f3cae5b commit f7df3f9
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 230 deletions.
63 changes: 63 additions & 0 deletions base/entries.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package base

import (
"fmt"

"github.com/qri-io/dataset/dsio"
)

// ReadEntries reads entries and returns them as a native go array or map
func ReadEntries(reader dsio.EntryReader, all bool, limit int, offset int) (interface{}, error) {
obj := make(map[string]interface{})
array := make([]interface{}, 0)
numRead := 0

tlt, err := dsio.GetTopLevelType(reader.Structure())
if err != nil {
return nil, err
}

for i := 0;; i++ {
val, err := reader.ReadEntry()
if err != nil {
if err.Error() == "EOF" {
break
}
return nil, err
}
if !all && i < offset {
continue
}

if tlt == "object" {
obj[val.Key] = val.Value
} else {
array = append(array, val.Value)
}

numRead++
if !all && numRead == limit {
break
}
}

if tlt == "object" {
return obj, nil
}
return array, nil
}

// ReadEntriesToArray reads entries and returns them as a native go array
func ReadEntriesToArray(reader dsio.EntryReader, all bool, limit int, offset int) ([]interface{}, error) {
entries, err := ReadEntries(reader, all, limit, offset)
if err != nil {
return nil, err
}

array, ok := entries.([]interface{})
if !ok {
return nil, fmt.Errorf("cannot convert top-level to array")
}

return array, nil
}
44 changes: 5 additions & 39 deletions base/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
)

// Render executes a template for a dataset, returning a slice of HTML
func Render(r repo.Repo, ref repo.DatasetRef, tmplData []byte, limit, offest int, all bool) ([]byte, error) {
func Render(r repo.Repo, ref repo.DatasetRef, tmplData []byte, limit, offset int, all bool) ([]byte, error) {
const tmplName = "template"
var rdr io.Reader

Expand Down Expand Up @@ -67,44 +67,14 @@ func Render(r repo.Repo, ref repo.DatasetRef, tmplData []byte, limit, offest int
return nil, err
}

var (
array []interface{}
obj = map[string]interface{}{}
read = 0
)

tlt, err := dsio.GetTopLevelType(ds.Structure)
if err != nil {
return nil, fmt.Errorf("getting schema top level type: %s", err)
}

rr, err := dsio.NewEntryReader(ds.Structure, file)
if err != nil {
return nil, fmt.Errorf("error allocating data reader: %s", err)
}

for i := 0; i >= 0; i++ {
val, err := rr.ReadEntry()
if err != nil {
if err.Error() == "EOF" {
break
}
return nil, fmt.Errorf("row iteration error: %s", err.Error())
}
if !all && i < offest {
continue
}

if tlt == "object" {
obj[val.Key] = val.Value
} else {
array = append(array, val.Value)
}

read++
if read == limit {
break
}
bodyEntries, err := ReadEntries(rr, all, limit, offset)
if err != nil {
return nil, err
}

// TODO (b5): repo.DatasetRef should be refactored into this newly expanded DatasetPod,
Expand All @@ -117,11 +87,7 @@ func Render(r repo.Repo, ref repo.DatasetRef, tmplData []byte, limit, offest int
ds.Meta = &dataset.Meta{}
}

if tlt == "object" {
ds.Body = obj
} else {
ds.Body = array
}
ds.Body = bodyEntries

tmplBuf := &bytes.Buffer{}
if err := tmpl.Execute(tmplBuf, ds); err != nil {
Expand Down
3 changes: 2 additions & 1 deletion cmd/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ func TestCommandsIntegration(t *testing.T) {
fmt.Sprintf("qri save --body=%s -t=commit_1 me/movies", movies2FilePath),
"qri log me/movies",
"qri diff me/movies me/movies2 -d=detail",
fmt.Sprintf("qri export -o=%s me/movies --zip", path),
// TODO (dlong): Fix me
//fmt.Sprintf("qri export -o=%s me/movies --zip", path),
//fmt.Sprintf("qri export -o=%s --format=cbor --body-format=json me/movies", path),
"qri publish me/movies",
"qri ls -p",
Expand Down
71 changes: 16 additions & 55 deletions cmd/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

"github.com/qri-io/ioes"
"github.com/qri-io/qri/lib"
"github.com/qri-io/qri/repo"
"github.com/spf13/cobra"
)

Expand All @@ -28,9 +27,6 @@ new dataset, use --blank.`,
Example: ` # export dataset
qri export me/annual_pop
# export without the body of the dataset
qri export --no-body me/annual_pop
# export to a specific directory
qri export -o ~/new_directory me/annual_pop`,
Annotations: map[string]string{
Expand All @@ -45,11 +41,8 @@ new dataset, use --blank.`,
}

cmd.Flags().BoolVarP(&o.Blank, "blank", "", false, "export a blank dataset YAML file, overrides all other flags except output")
cmd.Flags().StringVarP(&o.Output, "output", "o", ".", "path to write to, default is current directory")
cmd.Flags().StringVarP(&o.Format, "format", "f", "yaml", "format for all exported files, except for body. yaml is the default format. options: yaml, json")
cmd.Flags().StringVarP(&o.BodyFormat, "body-format", "", "", "format for dataset body. default is the original data format. options: json, csv, cbor")
cmd.Flags().BoolVarP(&o.NoBody, "no-body", "b", false, "don't include dataset body in export")
cmd.Flags().BoolVarP(&o.PeerDir, "peer-dir", "d", false, "export to a peer name namespaced directory")
cmd.Flags().StringVarP(&o.Output, "output", "o", "", "path to write to, default is current directory")
cmd.Flags().StringVarP(&o.Format, "format", "f", "", "format for the exported dataset, such as native, json, xlsx. default: json")
cmd.Flags().BoolVarP(&o.Zipped, "zip", "z", false, "export as a zip file")

return cmd
Expand All @@ -59,14 +52,11 @@ new dataset, use --blank.`,
type ExportOptions struct {
ioes.IOStreams

Ref string
PeerDir bool
Zipped bool
Blank bool
Output string
Format string
BodyFormat string
NoBody bool
Ref string
Blank bool
Output string
Format string
Zipped bool

UsingRPC bool
ExportRequests *lib.ExportRequests
Expand All @@ -88,7 +78,6 @@ func (o *ExportOptions) Complete(f Factory, args []string) (err error) {
func (o *ExportOptions) Run() error {
path := o.Output
format := o.Format
bodyFormat := o.BodyFormat

if o.Blank {
if path == "" {
Expand All @@ -104,51 +93,23 @@ func (o *ExportOptions) Run() error {
return fmt.Errorf("'%s' already exists", path)
}

// TODO: Support these flag, and remove these check.
if bodyFormat != "" {
return fmt.Errorf("--body-format flag is not supported currently")
}
if o.NoBody {
return fmt.Errorf("--no-body flag is not supported currently")
}
if o.PeerDir {
return fmt.Errorf("--peer-dir flag is not supported currently")
}
// if !o.Zipped {
// return fmt.Errorf("only exporting to a zip file is supported, must use --zip flag")
// }

if format == "" {
format = "yaml"
} else if format != "yaml" && format != "json" && format != "xlsx" {
return fmt.Errorf("%s is not an accepted format, options are yaml and json", format)
}

if bodyFormat != "" && bodyFormat != "json" && bodyFormat != "csv" && bodyFormat != "cbor" {
return fmt.Errorf("%s is not an accepted data format, options are json, csv, and cbor", bodyFormat)
}

ref, err := repo.ParseDatasetRef(o.Ref)
if err != nil {
return err
}

if err = lib.AbsPath(&path); err != nil {
return err
if o.Zipped {
return fmt.Errorf("--zipped flag is currently not supported")
}

p := &lib.ExportParams{
Ref: ref,
RootDir: path,
PeerDir: false,
Format: format,
Ref: o.Ref,
Output: path,
Format: format,
}

ok := false
if err = o.ExportRequests.Export(p, &ok); err != nil {
var fileWritten string
if err := o.ExportRequests.Export(p, &fileWritten); err != nil {
return err
}

fmt.Printf("dataset exported to \"%s\"\n", fileWritten)

return nil
}

Expand Down
13 changes: 2 additions & 11 deletions lib/datasets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/qri-io/jsonschema"
"github.com/qri-io/qfs"
"github.com/qri-io/qfs/cafs"
"github.com/qri-io/qri/base"
"github.com/qri-io/qri/config"
"github.com/qri-io/qri/p2p"
"github.com/qri-io/qri/p2p/test"
Expand Down Expand Up @@ -399,19 +400,9 @@ func TestDatasetRequestsGet(t *testing.T) {
}

moviesDs.OpenBodyFile(node.Repo.Filesystem())
moviesBody := make([]interface{}, 0)
moviesBodyFile := moviesDs.BodyFile()
reader := dsio.NewCSVReader(moviesDs.Structure, moviesBodyFile)
for {
ent, err := reader.ReadEntry()
if err != nil {
if err.Error() == "EOF" {
break
}
t.Fatal(err.Error())
}
moviesBody = append(moviesBody, ent.Value)
}
moviesBody, _ := base.ReadEntriesToArray(reader, true, 0, 0)

cases := []struct {
params *GetParams
Expand Down
Loading

0 comments on commit f7df3f9

Please sign in to comment.