From df18f140e27141c194acaa3ab818caa743cf665d Mon Sep 17 00:00:00 2001 From: tyru Date: Sat, 14 Apr 2018 11:49:56 +0900 Subject: [PATCH 01/79] feat: implement intepreter, do operation --- dsl/execute.go | 37 ++++++++++ dsl/op/do.go | 54 ++++++++++++++ dsl/op/lookup.go | 13 ++++ dsl/op/sigcheck.go | 16 ++++ dsl/parse.go | 87 ++++++++++++++++++++++ dsl/types/expr.go | 41 +++++++++++ dsl/types/json.go | 178 +++++++++++++++++++++++++++++++++++++++++++++ dsl/types/op.go | 19 +++++ 8 files changed, 445 insertions(+) create mode 100644 dsl/execute.go create mode 100644 dsl/op/do.go create mode 100644 dsl/op/lookup.go create mode 100644 dsl/op/sigcheck.go create mode 100644 dsl/parse.go create mode 100644 dsl/types/expr.go create mode 100644 dsl/types/json.go create mode 100644 dsl/types/op.go diff --git a/dsl/execute.go b/dsl/execute.go new file mode 100644 index 00000000..d1dcf89a --- /dev/null +++ b/dsl/execute.go @@ -0,0 +1,37 @@ +package dsl + +import ( + "context" + "errors" + + "github.com/vim-volt/volt/dsl/types" +) + +// CtxKeyType is the type of the key of context specified for Execute() +type CtxKeyType uint + +const ( + // CtxTrxIDKey is the key to get transaction ID + CtxTrxIDKey CtxKeyType = iota + // CtxLockJSONKey is the key to get *lockjson.LockJSON value + CtxLockJSONKey + // CtxConfigKey is the key to get *config.Config value + CtxConfigKey +) + +// Execute executes given expr with given ctx. +func Execute(ctx context.Context, expr *types.Expr) (val types.Value, rollback func(), err error) { + ctx = context.WithValue(ctx, CtxTrxIDKey, genNewTrxID()) + if ctx.Value(CtxLockJSONKey) == nil { + return nil, func() {}, errors.New("no lock.json key in context") + } + if ctx.Value(CtxConfigKey) == nil { + return nil, func() {}, errors.New("no config.toml key in context") + } + return expr.Eval(ctx) +} + +func genNewTrxID() types.TrxID { + // TODO: Get unallocated transaction ID looking $VOLTPATH/trx/ directory + return types.TrxID(0) +} diff --git a/dsl/op/do.go b/dsl/op/do.go new file mode 100644 index 00000000..c56d5faa --- /dev/null +++ b/dsl/op/do.go @@ -0,0 +1,54 @@ +package op + +import ( + "context" + + "github.com/vim-volt/volt/dsl/types" +) + +func init() { + opsMap["do"] = &DoOp{} +} + +// DoOp is "do" operation +type DoOp struct{} + +// Describe describes its task(s) as zero or more lines of messages. +func (op *DoOp) Describe(args []types.Value) []string { + // TODO + return []string{} +} + +// Bind binds its arguments, and check if the types of values are correct. +func (op *DoOp) Bind(args ...types.Value) (*types.Expr, error) { + sig := make([]types.Type, 0, len(args)) + for i := 0; i < len(args); i++ { + sig = append(sig, types.ArrayType) + } + if err := signature(sig...).check(args); err != nil { + return nil, err + } + retType := args[len(args)-1].Type() + return types.NewExpr(op, args, retType), nil +} + +// InvertExpr returns inverted expression: Call Value.Invert() for each argument, +// and reverse arguments order. +func (op *DoOp) InvertExpr(args []types.Value) (*types.Expr, error) { + newargs := make([]types.Value, len(args)) + newargs[0] = args[0] // message + for i := 1; i < len(args); i++ { + a, err := args[i].Invert() + if err != nil { + return nil, err + } + newargs[len(args)-i] = a + } + return op.Bind(newargs...) +} + +// Execute executes "do" operation +func (op *DoOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { + // TODO + return nil, func() {}, nil +} diff --git a/dsl/op/lookup.go b/dsl/op/lookup.go new file mode 100644 index 00000000..ef318243 --- /dev/null +++ b/dsl/op/lookup.go @@ -0,0 +1,13 @@ +package op + +import "github.com/vim-volt/volt/dsl/types" + +// opsMap holds all operation structs. +// All operations in dsl/op/*.go sets its struct to this in init() +var opsMap map[string]types.Op + +// Lookup looks up operation name +func Lookup(name string) (types.Op, bool) { + op, exists := opsMap[name] + return op, exists +} diff --git a/dsl/op/sigcheck.go b/dsl/op/sigcheck.go new file mode 100644 index 00000000..02885fa1 --- /dev/null +++ b/dsl/op/sigcheck.go @@ -0,0 +1,16 @@ +package op + +import "github.com/vim-volt/volt/dsl/types" + +func signature(sig ...types.Type) *sigChecker { + return &sigChecker{sig} +} + +type sigChecker struct { + sig []types.Type +} + +func (sc *sigChecker) check(args []types.Value) error { + // TODO + return nil +} diff --git a/dsl/parse.go b/dsl/parse.go new file mode 100644 index 00000000..602aa110 --- /dev/null +++ b/dsl/parse.go @@ -0,0 +1,87 @@ +package dsl + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/vim-volt/volt/dsl/op" + "github.com/vim-volt/volt/dsl/types" +) + +// ParseOp parses expr JSON. +// This calls Parse() function. And +// 1. Does split to operation and its arguments +// 2. Analyzes semantically its arguments recursively +// 3. Convert the result value to *Expr +func ParseOp(expr []byte) (*types.Expr, error) { + value, err := Parse(expr) + if err != nil { + return nil, err + } + array, ok := value.(*types.Array) + if !ok { + return nil, errors.New("top-level value is not an array") + } + if len(array.Value) == 0 { + return nil, errors.New("expected operation but got an empty array") + } + opName, ok := array.Value[0].(*types.String) + if !ok { + return nil, fmt.Errorf("expected operation name but got '%+v'", array.Value[0]) + } + op, exists := op.Lookup(opName.Value) + if !exists { + return nil, fmt.Errorf("no such operation '%s'", opName.Value) + } + args := array.Value[1:] + return op.Bind(args...) +} + +// Parse parses expr JSON. +// This only maps encoding/json's types to Value types. +func Parse(expr []byte) (types.Value, error) { + var value interface{} + if err := json.Unmarshal(expr, value); err != nil { + return nil, err + } + return convert(value) +} + +func convert(value interface{}) (types.Value, error) { + switch val := value.(type) { + case nil: + return types.NullValue, nil + case bool: + if val { + return types.TrueValue, nil + } + return types.FalseValue, nil + case string: + return &types.String{val}, nil + case float64: + return &types.Number{val}, nil + case []interface{}: + a := make([]types.Value, 0, len(val)) + for o := range a { + v, err := convert(o) + if err != nil { + return nil, err + } + a = append(a, v) + } + return &types.Array{a}, nil + case map[string]interface{}: + m := make(map[string]types.Value, len(val)) + for k, o := range m { + v, err := convert(o) + if err != nil { + return nil, err + } + m[k] = v + } + return &types.Object{m}, nil + default: + return nil, fmt.Errorf("unknown value was given '%+v'", val) + } +} diff --git a/dsl/types/expr.go b/dsl/types/expr.go new file mode 100644 index 00000000..a56e5790 --- /dev/null +++ b/dsl/types/expr.go @@ -0,0 +1,41 @@ +package types + +import "context" + +// Expr has an operation and its arguments +type Expr struct { + op Op + args []Value + typ Type +} + +// NewExpr is the constructor of Expr +func NewExpr(op Op, args []Value, typ Type) *Expr { + return &Expr{op: op, args: args, typ: typ} +} + +// TrxID is a transaction ID, which is a serial number and directory name of +// transaction log file. +// XXX: this should be in transaction package? +type TrxID int64 + +// Eval evaluates given expression expr with given transaction ID trxID. +func (expr *Expr) Eval(ctx context.Context) (val Value, rollback func(), err error) { + return expr.op.Execute(ctx, expr.args) +} + +// Invert inverts this expression. +// This just calls Op.InvertExpr() with arguments. +func (expr *Expr) Invert() (Value, error) { + return expr.op.InvertExpr(expr.args) +} + +// Describe describes its task(s) as zero or more lines of messages. +func (expr *Expr) Describe() []string { + return expr.op.Describe(expr.args) +} + +// Type returns the type. +func (expr *Expr) Type() Type { + return expr.typ +} diff --git a/dsl/types/json.go b/dsl/types/json.go new file mode 100644 index 00000000..0962c246 --- /dev/null +++ b/dsl/types/json.go @@ -0,0 +1,178 @@ +package types + +import "context" + +// Value is JSON value +type Value interface { + // Invert returns inverted value/operation. + // All type values are invertible. + // Literals like string,number,... return itself as-is. + Invert() (Value, error) + + // Eval returns a evaluated value. + // Literals like string,number,... return itself as-is. + Eval(ctx context.Context) (val Value, rollback func(), err error) + + // Type returns the type of this value. + Type() Type +} + +// Type is a type of expression +type Type uint + +// ================ Null ================ + +// Null is JSON null struct +type Null struct{} + +// NullType is JSON null type +const NullType Type = 1 + +// NullValue is the JSON null value +var NullValue = &Null{} + +// Invert returns itself as-is. +func (v *Null) Invert() (Value, error) { + return v, nil +} + +// Eval returns itself as-is. +func (v *Null) Eval(context.Context) (val Value, rollback func(), err error) { + return v, func() {}, nil +} + +// Type returns the type. +func (v *Null) Type() Type { + return NullType +} + +// ================ Bool ================ + +// Bool is JSON boolean struct +type Bool struct { + Value bool +} + +// BoolType is JSON boolean type +const BoolType Type = 2 + +// Invert returns itself as-is. All literal types of JSON values are the same. +func (v *Bool) Invert() (Value, error) { + return v, nil +} + +// Eval returns itself as-is. +func (v *Bool) Eval(context.Context) (val Value, rollback func(), err error) { + return v, func() {}, nil +} + +// Type returns the type. +func (v *Bool) Type() Type { + return BoolType +} + +// TrueValue is the JSON true value +var TrueValue = &Bool{true} + +// FalseValue is the JSON false value +var FalseValue = &Bool{false} + +// ================ Number ================ + +// Number is JSON number struct +type Number struct { + Value float64 +} + +// NumberType is JSON number struct +const NumberType Type = 3 + +// Invert returns itself as-is. All literal types of JSON values are the same. +func (v *Number) Invert() (Value, error) { + return v, nil +} + +// Eval returns itself as-is. +func (v *Number) Eval(context.Context) (val Value, rollback func(), err error) { + return v, func() {}, nil +} + +// Type returns the type. +func (v *Number) Type() Type { + return NumberType +} + +// ================ String ================ + +// String is JSON string struct +type String struct { + Value string +} + +// StringType is JSON string type +const StringType Type = 4 + +// Invert returns itself as-is. All literal types of JSON values are the same. +func (v *String) Invert() (Value, error) { + return v, nil +} + +// Eval returns itself as-is. +func (v *String) Eval(context.Context) (val Value, rollback func(), err error) { + return v, func() {}, nil +} + +// Type returns the type. +func (v *String) Type() Type { + return StringType +} + +// ================ Array ================ + +// Array is JSON array struct +type Array struct { + Value []Value +} + +// ArrayType is JSON array type +const ArrayType Type = 5 + +// Invert returns itself as-is. All literal types of JSON values are the same. +func (v *Array) Invert() (Value, error) { + return v, nil +} + +// Eval returns itself as-is. +func (v *Array) Eval(context.Context) (val Value, rollback func(), err error) { + return v, func() {}, nil +} + +// Type returns the type. +func (v *Array) Type() Type { + return ArrayType +} + +// ================ Object ================ + +// Object is JSON object struct +type Object struct { + Value map[string]Value +} + +// ObjectType is JSON object type +const ObjectType Type = 6 + +// Invert returns itself as-is. All literal types of JSON values are the same. +func (v *Object) Invert() (Value, error) { + return v, nil +} + +// Eval returns itself as-is. +func (v *Object) Eval(context.Context) (val Value, rollback func(), err error) { + return v, func() {}, nil +} + +// Type returns the type. +func (v *Object) Type() Type { + return ObjectType +} diff --git a/dsl/types/op.go b/dsl/types/op.go new file mode 100644 index 00000000..4c044367 --- /dev/null +++ b/dsl/types/op.go @@ -0,0 +1,19 @@ +package types + +import "context" + +// Op is an operation of JSON DSL +type Op interface { + // Bind binds its arguments, and check if the types of values are correct. + Bind(args ...Value) (*Expr, error) + + // InvertExpr returns inverted expression + InvertExpr(args []Value) (*Expr, error) + + // Execute executes this operation and returns its result value and error + Execute(ctx context.Context, args []Value) (ret Value, rollback func(), err error) + + // Describe returns its type(s) of zero or more arguments and one return value. + // The types are used for type-checking. + Describe(args []Value) []string +} From 93d46db71bfaa136eb4fae4753263661d8e3d37e Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 15 Apr 2018 17:05:54 +0900 Subject: [PATCH 02/79] doc: add refactoring note (introduction to JSON DSL) --- _docs/json-dsl.md | 314 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 _docs/json-dsl.md diff --git a/_docs/json-dsl.md b/_docs/json-dsl.md new file mode 100644 index 00000000..fac24432 --- /dev/null +++ b/_docs/json-dsl.md @@ -0,0 +1,314 @@ + +[Original (Japanese)](https://gist.github.com/tyru/819e593b2d996321298f6338bbaa34e0) + +# Volt refactoring note: JSON DSL and Transaction + +## Example of JSON DSL + +```json +["label", + 0, + "installing plugins:", + ["vimdir/with-install", + ["parallel", + ["label", + 1, + "github.com/tyru/open-browser.vim ... {{if .Done}}done!{{end}}", + ["parallel", + ["lockjson/add", + ["repos/git/clone", "github.com/tyru/open-browser.vim"], + ["default"]], + ["plugconf/install", "github.com/tyru/open-browser.vim"]]], + ["label", + 1, + "github.com/tyru/open-browser-github.vim ... {{if .Done}}done!{{end}}", + ["parallel", + ["lockjson/add", + ["repos/git/clone", "github.com/tyru/open-browser-github.vim"], + ["default"]], + ["plugconf/install", "github.com/tyru/open-browser-github.vim"]]]]]] +``` + +## Wordings + +* operator: "function" of DSL + * e.g. "label" + * e.g. "parallel" +* macro: like "function", but is expanded before execution + * see JSON DSL note (TODO) +* expression: the form of operator application + * e.g. `["label", ...]` + * e.g. `["parallel", ...]` +* transaction log (file): a JSON file which is saved at + `$VOLTPATH/trx/{id}/log.json` + +## Goals + +This refactoring allows us or makes it easy to implement the following issues: + +1. JSON file of AST is saved under `$VOLTPATH/trx/{id}/` +2. The history feature (undo, redo, list, ...) like `yum history` + [#147](https://github.com/vim-volt/volt/issues/147) + * `volt history undo` executes `[$invert, expr]` for transaction log + * `volt history redo` just executes saved expression in transaction log +3. Display progress bar [#118](https://github.com/vim-volt/volt/issues/188) + * Updating before/after each expression node is executed +4. `volt watch` command can be easy to implement + [#174](https://github.com/vim-volt/volt/issues/174) + * Current `volt build` implementation installs all repositories of current + profile, not specific repositories +5. Parallelism + * Currently each command independently implements it using goroutine, but DSL + provides higher level parallel processing +6. More detailed unit testing + * Small component which is easy to test + * And especially "Subcmd layer" is easy because it does not access to + filesystem +7. Gracefully rollback when an error occurs while processing a DSL + +## Layered architecture + +The volt commands like `volt get` which may modify lock.json, config.toml(#221), +filesystem, are executed in several steps: + +1. (Gateway layer): pass subcommand arguments, lock.json & config.toml structure + to Subcmd layer +2. (Subcmd layer): Create an AST (abstract syntax tree) according to given information + * This layer cannot touch filesystem, because it makes unit testing difficult +3. (DSL layer): Execute the AST. This note mainly describes this layer's design + +Below is the dependency graph: + +``` +Gateway --> Subcmd --> DSL +``` + +* Gateway only depends Subcmd +* Subcmd doesn't know Gateway +* Subcmd only depends DSL +* DSL doesn't know Subcmd + +## Abstract + +JSON DSL is a S-expression like DSL represented as JSON format. + +```json +["op", "arg1", "arg2"] +``` + +This is an application form (called "expression" in this note) when `op` is a +known operator name. But if `op` is not a known operator, it is just an array +literal value. Each expression has 0 or more parameters. And evaluation +strategy is a non-strict evaluation. + +Parameter types are + +* JSON types + * boolean + * string + * number + * array + * object +* expression + +But all values must be able to be serialized to JSON. Because AST of whole +process is serialized and saved as a "transaction log file". The process can be +rolled back, when an error occur while the process, or user send SIGINT signal, +or `volt history undo` command is executed. The transaction log file does not +have ID but the saved directory `{id}` does: + +``` +$VOLTPATH/trx/{id}/{logfile} +``` + +`{id}` is called transaction ID, a simple serial number assigned `max + 1` like +DB's AUTOINCREMENT. + +JSON DSL has the following characteristic: + +* Idempotent +* Invertible + +## Idempotent + +All operators have an idempotency: "even if an expression is executed twice, it +guarantees the existence (not the content) of a requested resource." + +One also might think that "why the definition is so ambiguos?" Because, if we +define operator's idempotency as "after an expression was executed twice at +different times, lock.json, filesystem must be the same." But `volt get A` +installs the latest plugin of remote repository. At the first time and second +time, the repository's HEAD may be different. But it guarantees that the +existence of specified property of lock.json, and the repository on filesystem. + +Here is a more concrete example: + +1. Install plugin A, B, C by `volt get A B C`. +2. Uninstall plugin B. +3. Re-run 1's operation by `volt history redo {id}` + +At 3, one might think that "3 should raise an error because plugin B is already +uninstalled!" But volt does raise an error, because operators when uninstalling +(`repos/git/delete`, `lockjson/remove`, `plugconf/delete`) does nothing if given +plugin does not exist, like HTTP's DELETE method. Those operator guarantees +that "the specified resource is deleted after the execution." + +## Invertible + +All operators have an inverse expression. Here is the example of JSON DSL when +[tyru/caw.vim](https://github.com/tyru/caw.vim) plugin is installed (it may be +different with latest volt's JSON DSL when installing. this is a simplified +version for easiness). + +```json +["vimdir/with-install", + ["do", + ["lockjson/add", + ["repos/git/clone", "github.com/tyru/caw.vim"], + ["default"]], + ["plugconf/install", "github.com/tyru/caw.vim"]]] +``` + +I show you what happens in several steps when you "invert" the expression like +`volt history undo`. + +At first, to invert the expression, `$invert` macro is used: + +```json +["$invert", + ["vimdir/with-install", + ["do", + ["lockjson/add", + ["repos/git/clone", "github.com/tyru/caw.vim"], + ["default"]], + ["plugconf/install", "github.com/tyru/caw.vim"]]]] +``` + +`["$invert", ["vimdir/with-install", expr]]` is expanded to +`["vimdir/with-install", ["$invert", expr]]`. Internally, it is implemented as +calling `Invert()` method of `vimdir/with-install` operator struct. See "Go +API" section of JSONDSL note (TODO). + +```json +["vimdir/with-install", + ["$invert", + ["do", + ["lockjson/add", + ["repos/git/clone", "github.com/tyru/caw.vim"], + ["default"]], + ["plugconf/install", "github.com/tyru/caw.vim"]]]] +``` + +And `["$invert", ["do", expr1, expr2]]` becomes +`["do", ["$invert", expr2], ["$invert", expr1]]`. +Note that `expr1` and `expr2` becomes reversed order. + +```json +["vimdir/with-install", + ["do", + ["$invert", + ["plugconf/install", "github.com/tyru/caw.vim"]], + ["$invert", + ["lockjson/add", + ["repos/git/clone", "github.com/tyru/caw.vim"], + ["default"]]]]] +``` + +And +* `["$invert", ["lockjson/add", repos, profiles]]` becomes + `["lockjson/remove", ["$invert", repos], ["$invert", profiles]]` +* `["$invert", ["plugconf/install", repos]]` becomes + `["plugconf/delete", ["$invert", repos]]` + +```json +["vimdir/with-install", + ["do", + ["plugconf/delete", ["$invert", "github.com/tyru/caw.vim"]], + ["lockjson/remove", + ["$invert", ["repos/git/clone", "github.com/tyru/caw.vim"]], + ["$invert", ["default"]]]]] +``` + +`["$invert", ["repos/git/clone", repos_path]]` becomes +`["repos/git/delete", ["$invert", repos_path]]`. + +```json +["vimdir/with-install", + ["do", + ["plugconf/delete", ["$invert", "github.com/tyru/caw.vim"]], + ["lockjson/remove", + ["repos/git/delete", ["$invert", "github.com/tyru/caw.vim"]], + ["$invert", ["default"]]]]] +``` + +And if `$invert` is applied to literals like string, JSON array, it just remains +as-is. + +```json +["vimdir/with-install", + ["do", + ["plugconf/delete", "github.com/tyru/caw.vim"], + ["lockjson/remove", + ["repos/git/delete", "github.com/tyru/caw.vim"], + ["default"]]]] +``` + +We can successfully evaluate the inverse expression of the first expression :) + +## The implementation of an operator + +To achieve goals as mentioned above, +the signature of Go function of an operator is: + +```go +func (ctx Context, args []Value) (ret Value, rollback func(), err error) +``` + +* [Go context](https://golang.org/pkg/context/) makes graceful rollback easy. +* `Value` is the interface which is serializable to JSON. + All types of JSON DSL must implement this interface. +* `rollback` function is to rollback this operator's process. + Invoking rollback function after invoking this operator function + must rollback lock.json, config.toml, filesystem to the previous state. + * TODO: inverse expression may be enough for rollback? + +## Operator responsibility + +As the above signature shows, operators must take care the following points: + +1. is cancellable with given context, because Go's context is not a magic to + make it cancellable if receiving it as an argument :) +2. must rollback to the previous state invoking rollback function + * TODO: inverse expression may be enough for rollback? +3. must guarantee idempotency: it must not destroy user environment if + expression is executed twice +4. must have an inverse expression (not inverse operator) + +### Uninstall operation should be "Recovable"? + +If `volt history undo {id}` takes uninstall operation, it executes `git clone` +to install plugin(s) from remote. But should it be recovable like uninstall +operation just "archive" to specific directory, and `volt history undo {id}` +"unarchive" the repository? + +I don't think it is what a user wants to do. I think, when a user undoes +uninstall operation, it should just clone new repositories from remote. A user +just wants repositories to be back, of the latest one not the old version. + +It is possible to design to map commands to archive/unarchive operations (I +thought it in the initial design). But I found it is redundant. + +## Update operation must be Recovable! + +TODO + +* `"repos/git/fetch"` +* `"repos/git/reset"` + +## JSON DSL API + +TODO + +## Go API + +TODO From 99ea790c755a4de51d425742a0f36999a38fc10b Mon Sep 17 00:00:00 2001 From: Takuya Fujiwara Date: Sun, 15 Apr 2018 17:09:16 +0900 Subject: [PATCH 03/79] doc: update json-dsl.md --- _docs/json-dsl.md | 564 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 512 insertions(+), 52 deletions(-) diff --git a/_docs/json-dsl.md b/_docs/json-dsl.md index fac24432..72a145bb 100644 --- a/_docs/json-dsl.md +++ b/_docs/json-dsl.md @@ -7,24 +7,21 @@ ```json ["label", - 0, "installing plugins:", ["vimdir/with-install", ["parallel", ["label", - 1, - "github.com/tyru/open-browser.vim ... {{if .Done}}done!{{end}}", + " github.com/tyru/open-browser.vim ... {{if .Done}}done!{{end}}", ["parallel", ["lockjson/add", - ["repos/git/clone", "github.com/tyru/open-browser.vim"], + ["repos/get", "github.com/tyru/open-browser.vim"], ["default"]], ["plugconf/install", "github.com/tyru/open-browser.vim"]]], ["label", - 1, - "github.com/tyru/open-browser-github.vim ... {{if .Done}}done!{{end}}", + " github.com/tyru/open-browser-github.vim ... {{if .Done}}done!{{end}}", ["parallel", ["lockjson/add", - ["repos/git/clone", "github.com/tyru/open-browser-github.vim"], + ["repos/get", "github.com/tyru/open-browser-github.vim"], ["default"]], ["plugconf/install", "github.com/tyru/open-browser-github.vim"]]]]]] ``` @@ -46,35 +43,35 @@ This refactoring allows us or makes it easy to implement the following issues: -1. JSON file of AST is saved under `$VOLTPATH/trx/{id}/` +1. JSON file of AST (abstract syntax tree) is saved under `$VOLTPATH/trx/{id}/` 2. The history feature (undo, redo, list, ...) like `yum history` [#147](https://github.com/vim-volt/volt/issues/147) - * `volt history undo` executes `[$invert, expr]` for transaction log - * `volt history redo` just executes saved expression in transaction log + * `volt history undo` executes `[$invert, expr]` for transaction log + * `volt history redo` just executes saved expression in transaction log 3. Display progress bar [#118](https://github.com/vim-volt/volt/issues/188) - * Updating before/after each expression node is executed + * Updating progress bars according to `["label", ...]` expression 4. `volt watch` command can be easy to implement [#174](https://github.com/vim-volt/volt/issues/174) - * Current `volt build` implementation installs all repositories of current - profile, not specific repositories + * Current `volt build` implementation installs all repositories of current + profile, not specific repositories 5. Parallelism - * Currently each command independently implements it using goroutine, but DSL - provides higher level parallel processing + * Currently each command independently implements it using goroutine, but DSL + provides higher level parallel processing 6. More detailed unit testing - * Small component which is easy to test - * And especially "Subcmd layer" is easy because it does not access to - filesystem -7. Gracefully rollback when an error occurs while processing a DSL + * Small component is easy to test + * And especially "Subcmd layer" is easy because it does not access to + filesystem +7. Gracefully rollback when an error occurs while processing a DSL [#200](https://github.com/vim-volt/volt/issues/200) ## Layered architecture -The volt commands like `volt get` which may modify lock.json, config.toml(#221), +The volt commands like `volt get` which may modify lock.json, config.toml([#221](https://github.com/vim-volt/volt/issues/221)), filesystem, are executed in several steps: 1. (Gateway layer): pass subcommand arguments, lock.json & config.toml structure to Subcmd layer -2. (Subcmd layer): Create an AST (abstract syntax tree) according to given information - * This layer cannot touch filesystem, because it makes unit testing difficult +2. (Subcmd layer): Create an AST according to given information + * This layer cannot touch filesystem, because it makes unit testing difficult 3. (DSL layer): Execute the AST. This note mainly describes this layer's design Below is the dependency graph: @@ -149,7 +146,7 @@ Here is a more concrete example: At 3, one might think that "3 should raise an error because plugin B is already uninstalled!" But volt does raise an error, because operators when uninstalling -(`repos/git/delete`, `lockjson/remove`, `plugconf/delete`) does nothing if given +(`repos/delete`, `lockjson/remove`, `plugconf/delete`) does nothing if given plugin does not exist, like HTTP's DELETE method. Those operator guarantees that "the specified resource is deleted after the execution." @@ -164,7 +161,7 @@ version for easiness). ["vimdir/with-install", ["do", ["lockjson/add", - ["repos/git/clone", "github.com/tyru/caw.vim"], + ["repos/get", "github.com/tyru/caw.vim"], ["default"]], ["plugconf/install", "github.com/tyru/caw.vim"]]] ``` @@ -179,7 +176,7 @@ At first, to invert the expression, `$invert` macro is used: ["vimdir/with-install", ["do", ["lockjson/add", - ["repos/git/clone", "github.com/tyru/caw.vim"], + ["repos/get", "github.com/tyru/caw.vim"], ["default"]], ["plugconf/install", "github.com/tyru/caw.vim"]]]] ``` @@ -194,7 +191,7 @@ API" section of JSONDSL note (TODO). ["$invert", ["do", ["lockjson/add", - ["repos/git/clone", "github.com/tyru/caw.vim"], + ["repos/get", "github.com/tyru/caw.vim"], ["default"]], ["plugconf/install", "github.com/tyru/caw.vim"]]]] ``` @@ -210,7 +207,7 @@ Note that `expr1` and `expr2` becomes reversed order. ["plugconf/install", "github.com/tyru/caw.vim"]], ["$invert", ["lockjson/add", - ["repos/git/clone", "github.com/tyru/caw.vim"], + ["repos/get", "github.com/tyru/caw.vim"], ["default"]]]]] ``` @@ -225,19 +222,19 @@ And ["do", ["plugconf/delete", ["$invert", "github.com/tyru/caw.vim"]], ["lockjson/remove", - ["$invert", ["repos/git/clone", "github.com/tyru/caw.vim"]], + ["$invert", ["repos/get", "github.com/tyru/caw.vim"]], ["$invert", ["default"]]]]] ``` -`["$invert", ["repos/git/clone", repos_path]]` becomes -`["repos/git/delete", ["$invert", repos_path]]`. +`["$invert", ["repos/get", path]]` becomes +`["repos/delete", ["$invert", path]]`. ```json ["vimdir/with-install", ["do", ["plugconf/delete", ["$invert", "github.com/tyru/caw.vim"]], ["lockjson/remove", - ["repos/git/delete", ["$invert", "github.com/tyru/caw.vim"]], + ["repos/delete", ["$invert", "github.com/tyru/caw.vim"]], ["$invert", ["default"]]]]] ``` @@ -249,12 +246,33 @@ as-is. ["do", ["plugconf/delete", "github.com/tyru/caw.vim"], ["lockjson/remove", - ["repos/git/delete", "github.com/tyru/caw.vim"], + ["repos/delete", "github.com/tyru/caw.vim"], ["default"]]]] ``` We can successfully evaluate the inverse expression of the first expression :) +### Uninstall operation should be "Recovable"? + +If `volt history undo {id}` takes uninstall operation, it executes `git clone` +to install plugin(s) from remote. But should it be recovable like uninstall +operation just "archive" to specific directory, and `volt history undo {id}` +"unarchive" the repository? + +I don't think it is what a user wants to do. I think, when a user undoes +uninstall operation, it should just clone new repositories from remote. A user +just wants repositories to be back, of the latest one not the old version. + +It is possible to design to map commands to archive/unarchive operations (I +thought it in the initial design). But I found it is redundant. + +## Update operation must be Recovable! + +TODO + +* `"repos/git/fetch"` +* `"repos/git/update"` + ## The implementation of an operator To achieve goals as mentioned above, @@ -270,7 +288,7 @@ func (ctx Context, args []Value) (ret Value, rollback func(), err error) * `rollback` function is to rollback this operator's process. Invoking rollback function after invoking this operator function must rollback lock.json, config.toml, filesystem to the previous state. - * TODO: inverse expression may be enough for rollback? + * TODO: inverse expression may be enough for rollback? ## Operator responsibility @@ -279,36 +297,478 @@ As the above signature shows, operators must take care the following points: 1. is cancellable with given context, because Go's context is not a magic to make it cancellable if receiving it as an argument :) 2. must rollback to the previous state invoking rollback function - * TODO: inverse expression may be enough for rollback? + * TODO: inverse expression may be enough for rollback? 3. must guarantee idempotency: it must not destroy user environment if expression is executed twice 4. must have an inverse expression (not inverse operator) -### Uninstall operation should be "Recovable"? +## JSON DSL API -If `volt history undo {id}` takes uninstall operation, it executes `git clone` -to install plugin(s) from remote. But should it be recovable like uninstall -operation just "archive" to specific directory, and `volt history undo {id}` -"unarchive" the repository? +TODO: Move to Godoc. + +### Macro + +All macros has `$` prefixed name for readability. +Macros are not saved in transaction log (expanded before saving). + +* `["$invert", expr Expr[* => *]] Expr[* => *]` + * Returns inverse expression of given expression. + * Internally, this macro calls `InvertExpr()` method of each operator struct. + * What value is returned depends on each operator's `InvertExpr()` + implementation. + +* `["$eval", expr Expr[* => *]] Expr[* => *]` + * Evaluate `expr` at parsing time. + This is useful to save evaluated value to transaction log, + instead of its expression. + * See `repos/git/fetch`, `repos/git/update` for concrete example. + +### Basic operators + +* `["label", tmpl string, expr Expr[* => R]] R` + * Render `tmpl` by text/template to progress bar using + [pgr](https://github.com/tyru/pgr/) library. + Returns the evaluated value of `expr`. + * e.g. + * `["$invert", ["label", "msg", expr]]` = `["label", "revert: \"msg\"", ["$invert", expr]]` + * See `Label examples` section for more details + +* `["do", expr1 Expr[* => R1], ..., expr_last Expr[* => R2]] R2` + * Executes multiple expressions in series. + * Returns the evaluated value of the last expression. + * e.g. + * `["$invert", ["do", expr1, expr2]]` = `["do", ["$invert", expr1], ["$invert", expr2]]` + * Note that the arguments are reversed. + +* `["parallel", msg string, expr1 Expr[* => R1], ..., expr_last Expr[* => R2]] R2` + * Executes multiple expressions in parallel. + * Returns the evaluated value of the last expression. + * e.g. + * `["$invert", ["parallel", expr1, expr2]]` = `["parallel", ["$invert", expr2], ["$invert", expr1]]` + * The arguments are **not** reversed because parallel does not care of + execution order of given expressions. + +### Repository operators + +* `["repos/get", path ReposPath] Repos` + * If the repository does not exist, executes `git clone` on given `path` + repository and saves to `$VOLTPATH/repos/{path}`. + * If the repository already exists, returns the repository information. + The information is the repository information on filesystem, not in lock.json. + * `volt get A` emits `["lockjson/add", ["repos/get", path], profiles]` + * If A is git repository, it updates lock.json information with repository + information. + * If A is static repository, it does nothing. + * e.g. + * `["repos/get", "github.com/tyru/caw.vim"]` + * `["$invert", ["repos/get", path]]` = `["repos/delete", ["$invert", path]]` + +* `["repos/delete", path ReposPath] Repos` + * If the repository does not exist, it does nothing. + * If the repository already exists, returns the repository information. + The information is the repository information on filesystem, not in lock.json. + * `volt rm -r A` emits `["lockjson/add", ["repos/delete", path], profiles]` + * `volt rm A` emits `["lockjson/add", ["repos/info", path], profiles]` + * If A is git repository, it deletes `path` repository's directory. + * If A is static repository, shows warning `static repository cannot be + deleted by 'volt rm' command. delete '{path}' manually` + * To avoid removing local repository accidentally. + * e.g. + * `["repos/delete", "github.com/tyru/caw.vim"]` + * `["$invert", ["repos/delete", path]]` = `["repos/get", ["$invert", path]]` + +* `["repos/info", path ReposPath] Repos` + * Returns `path` repository information. + * e.g. + * `["lockjson/add", ["repos/get", path], profiles]` + * `volt rm A` emits this expression. + * `["$invert", ["repos/info", path]]` = `["repos/info", ["$invert", path]]` + +* `["repos/git/fetch", path ReposPath] head_hash string` + * Executes `git fetch` on `path` repository. + Returns the hash string of HEAD. + * For bare repository, the result HEAD hash string is the hash string of + default branch's HEAD. + * e.g. + * `["$invert", ["repos/git/fetch", path]]` = `["repos/git/fetch", ["$invert", path]]` + +* `["repos/git/update", path ReposPath, target_hash string, prev_hash string] void` + * This fails if the working tree is dirty. + * If `target_hash` is not merged yet in current branch, try `git merge + --ff-only` (it raises an error if cannot merge with fast-forward) + * If `target_hash` is already merged in current branch, try `git reset --hard + {target_hash}` + * It does nothing for bare git repository. + * e.g. + * `["repos/git/update", "github.com/tyru/caw.vim", ["$eval", ["repos/git/rev-parse", "HEAD", "github.com/tyru/caw.vim"]], ["$eval", ["repos/git/fetch", "github.com/tyru/caw.vim"]]]` + * To save evaluated hash string in transaction log instead of its + expression, apply `$eval` to `repos/git/fetch` expression. + * `["$invert", ["repos/git/update", path, target_hash, prev_hash]]` = `["repos/git/update", ["$invert", path], ["$invert", prev_hash], ["$invert", target_hash]]` + +* `["repos/git/rev-parse", str string, path ReposPath] hash string` + * Returns hash string from `str` argument. This executes `git rev-parse + {str}` on `path` repository. + * e.g. + * `["repos/git/rev-parse", "HEAD", "github.com/tyru/caw.vim"]` + * `["$invert", ["repos/git/rev-parse", str, path]]` = `["repos/git/rev-parse", ["$invert", str], ["$invert", path]]` + +### lock.json operators + +* `["lockjson/add", repos Repos, profiles []string]` + * Add `repos` information to `repos[]` array in lock.json. + If `profiles` is not empty, the repository name is added to + specified profile (`profiles[]` array in lock.json). + * It fails if specified profile name does not exist. + * Need to create profile before using `lockjson/profile/add`. + * e.g. + * `["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], ["default"]]` + * `["$invert", ["lockjson/add", repos, profiles]]` = `["lockjson/remove", ["$invert", repos], ["$invert", profiles]]` + +* `["lockjson/profile/add", name string] Profile` + * Add empty profile named `name` if it does not exist. + If it exists, do nothing. + Returns created/existed profile. + * e.g. + * `["lockjson/profile/add", "default"]` + * `["$invert", ["lockjson/profile/add", name]]` = `["lockjson/profile/remove", ["$invert", name]]` + +* `["lockjson/profile/remove", name string] Profile` + * Remove specified profile named `name` if it exists. + If it does not exist, do nothing. + Returns removed profile. + * e.g. + * `["lockjson/profile/remove", "default"]` + * `["$invert", ["lockjson/profile/remove", name]]` = `["lockjson/profile/add", ["$invert", name]]` + +### Plugconf operators + +* `["plugconf/install", path ReposPath] void` + * Created plugconf of specified repository, or fetch a plugconf file from + [vim-volt/plugconf-templates](https://github.com/vim-volt/plugconf-templates) + * e.g. + * `["plugconf/install", "github.com/tyru/caw.vim"]` + * `["$invert", ["plugconf/install", path]]` = `["plugconf/delete", ["$invert", path]]` + +* `["plugconf/delete", path ReposPath] void` + * Delete a plugconf file of `path`. + If it does not exist, do nothing. + * e.g. + * `["plugconf/delete", "github.com/tyru/caw.vim"]` + * `["$invert", ["plugconf/delete", path]]` = `["plugconf/install", ["$invert", path]]` + +### Vim directory operators + +* `["vimdir/with-install", paths "all" | []ReposPath, expr Expr[* => R]] R` + * `paths` is the list of repositories to build after `expr` is executed. + * `"all"` means all repositories of current profile. + * e.g. + * `["$invert", ["vimdir/with-install", paths, expr]]` = `["vimdir/with-install", ["$invert", paths], ["$invert", expr]]` + * See "Why `vimdir/install` and `vimdir/uninstall` operators do not exist?" + section + +### Why `vimdir/install` and `vimdir/uninstall` operators do not exist? + +We'll describe why `vimdir/install` and `vimdir/uninstall` operators do not +exist, and `vimdir/with-install` exists instead. + +For example, now we have the following expression with `vimdir/uninstall`. It +removes lock.json, deletes repository, plugconf, and also the repository in vim +directory: -I don't think it is what a user wants to do. I think, when a user undoes -uninstall operation, it should just clone new repositories from remote. A user -just wants repositories to be back, of the latest one not the old version. +```json +[ + "do", + ["lockjson/remove", + { + "type": "git", + "path": "github.com/tyru/caw.vim", + "version": "deadbeefcafebabe" + }, + ["default"] + ], + ["repos/delete", "github.com/tyru/caw.vim"], + ["plugconf/delete", "github.com/tyru/caw.vim"], + ["vimdir/uninstall", "github.com/tyru/caw.vim"] +] +``` -It is possible to design to map commands to archive/unarchive operations (I -thought it in the initial design). But I found it is redundant. +And below is the inverse expression of above. -## Update operation must be Recovable! +```json +[ + "do", + ["vimdir/install", "github.com/tyru/caw.vim"], + ["plugconf/install", "github.com/tyru/caw.vim"], + ["repos/get", "github.com/tyru/caw.vim"], + ["lockjson/add", + { + "type": "git", + "path": "github.com/tyru/caw.vim", + "version": "deadbeefcafebabe" + }, + ["default"] + ] +] +``` -TODO +1. Installs the repository to vim directory + **EVEN THE REPOSITORY DOES NOT EXIST YET!** +2. Installs plugconf +3. Clones repository +4. Add repository information to lock.json -* `"repos/git/fetch"` -* `"repos/git/reset"` +1 must raise an error! +The problem is that `["$invert", ["do", exprs...]]` simply reverses the `exprs`. +We have to install **always** the repository to vim directory after all +expressions. -## JSON DSL API +This is what we expected. -TODO +```json +["vimdir/with-install", + ["github.com/tyru/caw.vim"], + ["do", + ["lockjson/remove", + { + "type": "git", + "path": "github.com/tyru/caw.vim", + "version": "deadbeefcafebabe" + }, + ["default"] + ], + ["repos/delete", "github.com/tyru/caw.vim"], + ["plugconf/delete", "github.com/tyru/caw.vim"]]] +``` -## Go API +The inverse expression of the above is: -TODO +```json +["vimdir/with-install", + ["github.com/tyru/caw.vim"], + ["do", + ["plugconf/install", "github.com/tyru/caw.vim"], + ["repos/get", "github.com/tyru/caw.vim"], + ["lockjson/add", + { + "type": "git", + "path": "github.com/tyru/caw.vim", + "version": "deadbeefcafebabe" + }, + ["default"]]]] +``` + +1. Installs plugconf +2. Clones repository +3. Add repository information to lock.json +4. Installs the repository to vim directory (yes!) + +We successfully installs [tyru/caw.vim](https://github.com/tyru/caw.vim) +plugin :) + +But, of course if we placed `vimdir/with-install` at before `repos/delete` or +`plugconf/delete` not at top-level. + +```json +["do", + ["vimdir/with-install", + ["github.com/tyru/caw.vim"], + "dummy"], + ["lockjson/remove", + { + "type": "git", + "path": "github.com/tyru/caw.vim", + "version": "deadbeefcafebabe" + }, + ["default"] + ], + ["repos/delete", "github.com/tyru/caw.vim"], + ["plugconf/delete", "github.com/tyru/caw.vim"]] +``` + +```json +["do", + ["plugconf/install", "github.com/tyru/caw.vim"], + ["repos/get", "github.com/tyru/caw.vim"], + ["lockjson/add", + { + "type": "git", + "path": "github.com/tyru/caw.vim", + "version": "deadbeefcafebabe" + }, + ["default"]], + ["vimdir/with-install", + ["github.com/tyru/caw.vim"], + "dummy"]] +``` + +But a user does not touch JSON DSL. In other words, constructing "wrong" AST +must not occur without Volt's bug. + +### Install examples + +Here is the simple JSON to install +[tyru/caw.vim](https://github.com/tyru/caw.vim) using Git. + +```json +["vimdir/with-install", + ["do", + ["lockjson/add", + ["repos/get", "github.com/tyru/caw.vim"], + ["default"]], + ["plugconf/install", "github.com/tyru/caw.vim"]]] +``` + +Here is the inverse expression of above. + +```json +["vimdir/with-install", + ["do", + ["plugconf/delete", "github.com/tyru/caw.vim"], + ["lockjson/remove", + ["repos/delete", "github.com/tyru/caw.vim"], + ["default"]]]] +``` + +Here is the JSON to install plugins from local directory (static repository). + +```json +["vimdir/with-install", + ["lockjson/add", + { ... (repository information of local directory) ... }, + ["default"]], + ["plugconf/install", "localhost/local/myplugin"]] +``` + +Here is the inverse expression of above. + +```json +["vimdir/with-install", + ["plugconf/delete", "localhost/local/myplugin"], + ["lockjson/remove", + { ... (repository information of local directory) ... }, + ["default"]]] +``` + +### Label examples + +Here is the simple example of installing +[tyru/caw.vim](https://github.com/tyru/caw.vim) plugin. + +```json +["label", + 0, + "installing github.com/caw.vim...", + ["vimdir/with-install", + ["do", + ["lockjson/add", + ["repos/get", "github.com/tyru/caw.vim"], + ["default"]], + ["plugconf/install", "github.com/tyru/caw.vim"]]]] +``` + +Here is the inverse expression of above. +Note that: + +* Message becomes `revert "%s"` +* `$invert` is applied to the third argument, thus the argument of `do` is + inverted + +```json +["label", + 0, + "revert \"installing github.com/caw.vim...\"", + ["vimdir/with-install", + ["do", + ["plugconf/install", "github.com/tyru/caw.vim"], + ["lockjson/add", + ["repos/get", "github.com/tyru/caw.vim"], + ["default"]]]]] +``` + +Here is more complex example to install two plugins "tyru/open-browser.vim", +"tyru/open-browser-github.vim". Two levels of `label` expression exist. + +```json +["label", + "installing plugins:", + ["vimdir/with-install", + ["parallel", + ["label", + " github.com/tyru/open-browser.vim ... {{if .Done}}done!{{end}}", + ["parallel", + ["lockjson/add", + ["repos/get", "github.com/tyru/open-browser.vim"], + ["default"]], + ["plugconf/install", "github.com/tyru/open-browser.vim"]]], + ["label", + " github.com/tyru/open-browser-github.vim ... {{if .Done}}done!{{end}}", + ["parallel", + ["lockjson/add", + ["repos/get", "github.com/tyru/open-browser-github.vim"], + ["default"]], + ["plugconf/install", "github.com/tyru/open-browser-github.vim"]]]]]] +``` + +### Go API + +* dsl package + * `Execute(Context, Expr) (val Value, rollback func(), err error)` + * Executes in new transacton. + * In the given context, if the following keys are missing, returns an error. + * lock.json information `*lockjson.LockJSON` + * config.toml information `*config.Config` + * And this function sets the below key before execution. + * Transaction ID: assigns `max + 1` + +* dsl/types package + * Value interface + * `Eval(Context) (val Value, rollback func(), err error)` + * Evaluate this value. the value of JSON literal just returns itself as-is. + * `Invert() (Value, error)` + * Returns inverse expression. + * `Describe() []string` + * TODO: remove + * Null struct + * implements Value + * NullType Type = 1 + * var NullValue Null + * Bool struct + * implements Value + * BoolType Type = 2 + * var TrueValue Bool + * var FalseValue Bool + * Number struct + * implements Value + * NumberType Type = 3 + * String struct + * implements Value + * StringType Type = 4 + * Array struct + * implements Value + * ArrayType Type = 5 + * Object struct + * implements Value + * ObjectType Type = 6 + * Expr struct + * implements Value + * Op Op + * Args []Value + * Type Type + +* dsl/op package + * `signature(types Type...) *sigChecker` + * `sigChecker.check(args []Value) error` + * Returns nil if the types of args match. + * Returns non-nil error otherwise. + * Op interface + * `Bind(args []Value...) (*Expr, error)` + * Binds arguments to this operator, and returns an expression. + * Checks semantics (type) of each argument. + * Sets the result type of Expr. + * `InvertExpr(args []Value) (*Expr, error)` + * Returns inverse expression. This normally inverts operator and arguments + and call Bind() (not all operators do it). + * `Describe(args []Value) []string` + * TODO: remove + * `Execute(ctx Context, args []Value) (val Value, rollback func(), err error)` + * Executes expression (operator + args). From 9e7c9174884e1648f70fd4e8f6fb24ebff92a59a Mon Sep 17 00:00:00 2001 From: tyru Date: Wed, 18 Apr 2018 22:26:05 +0900 Subject: [PATCH 04/79] feat: implement "@" operator --- _docs/json-dsl.md | 65 ++++++++++++++++++++-------------------- dsl/deparse.go | 58 ++++++++++++++++++++++++++++++++++++ dsl/op/array.go | 48 ++++++++++++++++++++++++++++++ dsl/op/do.go | 30 +++++++++++-------- dsl/parse.go | 75 ++++++++++++++++++++++------------------------- dsl/types/expr.go | 22 ++++---------- dsl/types/op.go | 5 ++-- 7 files changed, 200 insertions(+), 103 deletions(-) create mode 100644 dsl/deparse.go create mode 100644 dsl/op/array.go diff --git a/_docs/json-dsl.md b/_docs/json-dsl.md index 72a145bb..27a11150 100644 --- a/_docs/json-dsl.md +++ b/_docs/json-dsl.md @@ -15,14 +15,14 @@ ["parallel", ["lockjson/add", ["repos/get", "github.com/tyru/open-browser.vim"], - ["default"]], + ["@", "default"]], ["plugconf/install", "github.com/tyru/open-browser.vim"]]], ["label", " github.com/tyru/open-browser-github.vim ... {{if .Done}}done!{{end}}", ["parallel", ["lockjson/add", ["repos/get", "github.com/tyru/open-browser-github.vim"], - ["default"]], + ["@", "default"]], ["plugconf/install", "github.com/tyru/open-browser-github.vim"]]]]]] ``` @@ -93,9 +93,16 @@ JSON DSL is a S-expression like DSL represented as JSON format. ["op", "arg1", "arg2"] ``` -This is an application form (called "expression" in this note) when `op` is a -known operator name. But if `op` is not a known operator, it is just an array -literal value. Each expression has 0 or more parameters. And evaluation +This is an application form (called "expression" in this note). +An array literal value is written using `@` operator. + +```json +["@", 1, 2, 3] +``` + +This expression is evaluated to `[1, 2, 3]`. + +Each expression has 0 or more parameters. And evaluation strategy is a non-strict evaluation. Parameter types are @@ -162,7 +169,7 @@ version for easiness). ["do", ["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], - ["default"]], + ["@", "default"]], ["plugconf/install", "github.com/tyru/caw.vim"]]] ``` @@ -177,7 +184,7 @@ At first, to invert the expression, `$invert` macro is used: ["do", ["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], - ["default"]], + ["@", "default"]], ["plugconf/install", "github.com/tyru/caw.vim"]]]] ``` @@ -192,7 +199,7 @@ API" section of JSONDSL note (TODO). ["do", ["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], - ["default"]], + ["@", "default"]], ["plugconf/install", "github.com/tyru/caw.vim"]]]] ``` @@ -208,7 +215,7 @@ Note that `expr1` and `expr2` becomes reversed order. ["$invert", ["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], - ["default"]]]]] + ["@", "default"]]]]] ``` And @@ -223,7 +230,7 @@ And ["plugconf/delete", ["$invert", "github.com/tyru/caw.vim"]], ["lockjson/remove", ["$invert", ["repos/get", "github.com/tyru/caw.vim"]], - ["$invert", ["default"]]]]] + ["$invert", ["@", "default"]]]]] ``` `["$invert", ["repos/get", path]]` becomes @@ -235,7 +242,7 @@ And ["plugconf/delete", ["$invert", "github.com/tyru/caw.vim"]], ["lockjson/remove", ["repos/delete", ["$invert", "github.com/tyru/caw.vim"]], - ["$invert", ["default"]]]]] + ["$invert", ["@", "default"]]]]] ``` And if `$invert` is applied to literals like string, JSON array, it just remains @@ -247,7 +254,7 @@ as-is. ["plugconf/delete", "github.com/tyru/caw.vim"], ["lockjson/remove", ["repos/delete", "github.com/tyru/caw.vim"], - ["default"]]]] + ["@", "default"]]]] ``` We can successfully evaluate the inverse expression of the first expression :) @@ -421,7 +428,7 @@ Macros are not saved in transaction log (expanded before saving). * It fails if specified profile name does not exist. * Need to create profile before using `lockjson/profile/add`. * e.g. - * `["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], ["default"]]` + * `["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], ["@", "default"]]` * `["$invert", ["lockjson/add", repos, profiles]]` = `["lockjson/remove", ["$invert", repos], ["$invert", profiles]]` * `["lockjson/profile/add", name string] Profile` @@ -484,7 +491,7 @@ directory: "path": "github.com/tyru/caw.vim", "version": "deadbeefcafebabe" }, - ["default"] + ["@", "default"] ], ["repos/delete", "github.com/tyru/caw.vim"], ["plugconf/delete", "github.com/tyru/caw.vim"], @@ -506,7 +513,7 @@ And below is the inverse expression of above. "path": "github.com/tyru/caw.vim", "version": "deadbeefcafebabe" }, - ["default"] + ["@", "default"] ] ] ``` @@ -534,7 +541,7 @@ This is what we expected. "path": "github.com/tyru/caw.vim", "version": "deadbeefcafebabe" }, - ["default"] + ["@", "default"] ], ["repos/delete", "github.com/tyru/caw.vim"], ["plugconf/delete", "github.com/tyru/caw.vim"]]] @@ -554,7 +561,7 @@ The inverse expression of the above is: "path": "github.com/tyru/caw.vim", "version": "deadbeefcafebabe" }, - ["default"]]]] + ["@", "default"]]]] ``` 1. Installs plugconf @@ -579,7 +586,7 @@ But, of course if we placed `vimdir/with-install` at before `repos/delete` or "path": "github.com/tyru/caw.vim", "version": "deadbeefcafebabe" }, - ["default"] + ["@", "default"] ], ["repos/delete", "github.com/tyru/caw.vim"], ["plugconf/delete", "github.com/tyru/caw.vim"]] @@ -595,7 +602,7 @@ But, of course if we placed `vimdir/with-install` at before `repos/delete` or "path": "github.com/tyru/caw.vim", "version": "deadbeefcafebabe" }, - ["default"]], + ["@", "default"]], ["vimdir/with-install", ["github.com/tyru/caw.vim"], "dummy"]] @@ -614,7 +621,7 @@ Here is the simple JSON to install ["do", ["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], - ["default"]], + ["@", "default"]], ["plugconf/install", "github.com/tyru/caw.vim"]]] ``` @@ -626,7 +633,7 @@ Here is the inverse expression of above. ["plugconf/delete", "github.com/tyru/caw.vim"], ["lockjson/remove", ["repos/delete", "github.com/tyru/caw.vim"], - ["default"]]]] + ["@", "default"]]]] ``` Here is the JSON to install plugins from local directory (static repository). @@ -635,7 +642,7 @@ Here is the JSON to install plugins from local directory (static repository). ["vimdir/with-install", ["lockjson/add", { ... (repository information of local directory) ... }, - ["default"]], + ["@", "default"]], ["plugconf/install", "localhost/local/myplugin"]] ``` @@ -646,7 +653,7 @@ Here is the inverse expression of above. ["plugconf/delete", "localhost/local/myplugin"], ["lockjson/remove", { ... (repository information of local directory) ... }, - ["default"]]] + ["@", "default"]]] ``` ### Label examples @@ -662,7 +669,7 @@ Here is the simple example of installing ["do", ["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], - ["default"]], + ["@", "default"]], ["plugconf/install", "github.com/tyru/caw.vim"]]]] ``` @@ -682,7 +689,7 @@ Note that: ["plugconf/install", "github.com/tyru/caw.vim"], ["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], - ["default"]]]]] + ["@", "default"]]]]] ``` Here is more complex example to install two plugins "tyru/open-browser.vim", @@ -698,14 +705,14 @@ Here is more complex example to install two plugins "tyru/open-browser.vim", ["parallel", ["lockjson/add", ["repos/get", "github.com/tyru/open-browser.vim"], - ["default"]], + ["@", "default"]], ["plugconf/install", "github.com/tyru/open-browser.vim"]]], ["label", " github.com/tyru/open-browser-github.vim ... {{if .Done}}done!{{end}}", ["parallel", ["lockjson/add", ["repos/get", "github.com/tyru/open-browser-github.vim"], - ["default"]], + ["@", "default"]], ["plugconf/install", "github.com/tyru/open-browser-github.vim"]]]]]] ``` @@ -726,8 +733,6 @@ Here is more complex example to install two plugins "tyru/open-browser.vim", * Evaluate this value. the value of JSON literal just returns itself as-is. * `Invert() (Value, error)` * Returns inverse expression. - * `Describe() []string` - * TODO: remove * Null struct * implements Value * NullType Type = 1 @@ -768,7 +773,5 @@ Here is more complex example to install two plugins "tyru/open-browser.vim", * `InvertExpr(args []Value) (*Expr, error)` * Returns inverse expression. This normally inverts operator and arguments and call Bind() (not all operators do it). - * `Describe(args []Value) []string` - * TODO: remove * `Execute(ctx Context, args []Value) (val Value, rollback func(), err error)` * Executes expression (operator + args). diff --git a/dsl/deparse.go b/dsl/deparse.go new file mode 100644 index 00000000..b4c297e3 --- /dev/null +++ b/dsl/deparse.go @@ -0,0 +1,58 @@ +package dsl + +import ( + "encoding/json" + "fmt" + + "github.com/vim-volt/volt/dsl/op" + "github.com/vim-volt/volt/dsl/types" +) + +// Deparse deparses *types.Expr. +// ["@", 1, 2, 3] becomes [1, 2, 3] +func Deparse(expr *types.Expr) (interface{}, error) { + value, err := deparse(expr) + if err != nil { + return nil, err + } + return json.Marshal(value) +} + +func deparse(value types.Value) (interface{}, error) { + switch val := value.(type) { + case *types.Null: + return nil, nil + case *types.Bool: + return val.Value, nil + case *types.String: + return val.Value, nil + case *types.Number: + return val.Value, nil + case *types.Object: + m := make(map[string]interface{}, len(val.Value)) + for k, o := range val.Value { + v, err := deparse(o) + if err != nil { + return nil, err + } + m[k] = v + } + return m, nil + case *types.Expr: + a := make([]interface{}, 0, len(val.Args)+1) + // Do not include "@" in array literal + if val.Op.String() != op.ArrayOp.String() { + a = append(a, &types.String{Value: val.Op.String()}) + } + for i := range a { + v, err := deparse(val.Args[i]) + if err != nil { + return nil, err + } + a = append(a, v) + } + return a, nil + default: + return nil, fmt.Errorf("unknown value was given '%+v'", val) + } +} diff --git a/dsl/op/array.go b/dsl/op/array.go new file mode 100644 index 00000000..7daccb11 --- /dev/null +++ b/dsl/op/array.go @@ -0,0 +1,48 @@ +package op + +import ( + "context" + + "github.com/vim-volt/volt/dsl/types" +) + +func init() { + opsMap[string(ArrayOp)] = &ArrayOp +} + +type arrayOp string + +// ArrayOp is "@" operation +var ArrayOp arrayOp = "@" + +// String returns operator name +func (op *arrayOp) String() string { + return string(*op) +} + +// Bind binds its arguments +func (op *arrayOp) Bind(args ...types.Value) (*types.Expr, error) { + return &types.Expr{ + Op: &ArrayOp, + Args: args, + Typ: types.ArrayType, + }, nil +} + +// InvertExpr returns inverted expression +func (op *arrayOp) InvertExpr(args []types.Value) (*types.Expr, error) { + newargs := make([]types.Value, 0, len(args)) + for i := range args { + a, err := args[i].Invert() + if err != nil { + return nil, err + } + newargs = append(newargs, a) + } + return ArrayOp.Bind(newargs...) +} + +// Execute executes "@" operation +func (op *arrayOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { + return &types.Array{Value: args}, func() {}, nil +} diff --git a/dsl/op/do.go b/dsl/op/do.go index c56d5faa..68fce813 100644 --- a/dsl/op/do.go +++ b/dsl/op/do.go @@ -7,20 +7,21 @@ import ( ) func init() { - opsMap["do"] = &DoOp{} + opsMap[string(DoOp)] = &DoOp } +type doOp string + // DoOp is "do" operation -type DoOp struct{} +var DoOp doOp = "do" -// Describe describes its task(s) as zero or more lines of messages. -func (op *DoOp) Describe(args []types.Value) []string { - // TODO - return []string{} +// String returns operator name +func (op *doOp) String() string { + return string(*op) } // Bind binds its arguments, and check if the types of values are correct. -func (op *DoOp) Bind(args ...types.Value) (*types.Expr, error) { +func (op *doOp) Bind(args ...types.Value) (*types.Expr, error) { sig := make([]types.Type, 0, len(args)) for i := 0; i < len(args); i++ { sig = append(sig, types.ArrayType) @@ -29,26 +30,29 @@ func (op *DoOp) Bind(args ...types.Value) (*types.Expr, error) { return nil, err } retType := args[len(args)-1].Type() - return types.NewExpr(op, args, retType), nil + return &types.Expr{ + Op: &DoOp, + Args: args, + Typ: retType, + }, nil } // InvertExpr returns inverted expression: Call Value.Invert() for each argument, // and reverse arguments order. -func (op *DoOp) InvertExpr(args []types.Value) (*types.Expr, error) { +func (op *doOp) InvertExpr(args []types.Value) (*types.Expr, error) { newargs := make([]types.Value, len(args)) - newargs[0] = args[0] // message - for i := 1; i < len(args); i++ { + for i := range args { a, err := args[i].Invert() if err != nil { return nil, err } newargs[len(args)-i] = a } - return op.Bind(newargs...) + return DoOp.Bind(newargs...) } // Execute executes "do" operation -func (op *DoOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { +func (op *doOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { // TODO return nil, func() {}, nil } diff --git a/dsl/parse.go b/dsl/parse.go index 602aa110..4123d5ed 100644 --- a/dsl/parse.go +++ b/dsl/parse.go @@ -9,46 +9,49 @@ import ( "github.com/vim-volt/volt/dsl/types" ) -// ParseOp parses expr JSON. -// This calls Parse() function. And -// 1. Does split to operation and its arguments -// 2. Analyzes semantically its arguments recursively -// 3. Convert the result value to *Expr -func ParseOp(expr []byte) (*types.Expr, error) { - value, err := Parse(expr) - if err != nil { +// Parse parses expr JSON. And if an array literal value is found: +// 1. Split to operation and its arguments +// 2. Do semantic analysis recursively for its arguments +// 3. Convert to *Expr +func Parse(expr []byte) (types.Value, error) { + var value interface{} + if err := json.Unmarshal(expr, value); err != nil { return nil, err } - array, ok := value.(*types.Array) - if !ok { - return nil, errors.New("top-level value is not an array") + return parseExpr(value) +} + +func parseExpr(value interface{}) (*types.Expr, error) { + if array, ok := value.([]interface{}); ok { + return parseArray(array) } - if len(array.Value) == 0 { + return nil, errors.New("top-level must be an array") +} + +func parseArray(array []interface{}) (*types.Expr, error) { + if len(array) == 0 { return nil, errors.New("expected operation but got an empty array") } - opName, ok := array.Value[0].(*types.String) + opName, ok := array[0].(string) if !ok { - return nil, fmt.Errorf("expected operation name but got '%+v'", array.Value[0]) + return nil, fmt.Errorf("expected operator (string) but got '%+v'", array[0]) } - op, exists := op.Lookup(opName.Value) - if !exists { - return nil, fmt.Errorf("no such operation '%s'", opName.Value) + a := make([]types.Value, 0, len(array)-1) + for i := 1; i < len(array); i++ { + v, err := parse(array[i]) + if err != nil { + return nil, err + } + a = append(a, v) } - args := array.Value[1:] - return op.Bind(args...) -} - -// Parse parses expr JSON. -// This only maps encoding/json's types to Value types. -func Parse(expr []byte) (types.Value, error) { - var value interface{} - if err := json.Unmarshal(expr, value); err != nil { - return nil, err + op, exists := op.Lookup(opName) + if !exists { + return nil, fmt.Errorf("no such operation '%s'", opName) } - return convert(value) + return op.Bind(a...) } -func convert(value interface{}) (types.Value, error) { +func parse(value interface{}) (types.Value, error) { switch val := value.(type) { case nil: return types.NullValue, nil @@ -61,26 +64,18 @@ func convert(value interface{}) (types.Value, error) { return &types.String{val}, nil case float64: return &types.Number{val}, nil - case []interface{}: - a := make([]types.Value, 0, len(val)) - for o := range a { - v, err := convert(o) - if err != nil { - return nil, err - } - a = append(a, v) - } - return &types.Array{a}, nil case map[string]interface{}: m := make(map[string]types.Value, len(val)) for k, o := range m { - v, err := convert(o) + v, err := parse(o) if err != nil { return nil, err } m[k] = v } return &types.Object{m}, nil + case []interface{}: + return parseArray(val) default: return nil, fmt.Errorf("unknown value was given '%+v'", val) } diff --git a/dsl/types/expr.go b/dsl/types/expr.go index a56e5790..306fce6b 100644 --- a/dsl/types/expr.go +++ b/dsl/types/expr.go @@ -4,14 +4,9 @@ import "context" // Expr has an operation and its arguments type Expr struct { - op Op - args []Value - typ Type -} - -// NewExpr is the constructor of Expr -func NewExpr(op Op, args []Value, typ Type) *Expr { - return &Expr{op: op, args: args, typ: typ} + Op Op + Args []Value + Typ Type } // TrxID is a transaction ID, which is a serial number and directory name of @@ -21,21 +16,16 @@ type TrxID int64 // Eval evaluates given expression expr with given transaction ID trxID. func (expr *Expr) Eval(ctx context.Context) (val Value, rollback func(), err error) { - return expr.op.Execute(ctx, expr.args) + return expr.Op.Execute(ctx, expr.Args) } // Invert inverts this expression. // This just calls Op.InvertExpr() with arguments. func (expr *Expr) Invert() (Value, error) { - return expr.op.InvertExpr(expr.args) -} - -// Describe describes its task(s) as zero or more lines of messages. -func (expr *Expr) Describe() []string { - return expr.op.Describe(expr.args) + return expr.Op.InvertExpr(expr.Args) } // Type returns the type. func (expr *Expr) Type() Type { - return expr.typ + return expr.Typ } diff --git a/dsl/types/op.go b/dsl/types/op.go index 4c044367..40b67fa1 100644 --- a/dsl/types/op.go +++ b/dsl/types/op.go @@ -13,7 +13,6 @@ type Op interface { // Execute executes this operation and returns its result value and error Execute(ctx context.Context, args []Value) (ret Value, rollback func(), err error) - // Describe returns its type(s) of zero or more arguments and one return value. - // The types are used for type-checking. - Describe(args []Value) []string + // Returns operator name + String() string } From 3dd15e886aee758e94cb738e742cb3b4c39c1484 Mon Sep 17 00:00:00 2001 From: tyru Date: Thu, 19 Apr 2018 00:03:49 +0900 Subject: [PATCH 05/79] feat: split Op interface to Macro,Func --- dsl/deparse.go | 4 ++-- dsl/op/array.go | 36 ++++++------------------------------ dsl/op/do.go | 14 +++++++------- dsl/op/lookup.go | 20 +++++++++++++++----- dsl/parse.go | 40 ++++++++++++++++++++++++---------------- dsl/types/expr.go | 8 ++++---- dsl/types/op.go | 19 ++++++++++++++----- 7 files changed, 72 insertions(+), 69 deletions(-) diff --git a/dsl/deparse.go b/dsl/deparse.go index b4c297e3..c23fca57 100644 --- a/dsl/deparse.go +++ b/dsl/deparse.go @@ -41,8 +41,8 @@ func deparse(value types.Value) (interface{}, error) { case *types.Expr: a := make([]interface{}, 0, len(val.Args)+1) // Do not include "@" in array literal - if val.Op.String() != op.ArrayOp.String() { - a = append(a, &types.String{Value: val.Op.String()}) + if val.Func.String() != op.ArrayOp.String() { + a = append(a, &types.String{Value: val.Func.String()}) } for i := range a { v, err := deparse(val.Args[i]) diff --git a/dsl/op/array.go b/dsl/op/array.go index 7daccb11..beed69a1 100644 --- a/dsl/op/array.go +++ b/dsl/op/array.go @@ -1,13 +1,11 @@ package op import ( - "context" - "github.com/vim-volt/volt/dsl/types" ) func init() { - opsMap[string(ArrayOp)] = &ArrayOp + macroMap[string(ArrayOp)] = &ArrayOp } type arrayOp string @@ -15,34 +13,12 @@ type arrayOp string // ArrayOp is "@" operation var ArrayOp arrayOp = "@" -// String returns operator name -func (op *arrayOp) String() string { - return string(*op) -} - -// Bind binds its arguments -func (op *arrayOp) Bind(args ...types.Value) (*types.Expr, error) { - return &types.Expr{ - Op: &ArrayOp, - Args: args, - Typ: types.ArrayType, - }, nil -} - -// InvertExpr returns inverted expression -func (op *arrayOp) InvertExpr(args []types.Value) (*types.Expr, error) { - newargs := make([]types.Value, 0, len(args)) - for i := range args { - a, err := args[i].Invert() - if err != nil { - return nil, err - } - newargs = append(newargs, a) - } - return ArrayOp.Bind(newargs...) +// String returns "@" +func (*arrayOp) String() string { + return string(ArrayOp) } // Execute executes "@" operation -func (op *arrayOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { - return &types.Array{Value: args}, func() {}, nil +func (*arrayOp) Expand(args []types.Value) types.Value { + return &types.Array{Value: args} } diff --git a/dsl/op/do.go b/dsl/op/do.go index 68fce813..f2c95571 100644 --- a/dsl/op/do.go +++ b/dsl/op/do.go @@ -7,7 +7,7 @@ import ( ) func init() { - opsMap[string(DoOp)] = &DoOp + funcMap[string(DoOp)] = &DoOp } type doOp string @@ -16,12 +16,12 @@ type doOp string var DoOp doOp = "do" // String returns operator name -func (op *doOp) String() string { - return string(*op) +func (*doOp) String() string { + return string(DoOp) } // Bind binds its arguments, and check if the types of values are correct. -func (op *doOp) Bind(args ...types.Value) (*types.Expr, error) { +func (*doOp) Bind(args ...types.Value) (*types.Expr, error) { sig := make([]types.Type, 0, len(args)) for i := 0; i < len(args); i++ { sig = append(sig, types.ArrayType) @@ -31,7 +31,7 @@ func (op *doOp) Bind(args ...types.Value) (*types.Expr, error) { } retType := args[len(args)-1].Type() return &types.Expr{ - Op: &DoOp, + Func: &DoOp, Args: args, Typ: retType, }, nil @@ -39,7 +39,7 @@ func (op *doOp) Bind(args ...types.Value) (*types.Expr, error) { // InvertExpr returns inverted expression: Call Value.Invert() for each argument, // and reverse arguments order. -func (op *doOp) InvertExpr(args []types.Value) (*types.Expr, error) { +func (*doOp) InvertExpr(args []types.Value) (*types.Expr, error) { newargs := make([]types.Value, len(args)) for i := range args { a, err := args[i].Invert() @@ -52,7 +52,7 @@ func (op *doOp) InvertExpr(args []types.Value) (*types.Expr, error) { } // Execute executes "do" operation -func (op *doOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { +func (*doOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { // TODO return nil, func() {}, nil } diff --git a/dsl/op/lookup.go b/dsl/op/lookup.go index ef318243..5d15a5d3 100644 --- a/dsl/op/lookup.go +++ b/dsl/op/lookup.go @@ -2,12 +2,22 @@ package op import "github.com/vim-volt/volt/dsl/types" -// opsMap holds all operation structs. +// funcMap holds all operation structs. // All operations in dsl/op/*.go sets its struct to this in init() -var opsMap map[string]types.Op +var funcMap map[string]types.Func -// Lookup looks up operation name -func Lookup(name string) (types.Op, bool) { - op, exists := opsMap[name] +// LookupFunc looks up function name +func LookupFunc(name string) (types.Func, bool) { + op, exists := funcMap[name] + return op, exists +} + +// macroMap holds all operation structs. +// All operations in dsl/op/*.go sets its struct to this in init() +var macroMap map[string]types.Macro + +// LookupMacro looks up macro name +func LookupMacro(name string) (types.Macro, bool) { + op, exists := macroMap[name] return op, exists } diff --git a/dsl/parse.go b/dsl/parse.go index 4123d5ed..5adb60f0 100644 --- a/dsl/parse.go +++ b/dsl/parse.go @@ -13,22 +13,28 @@ import ( // 1. Split to operation and its arguments // 2. Do semantic analysis recursively for its arguments // 3. Convert to *Expr -func Parse(expr []byte) (types.Value, error) { +func Parse(content []byte) (types.Value, error) { var value interface{} - if err := json.Unmarshal(expr, value); err != nil { + if err := json.Unmarshal(content, value); err != nil { return nil, err } - return parseExpr(value) -} - -func parseExpr(value interface{}) (*types.Expr, error) { - if array, ok := value.([]interface{}); ok { - return parseArray(array) + array, ok := value.([]interface{}) + if !ok { + return nil, errors.New("top-level must be an array") } - return nil, errors.New("top-level must be an array") + expr, err := parseArray(array) + if err != nil { + return nil, err + } + // If expression's operator is a macro, return value may not be an array + // (e.g. ["macro", 1, 2]) + if _, ok := expr.(*types.Expr); !ok { + return nil, errors.New("the result must be an expression") + } + return expr, nil } -func parseArray(array []interface{}) (*types.Expr, error) { +func parseArray(array []interface{}) (types.Value, error) { if len(array) == 0 { return nil, errors.New("expected operation but got an empty array") } @@ -36,19 +42,21 @@ func parseArray(array []interface{}) (*types.Expr, error) { if !ok { return nil, fmt.Errorf("expected operator (string) but got '%+v'", array[0]) } - a := make([]types.Value, 0, len(array)-1) + args := make([]types.Value, 0, len(array)-1) for i := 1; i < len(array); i++ { v, err := parse(array[i]) if err != nil { return nil, err } - a = append(a, v) + args = append(args, v) + } + if macro, exists := op.LookupMacro(opName); exists { + return macro.Expand(args), nil } - op, exists := op.Lookup(opName) - if !exists { - return nil, fmt.Errorf("no such operation '%s'", opName) + if fn, exists := op.LookupFunc(opName); exists { + return fn.Bind(args...) } - return op.Bind(a...) + return nil, fmt.Errorf("no such operation '%s'", opName) } func parse(value interface{}) (types.Value, error) { diff --git a/dsl/types/expr.go b/dsl/types/expr.go index 306fce6b..d74a82cc 100644 --- a/dsl/types/expr.go +++ b/dsl/types/expr.go @@ -4,7 +4,7 @@ import "context" // Expr has an operation and its arguments type Expr struct { - Op Op + Func Func Args []Value Typ Type } @@ -16,13 +16,13 @@ type TrxID int64 // Eval evaluates given expression expr with given transaction ID trxID. func (expr *Expr) Eval(ctx context.Context) (val Value, rollback func(), err error) { - return expr.Op.Execute(ctx, expr.Args) + return expr.Func.Execute(ctx, expr.Args) } // Invert inverts this expression. -// This just calls Op.InvertExpr() with arguments. +// This just calls Func.InvertExpr() with arguments. func (expr *Expr) Invert() (Value, error) { - return expr.Op.InvertExpr(expr.Args) + return expr.Func.InvertExpr(expr.Args) } // Type returns the type. diff --git a/dsl/types/op.go b/dsl/types/op.go index 40b67fa1..a5a5d126 100644 --- a/dsl/types/op.go +++ b/dsl/types/op.go @@ -2,17 +2,26 @@ package types import "context" -// Op is an operation of JSON DSL -type Op interface { - // Bind binds its arguments, and check if the types of values are correct. - Bind(args ...Value) (*Expr, error) +// Func is an operation of JSON DSL +type Func interface { + // Returns function name + String() string // InvertExpr returns inverted expression InvertExpr(args []Value) (*Expr, error) + // Bind binds its arguments, and check if the types of values are correct. + Bind(args ...Value) (*Expr, error) + // Execute executes this operation and returns its result value and error Execute(ctx context.Context, args []Value) (ret Value, rollback func(), err error) +} - // Returns operator name +// Macro is an operation of JSON DSL +type Macro interface { + // Returns macro name String() string + + // Execute executes this operation and returns its result value and error + Expand(args []Value) Value } From 2b524ebfc9e66ca31e796b8f14d53a0c277955b7 Mon Sep 17 00:00:00 2001 From: tyru Date: Thu, 19 Apr 2018 20:54:21 +0900 Subject: [PATCH 06/79] doc: add linenum argument to label, and so on --- _docs/json-dsl.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/_docs/json-dsl.md b/_docs/json-dsl.md index 27a11150..098e4369 100644 --- a/_docs/json-dsl.md +++ b/_docs/json-dsl.md @@ -7,10 +7,12 @@ ```json ["label", + 1, "installing plugins:", ["vimdir/with-install", ["parallel", ["label", + 2, " github.com/tyru/open-browser.vim ... {{if .Done}}done!{{end}}", ["parallel", ["lockjson/add", @@ -18,6 +20,7 @@ ["@", "default"]], ["plugconf/install", "github.com/tyru/open-browser.vim"]]], ["label", + 3, " github.com/tyru/open-browser-github.vim ... {{if .Done}}done!{{end}}", ["parallel", ["lockjson/add", @@ -28,11 +31,12 @@ ## Wordings -* operator: "function" of DSL +* operator: "callable" object of DSL. this is generic name of function and macro +* function: the name of process * e.g. "label" * e.g. "parallel" -* macro: like "function", but is expanded before execution - * see JSON DSL note (TODO) +* macro: like function, but is expanded before execution + * e.g. "@" * expression: the form of operator application * e.g. `["label", ...]` * e.g. `["parallel", ...]` @@ -138,7 +142,7 @@ JSON DSL has the following characteristic: All operators have an idempotency: "even if an expression is executed twice, it guarantees the existence (not the content) of a requested resource." -One also might think that "why the definition is so ambiguos?" Because, if we +One also might think that "why the it defines the existence, not content?" Because, if we define operator's idempotency as "after an expression was executed twice at different times, lock.json, filesystem must be the same." But `volt get A` installs the latest plugin of remote repository. At the first time and second @@ -191,7 +195,7 @@ At first, to invert the expression, `$invert` macro is used: `["$invert", ["vimdir/with-install", expr]]` is expanded to `["vimdir/with-install", ["$invert", expr]]`. Internally, it is implemented as calling `Invert()` method of `vimdir/with-install` operator struct. See "Go -API" section of JSONDSL note (TODO). +API" section. ```json ["vimdir/with-install", @@ -332,12 +336,11 @@ Macros are not saved in transaction log (expanded before saving). ### Basic operators -* `["label", tmpl string, expr Expr[* => R]] R` - * Render `tmpl` by text/template to progress bar using - [pgr](https://github.com/tyru/pgr/) library. +* `["label", linenum: number, tmpl string, expr Expr[* => R]] R` + * Render `tmpl` by text/template to `linenum` line (1-origin). Returns the evaluated value of `expr`. * e.g. - * `["$invert", ["label", "msg", expr]]` = `["label", "revert: \"msg\"", ["$invert", expr]]` + * `["$invert", ["label", linenum, "msg", expr]]` = `["label", ["$invert", linenum], "revert: \"msg\"", ["$invert", expr]]` * See `Label examples` section for more details * `["do", expr1 Expr[* => R1], ..., expr_last Expr[* => R2]] R2` @@ -663,7 +666,7 @@ Here is the simple example of installing ```json ["label", - 0, + 1, "installing github.com/caw.vim...", ["vimdir/with-install", ["do", @@ -682,7 +685,7 @@ Note that: ```json ["label", - 0, + 1, "revert \"installing github.com/caw.vim...\"", ["vimdir/with-install", ["do", @@ -697,10 +700,12 @@ Here is more complex example to install two plugins "tyru/open-browser.vim", ```json ["label", + 1, "installing plugins:", ["vimdir/with-install", ["parallel", ["label", + 2, " github.com/tyru/open-browser.vim ... {{if .Done}}done!{{end}}", ["parallel", ["lockjson/add", @@ -708,6 +713,7 @@ Here is more complex example to install two plugins "tyru/open-browser.vim", ["@", "default"]], ["plugconf/install", "github.com/tyru/open-browser.vim"]]], ["label", + 3, " github.com/tyru/open-browser-github.vim ... {{if .Done}}done!{{end}}", ["parallel", ["lockjson/add", From dbb9a30b1e1c59ee2c18b03a41cb111791b16987 Mon Sep 17 00:00:00 2001 From: tyru Date: Thu, 19 Apr 2018 21:20:05 +0900 Subject: [PATCH 07/79] refactor: change Macro.Expand() signature --- dsl/op/array.go | 4 ++-- dsl/parse.go | 2 +- dsl/types/json.go | 1 + dsl/types/op.go | 9 +++++---- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/dsl/op/array.go b/dsl/op/array.go index beed69a1..62b747fe 100644 --- a/dsl/op/array.go +++ b/dsl/op/array.go @@ -19,6 +19,6 @@ func (*arrayOp) String() string { } // Execute executes "@" operation -func (*arrayOp) Expand(args []types.Value) types.Value { - return &types.Array{Value: args} +func (*arrayOp) Expand(args []types.Value) (types.Value, error) { + return &types.Array{Value: args}, nil } diff --git a/dsl/parse.go b/dsl/parse.go index 5adb60f0..854049d1 100644 --- a/dsl/parse.go +++ b/dsl/parse.go @@ -51,7 +51,7 @@ func parseArray(array []interface{}) (types.Value, error) { args = append(args, v) } if macro, exists := op.LookupMacro(opName); exists { - return macro.Expand(args), nil + return macro.Expand(args) } if fn, exists := op.LookupFunc(opName); exists { return fn.Bind(args...) diff --git a/dsl/types/json.go b/dsl/types/json.go index 0962c246..bbf61488 100644 --- a/dsl/types/json.go +++ b/dsl/types/json.go @@ -7,6 +7,7 @@ type Value interface { // Invert returns inverted value/operation. // All type values are invertible. // Literals like string,number,... return itself as-is. + // If argument type or arity is different, this returns non-nil error. Invert() (Value, error) // Eval returns a evaluated value. diff --git a/dsl/types/op.go b/dsl/types/op.go index a5a5d126..add10f93 100644 --- a/dsl/types/op.go +++ b/dsl/types/op.go @@ -4,7 +4,7 @@ import "context" // Func is an operation of JSON DSL type Func interface { - // Returns function name + // String returns function name String() string // InvertExpr returns inverted expression @@ -19,9 +19,10 @@ type Func interface { // Macro is an operation of JSON DSL type Macro interface { - // Returns macro name + // String returns macro name String() string - // Execute executes this operation and returns its result value and error - Expand(args []Value) Value + // Expand expands this expression (operator + args). + // If argument type or arity is different, this returns non-nil error. + Expand(args []Value) (Value, error) } From b70ac44d4f5fd25201aa274799aee391c02d05b2 Mon Sep 17 00:00:00 2001 From: tyru Date: Thu, 19 Apr 2018 21:20:31 +0900 Subject: [PATCH 08/79] refactor: move Type constants to dsl/op/types.go --- dsl/types/json.go | 21 --------------------- dsl/types/types.go | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 21 deletions(-) create mode 100644 dsl/types/types.go diff --git a/dsl/types/json.go b/dsl/types/json.go index bbf61488..88f801d2 100644 --- a/dsl/types/json.go +++ b/dsl/types/json.go @@ -18,17 +18,11 @@ type Value interface { Type() Type } -// Type is a type of expression -type Type uint - // ================ Null ================ // Null is JSON null struct type Null struct{} -// NullType is JSON null type -const NullType Type = 1 - // NullValue is the JSON null value var NullValue = &Null{} @@ -54,9 +48,6 @@ type Bool struct { Value bool } -// BoolType is JSON boolean type -const BoolType Type = 2 - // Invert returns itself as-is. All literal types of JSON values are the same. func (v *Bool) Invert() (Value, error) { return v, nil @@ -85,9 +76,6 @@ type Number struct { Value float64 } -// NumberType is JSON number struct -const NumberType Type = 3 - // Invert returns itself as-is. All literal types of JSON values are the same. func (v *Number) Invert() (Value, error) { return v, nil @@ -110,9 +98,6 @@ type String struct { Value string } -// StringType is JSON string type -const StringType Type = 4 - // Invert returns itself as-is. All literal types of JSON values are the same. func (v *String) Invert() (Value, error) { return v, nil @@ -135,9 +120,6 @@ type Array struct { Value []Value } -// ArrayType is JSON array type -const ArrayType Type = 5 - // Invert returns itself as-is. All literal types of JSON values are the same. func (v *Array) Invert() (Value, error) { return v, nil @@ -160,9 +142,6 @@ type Object struct { Value map[string]Value } -// ObjectType is JSON object type -const ObjectType Type = 6 - // Invert returns itself as-is. All literal types of JSON values are the same. func (v *Object) Invert() (Value, error) { return v, nil diff --git a/dsl/types/types.go b/dsl/types/types.go new file mode 100644 index 00000000..7153610e --- /dev/null +++ b/dsl/types/types.go @@ -0,0 +1,19 @@ +package types + +// Type is a type of expression +type Type uint + +const ( + // NullType is JSON null type + NullType Type = iota + // BoolType is JSON boolean type + BoolType + // NumberType is JSON number struct + NumberType + // StringType is JSON string type + StringType + // ArrayType is JSON array type + ArrayType + // ObjectType is JSON object type + ObjectType +) From 756b2a83e8e8397fbccaa72d9f0a854841cb3a4b Mon Sep 17 00:00:00 2001 From: tyru Date: Thu, 19 Apr 2018 21:21:38 +0900 Subject: [PATCH 09/79] feat: add VoidType --- dsl/types/types.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dsl/types/types.go b/dsl/types/types.go index 7153610e..f4aa3b63 100644 --- a/dsl/types/types.go +++ b/dsl/types/types.go @@ -4,8 +4,10 @@ package types type Type uint const ( + // VoidType is void type + VoidType Type = iota // NullType is JSON null type - NullType Type = iota + NullType // BoolType is JSON boolean type BoolType // NumberType is JSON number struct From 6d1cdec258aa7c9f07fe07604c027579795f814f Mon Sep 17 00:00:00 2001 From: tyru Date: Thu, 19 Apr 2018 21:24:00 +0900 Subject: [PATCH 10/79] refactor: rename Expr.Typ to Expr.RetType --- dsl/op/do.go | 6 +++--- dsl/types/expr.go | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dsl/op/do.go b/dsl/op/do.go index f2c95571..673adb88 100644 --- a/dsl/op/do.go +++ b/dsl/op/do.go @@ -31,9 +31,9 @@ func (*doOp) Bind(args ...types.Value) (*types.Expr, error) { } retType := args[len(args)-1].Type() return &types.Expr{ - Func: &DoOp, - Args: args, - Typ: retType, + Func: &DoOp, + Args: args, + RetType: retType, }, nil } diff --git a/dsl/types/expr.go b/dsl/types/expr.go index d74a82cc..16639e44 100644 --- a/dsl/types/expr.go +++ b/dsl/types/expr.go @@ -4,9 +4,9 @@ import "context" // Expr has an operation and its arguments type Expr struct { - Func Func - Args []Value - Typ Type + Func Func + Args []Value + RetType Type } // TrxID is a transaction ID, which is a serial number and directory name of @@ -27,5 +27,5 @@ func (expr *Expr) Invert() (Value, error) { // Type returns the type. func (expr *Expr) Type() Type { - return expr.Typ + return expr.RetType } From 22105bb139d094da549c816bbad00a76df69394f Mon Sep 17 00:00:00 2001 From: tyru Date: Thu, 19 Apr 2018 22:13:54 +0900 Subject: [PATCH 11/79] feat: implement signature() function --- dsl/op/sigcheck.go | 22 ++++-- dsl/types/types.go | 164 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 162 insertions(+), 24 deletions(-) diff --git a/dsl/op/sigcheck.go b/dsl/op/sigcheck.go index 02885fa1..2fe44085 100644 --- a/dsl/op/sigcheck.go +++ b/dsl/op/sigcheck.go @@ -1,16 +1,28 @@ package op -import "github.com/vim-volt/volt/dsl/types" +import ( + "fmt" -func signature(sig ...types.Type) *sigChecker { - return &sigChecker{sig} + "github.com/vim-volt/volt/dsl/types" +) + +func signature(argTypes ...types.Type) *sigChecker { + return &sigChecker{argTypes: argTypes} } type sigChecker struct { - sig []types.Type + argTypes []types.Type } func (sc *sigChecker) check(args []types.Value) error { - // TODO + if len(args) != len(sc.argTypes) { + return fmt.Errorf("expected %d arity but got %d", len(sc.argTypes), len(args)) + } + for i := range sc.argTypes { + if !args[i].Type().InstanceOf(sc.argTypes[i]) { + return fmt.Errorf("expected %s instance but got %s", + sc.argTypes[i].String(), args[i].Type().String()) + } + } return nil } diff --git a/dsl/types/types.go b/dsl/types/types.go index f4aa3b63..67bca594 100644 --- a/dsl/types/types.go +++ b/dsl/types/types.go @@ -1,21 +1,147 @@ package types -// Type is a type of expression -type Type uint - -const ( - // VoidType is void type - VoidType Type = iota - // NullType is JSON null type - NullType - // BoolType is JSON boolean type - BoolType - // NumberType is JSON number struct - NumberType - // StringType is JSON string type - StringType - // ArrayType is JSON array type - ArrayType - // ObjectType is JSON object type - ObjectType -) +// Type is a type of a value +type Type interface { + // String returns a string like "" + String() string + + // InstanceOf checks has-a relation with its argument type + InstanceOf(Type) bool +} + +// ===================== VoidType ===================== // + +// VoidType is void type +type VoidType struct{} + +func (*VoidType) String() string { + return "Void" +} + +// InstanceOf returns true if t is VoidType +func (*VoidType) InstanceOf(t Type) bool { + if _, ok := t.(*VoidType); ok { + return true + } + return false +} + +// ===================== NullType ===================== // + +// NullType is JSON null type +type NullType struct{} + +func (*NullType) String() string { + return "Null" +} + +// InstanceOf returns true if t is NullType +func (*NullType) InstanceOf(t Type) bool { + if _, ok := t.(*NullType); ok { + return true + } + return false +} + +// ===================== BoolType ===================== // + +// BoolType is JSON boolean type +type BoolType struct{} + +func (*BoolType) String() string { + return "Bool" +} + +// InstanceOf returns true if t is BoolType +func (*BoolType) InstanceOf(t Type) bool { + if _, ok := t.(*BoolType); ok { + return true + } + return false +} + +// ===================== NumberType ===================== // + +// NumberType is JSON number type +type NumberType struct{} + +func (*NumberType) String() string { + return "Number" +} + +// InstanceOf returns true if t is NumberType +func (*NumberType) InstanceOf(t Type) bool { + if _, ok := t.(*NumberType); ok { + return true + } + return false +} + +// ===================== StringType ===================== // + +// StringType is JSON string type +type StringType struct{} + +func (*StringType) String() string { + return "String" +} + +// InstanceOf returns true if t is StringType +func (*StringType) InstanceOf(t Type) bool { + if _, ok := t.(*StringType); ok { + return true + } + return false +} + +// ===================== ArrayType ===================== // + +// ArrayType is JSON array type +type ArrayType struct { + Arg Type +} + +func (t *ArrayType) String() string { + return "Array[" + t.Arg.String() + "]" +} + +// InstanceOf returns true if t2 is exactly same type as t +func (t *ArrayType) InstanceOf(t2 Type) bool { + if array, ok := t2.(*ArrayType); ok { + return t.Arg.InstanceOf(array.Arg) + } + return false +} + +// ===================== ObjectType ===================== // + +// ObjectType is JSON object type +type ObjectType struct { + Arg Type +} + +func (t *ObjectType) String() string { + return "Object[" + t.Arg.String() + "]" +} + +// InstanceOf returns true if t2 is exactly same type as t +func (t *ObjectType) InstanceOf(t2 Type) bool { + if array, ok := t2.(*ObjectType); ok { + return t.Arg.InstanceOf(array.Arg) + } + return false +} + +// ===================== AnyType ===================== // + +// AnyType allows any type +type AnyType struct{} + +func (*AnyType) String() string { + return "Any" +} + +// InstanceOf always returns true +func (*AnyType) InstanceOf(_ Type) bool { + return true +} From b851a2ccdd8005833f836de9037b64f141095ed6 Mon Sep 17 00:00:00 2001 From: tyru Date: Thu, 19 Apr 2018 23:45:17 +0900 Subject: [PATCH 12/79] feat: implement $invert macro --- dsl/op/invert.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 dsl/op/invert.go diff --git a/dsl/op/invert.go b/dsl/op/invert.go new file mode 100644 index 00000000..e330243c --- /dev/null +++ b/dsl/op/invert.go @@ -0,0 +1,27 @@ +package op + +import ( + "github.com/vim-volt/volt/dsl/types" +) + +func init() { + macroMap[string(InvertOp)] = &InvertOp +} + +type invertOp string + +// InvertOp is "$invert" operation +var InvertOp invertOp = "$invert" + +// String returns "$invert" +func (*invertOp) String() string { + return string(InvertOp) +} + +// Execute executes "$invert" operation +func (*invertOp) Expand(args []types.Value) (types.Value, error) { + if err := signature(&types.AnyType{}).check(args); err != nil { + return nil, err + } + return args[0].Invert() +} From 3c6a0d99672337982ae7b33793fc87029b83a609 Mon Sep 17 00:00:00 2001 From: tyru Date: Thu, 19 Apr 2018 23:46:13 +0900 Subject: [PATCH 13/79] feat: add guard utility struct --- Gopkg.lock | 8 +- dsl/op/guard.go | 54 ++ vendor/github.com/pkg/errors/.gitignore | 24 + vendor/github.com/pkg/errors/.travis.yml | 11 + vendor/github.com/pkg/errors/LICENSE | 23 + vendor/github.com/pkg/errors/README.md | 52 ++ vendor/github.com/pkg/errors/appveyor.yml | 32 ++ vendor/github.com/pkg/errors/bench_test.go | 59 ++ vendor/github.com/pkg/errors/errors.go | 269 ++++++++++ vendor/github.com/pkg/errors/errors_test.go | 226 ++++++++ vendor/github.com/pkg/errors/example_test.go | 205 +++++++ vendor/github.com/pkg/errors/format_test.go | 535 +++++++++++++++++++ vendor/github.com/pkg/errors/stack.go | 178 ++++++ vendor/github.com/pkg/errors/stack_test.go | 292 ++++++++++ 14 files changed, 1967 insertions(+), 1 deletion(-) create mode 100644 dsl/op/guard.go create mode 100644 vendor/github.com/pkg/errors/.gitignore create mode 100644 vendor/github.com/pkg/errors/.travis.yml create mode 100644 vendor/github.com/pkg/errors/LICENSE create mode 100644 vendor/github.com/pkg/errors/README.md create mode 100644 vendor/github.com/pkg/errors/appveyor.yml create mode 100644 vendor/github.com/pkg/errors/bench_test.go create mode 100644 vendor/github.com/pkg/errors/errors.go create mode 100644 vendor/github.com/pkg/errors/errors_test.go create mode 100644 vendor/github.com/pkg/errors/example_test.go create mode 100644 vendor/github.com/pkg/errors/format_test.go create mode 100644 vendor/github.com/pkg/errors/stack.go create mode 100644 vendor/github.com/pkg/errors/stack_test.go diff --git a/Gopkg.lock b/Gopkg.lock index 0554c19d..8d5e1aaf 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -60,6 +60,12 @@ packages = ["."] revision = "b8bc1bf767474819792c23f32d8286a45736f1c6" +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + [[projects]] branch = "master" name = "github.com/sergi/go-diff" @@ -189,6 +195,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "b1abfdb1122cba036acff15042e704f2621de2eb3348e5751a2eb4fe46112e4a" + inputs-digest = "184a72da18c6dd9f91e0525a9fe6a5e072c2758b9046233614da5c88483acece" solver-name = "gps-cdcl" solver-version = 1 diff --git a/dsl/op/guard.go b/dsl/op/guard.go new file mode 100644 index 00000000..877b0dbb --- /dev/null +++ b/dsl/op/guard.go @@ -0,0 +1,54 @@ +package op + +import ( + "fmt" + "github.com/pkg/errors" +) + +// guard invokes "rollback functions" if rollback method received non-nil value +// (e.g. recover(), non-nil error). +type guard struct { + errMsg string + rbFuncs []func() +} + +// rollback rolls back if v is non-nil. +// +// defer func() { err = g.Rollback(recover()) }() +// +// // or +// +// if e != nil { +// err = g.Rollback(e) +// err = g.Rollback(e) // this won't call rollback functions twice! +// return +// } +func (g *guard) rollback(v interface{}) error { + var err error + if e, ok := v.(error); ok { + err = e + } else if v != nil { + err = fmt.Errorf("%s", v) + } + if err != nil { + g.rollbackForcefully() + } + return errors.Wrap(err, g.errMsg) +} + +// rollbackForcefully calls rollback functions in reversed order +func (g *guard) rollbackForcefully() { + for i := len(g.rbFuncs) - 1; i >= 0; i-- { + g.rbFuncs[i]() + } + g.rbFuncs = nil // do not rollback twice +} + +// add adds given rollback functions +func (g *guard) add(f func()) { + g.rbFuncs = append(g.rbFuncs, f) +} + +func funcGuard(name string) *guard { + return &guard{errMsg: fmt.Sprintf("function \"%s\" has an error", name)} +} diff --git a/vendor/github.com/pkg/errors/.gitignore b/vendor/github.com/pkg/errors/.gitignore new file mode 100644 index 00000000..daf913b1 --- /dev/null +++ b/vendor/github.com/pkg/errors/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml new file mode 100644 index 00000000..588ceca1 --- /dev/null +++ b/vendor/github.com/pkg/errors/.travis.yml @@ -0,0 +1,11 @@ +language: go +go_import_path: github.com/pkg/errors +go: + - 1.4.3 + - 1.5.4 + - 1.6.2 + - 1.7.1 + - tip + +script: + - go test -v ./... diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE new file mode 100644 index 00000000..835ba3e7 --- /dev/null +++ b/vendor/github.com/pkg/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md new file mode 100644 index 00000000..273db3c9 --- /dev/null +++ b/vendor/github.com/pkg/errors/README.md @@ -0,0 +1,52 @@ +# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) + +Package errors provides simple error handling primitives. + +`go get github.com/pkg/errors` + +The traditional error handling idiom in Go is roughly akin to +```go +if err != nil { + return err +} +``` +which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. + +## Adding context to an error + +The errors.Wrap function returns a new error that adds context to the original error. For example +```go +_, err := ioutil.ReadAll(r) +if err != nil { + return errors.Wrap(err, "read failed") +} +``` +## Retrieving the cause of an error + +Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. +```go +type causer interface { + Cause() error +} +``` +`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: +```go +switch err := errors.Cause(err).(type) { +case *MyError: + // handle specifically +default: + // unknown error +} +``` + +[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). + +## Contributing + +We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. + +Before proposing a change, please discuss your change by raising an issue. + +## Licence + +BSD-2-Clause diff --git a/vendor/github.com/pkg/errors/appveyor.yml b/vendor/github.com/pkg/errors/appveyor.yml new file mode 100644 index 00000000..a932eade --- /dev/null +++ b/vendor/github.com/pkg/errors/appveyor.yml @@ -0,0 +1,32 @@ +version: build-{build}.{branch} + +clone_folder: C:\gopath\src\github.com\pkg\errors +shallow_clone: true # for startup speed + +environment: + GOPATH: C:\gopath + +platform: + - x64 + +# http://www.appveyor.com/docs/installed-software +install: + # some helpful output for debugging builds + - go version + - go env + # pre-installed MinGW at C:\MinGW is 32bit only + # but MSYS2 at C:\msys64 has mingw64 + - set PATH=C:\msys64\mingw64\bin;%PATH% + - gcc --version + - g++ --version + +build_script: + - go install -v ./... + +test_script: + - set PATH=C:\gopath\bin;%PATH% + - go test -v ./... + +#artifacts: +# - path: '%GOPATH%\bin\*.exe' +deploy: off diff --git a/vendor/github.com/pkg/errors/bench_test.go b/vendor/github.com/pkg/errors/bench_test.go new file mode 100644 index 00000000..0416a3cb --- /dev/null +++ b/vendor/github.com/pkg/errors/bench_test.go @@ -0,0 +1,59 @@ +// +build go1.7 + +package errors + +import ( + "fmt" + "testing" + + stderrors "errors" +) + +func noErrors(at, depth int) error { + if at >= depth { + return stderrors.New("no error") + } + return noErrors(at+1, depth) +} +func yesErrors(at, depth int) error { + if at >= depth { + return New("ye error") + } + return yesErrors(at+1, depth) +} + +func BenchmarkErrors(b *testing.B) { + var toperr error + type run struct { + stack int + std bool + } + runs := []run{ + {10, false}, + {10, true}, + {100, false}, + {100, true}, + {1000, false}, + {1000, true}, + } + for _, r := range runs { + part := "pkg/errors" + if r.std { + part = "errors" + } + name := fmt.Sprintf("%s-stack-%d", part, r.stack) + b.Run(name, func(b *testing.B) { + var err error + f := yesErrors + if r.std { + f = noErrors + } + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err = f(0, r.stack) + } + b.StopTimer() + toperr = err + }) + } +} diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 00000000..842ee804 --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,269 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error by recording a stack trace at the point Wrap is called, +// and the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// If additional control is required the errors.WithStack and errors.WithMessage +// functions destructure errors.Wrap into its component operations of annotating +// an error with a stack trace and an a message, respectively. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// causer interface is not exported by this package, but is considered a part +// of stable public API. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported +// +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface. +// +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } +// +// Where errors.StackTrace is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } +// +// stackTracer interface is not exported by this package, but is considered a part +// of stable public API. +// +// See the documentation for Frame.Format for more details. +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func (f *fundamental) Error() string { return f.msg } + +func (f *fundamental) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, f.msg) + f.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) + } +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +func WithStack(err error) error { + if err == nil { + return nil + } + return &withStack{ + err, + callers(), + } +} + +type withStack struct { + error + *stack +} + +func (w *withStack) Cause() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) + } +} + +// Wrap returns an error annotating err with a stack trace +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is call, and the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + } +} + +type withMessage struct { + cause error + msg string +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/vendor/github.com/pkg/errors/errors_test.go b/vendor/github.com/pkg/errors/errors_test.go new file mode 100644 index 00000000..1d8c6355 --- /dev/null +++ b/vendor/github.com/pkg/errors/errors_test.go @@ -0,0 +1,226 @@ +package errors + +import ( + "errors" + "fmt" + "io" + "reflect" + "testing" +) + +func TestNew(t *testing.T) { + tests := []struct { + err string + want error + }{ + {"", fmt.Errorf("")}, + {"foo", fmt.Errorf("foo")}, + {"foo", New("foo")}, + {"string with format specifiers: %v", errors.New("string with format specifiers: %v")}, + } + + for _, tt := range tests { + got := New(tt.err) + if got.Error() != tt.want.Error() { + t.Errorf("New.Error(): got: %q, want %q", got, tt.want) + } + } +} + +func TestWrapNil(t *testing.T) { + got := Wrap(nil, "no error") + if got != nil { + t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got) + } +} + +func TestWrap(t *testing.T) { + tests := []struct { + err error + message string + want string + }{ + {io.EOF, "read error", "read error: EOF"}, + {Wrap(io.EOF, "read error"), "client error", "client error: read error: EOF"}, + } + + for _, tt := range tests { + got := Wrap(tt.err, tt.message).Error() + if got != tt.want { + t.Errorf("Wrap(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) + } + } +} + +type nilError struct{} + +func (nilError) Error() string { return "nil error" } + +func TestCause(t *testing.T) { + x := New("error") + tests := []struct { + err error + want error + }{{ + // nil error is nil + err: nil, + want: nil, + }, { + // explicit nil error is nil + err: (error)(nil), + want: nil, + }, { + // typed nil is nil + err: (*nilError)(nil), + want: (*nilError)(nil), + }, { + // uncaused error is unaffected + err: io.EOF, + want: io.EOF, + }, { + // caused error returns cause + err: Wrap(io.EOF, "ignored"), + want: io.EOF, + }, { + err: x, // return from errors.New + want: x, + }, { + WithMessage(nil, "whoops"), + nil, + }, { + WithMessage(io.EOF, "whoops"), + io.EOF, + }, { + WithStack(nil), + nil, + }, { + WithStack(io.EOF), + io.EOF, + }} + + for i, tt := range tests { + got := Cause(tt.err) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want) + } + } +} + +func TestWrapfNil(t *testing.T) { + got := Wrapf(nil, "no error") + if got != nil { + t.Errorf("Wrapf(nil, \"no error\"): got %#v, expected nil", got) + } +} + +func TestWrapf(t *testing.T) { + tests := []struct { + err error + message string + want string + }{ + {io.EOF, "read error", "read error: EOF"}, + {Wrapf(io.EOF, "read error without format specifiers"), "client error", "client error: read error without format specifiers: EOF"}, + {Wrapf(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"}, + } + + for _, tt := range tests { + got := Wrapf(tt.err, tt.message).Error() + if got != tt.want { + t.Errorf("Wrapf(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) + } + } +} + +func TestErrorf(t *testing.T) { + tests := []struct { + err error + want string + }{ + {Errorf("read error without format specifiers"), "read error without format specifiers"}, + {Errorf("read error with %d format specifier", 1), "read error with 1 format specifier"}, + } + + for _, tt := range tests { + got := tt.err.Error() + if got != tt.want { + t.Errorf("Errorf(%v): got: %q, want %q", tt.err, got, tt.want) + } + } +} + +func TestWithStackNil(t *testing.T) { + got := WithStack(nil) + if got != nil { + t.Errorf("WithStack(nil): got %#v, expected nil", got) + } +} + +func TestWithStack(t *testing.T) { + tests := []struct { + err error + want string + }{ + {io.EOF, "EOF"}, + {WithStack(io.EOF), "EOF"}, + } + + for _, tt := range tests { + got := WithStack(tt.err).Error() + if got != tt.want { + t.Errorf("WithStack(%v): got: %v, want %v", tt.err, got, tt.want) + } + } +} + +func TestWithMessageNil(t *testing.T) { + got := WithMessage(nil, "no error") + if got != nil { + t.Errorf("WithMessage(nil, \"no error\"): got %#v, expected nil", got) + } +} + +func TestWithMessage(t *testing.T) { + tests := []struct { + err error + message string + want string + }{ + {io.EOF, "read error", "read error: EOF"}, + {WithMessage(io.EOF, "read error"), "client error", "client error: read error: EOF"}, + } + + for _, tt := range tests { + got := WithMessage(tt.err, tt.message).Error() + if got != tt.want { + t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want) + } + } + +} + +// errors.New, etc values are not expected to be compared by value +// but the change in errors#27 made them incomparable. Assert that +// various kinds of errors have a functional equality operator, even +// if the result of that equality is always false. +func TestErrorEquality(t *testing.T) { + vals := []error{ + nil, + io.EOF, + errors.New("EOF"), + New("EOF"), + Errorf("EOF"), + Wrap(io.EOF, "EOF"), + Wrapf(io.EOF, "EOF%d", 2), + WithMessage(nil, "whoops"), + WithMessage(io.EOF, "whoops"), + WithStack(io.EOF), + WithStack(nil), + } + + for i := range vals { + for j := range vals { + _ = vals[i] == vals[j] // mustn't panic + } + } +} diff --git a/vendor/github.com/pkg/errors/example_test.go b/vendor/github.com/pkg/errors/example_test.go new file mode 100644 index 00000000..c1fc13e3 --- /dev/null +++ b/vendor/github.com/pkg/errors/example_test.go @@ -0,0 +1,205 @@ +package errors_test + +import ( + "fmt" + + "github.com/pkg/errors" +) + +func ExampleNew() { + err := errors.New("whoops") + fmt.Println(err) + + // Output: whoops +} + +func ExampleNew_printf() { + err := errors.New("whoops") + fmt.Printf("%+v", err) + + // Example output: + // whoops + // github.com/pkg/errors_test.ExampleNew_printf + // /home/dfc/src/github.com/pkg/errors/example_test.go:17 + // testing.runExample + // /home/dfc/go/src/testing/example.go:114 + // testing.RunExamples + // /home/dfc/go/src/testing/example.go:38 + // testing.(*M).Run + // /home/dfc/go/src/testing/testing.go:744 + // main.main + // /github.com/pkg/errors/_test/_testmain.go:106 + // runtime.main + // /home/dfc/go/src/runtime/proc.go:183 + // runtime.goexit + // /home/dfc/go/src/runtime/asm_amd64.s:2059 +} + +func ExampleWithMessage() { + cause := errors.New("whoops") + err := errors.WithMessage(cause, "oh noes") + fmt.Println(err) + + // Output: oh noes: whoops +} + +func ExampleWithStack() { + cause := errors.New("whoops") + err := errors.WithStack(cause) + fmt.Println(err) + + // Output: whoops +} + +func ExampleWithStack_printf() { + cause := errors.New("whoops") + err := errors.WithStack(cause) + fmt.Printf("%+v", err) + + // Example Output: + // whoops + // github.com/pkg/errors_test.ExampleWithStack_printf + // /home/fabstu/go/src/github.com/pkg/errors/example_test.go:55 + // testing.runExample + // /usr/lib/go/src/testing/example.go:114 + // testing.RunExamples + // /usr/lib/go/src/testing/example.go:38 + // testing.(*M).Run + // /usr/lib/go/src/testing/testing.go:744 + // main.main + // github.com/pkg/errors/_test/_testmain.go:106 + // runtime.main + // /usr/lib/go/src/runtime/proc.go:183 + // runtime.goexit + // /usr/lib/go/src/runtime/asm_amd64.s:2086 + // github.com/pkg/errors_test.ExampleWithStack_printf + // /home/fabstu/go/src/github.com/pkg/errors/example_test.go:56 + // testing.runExample + // /usr/lib/go/src/testing/example.go:114 + // testing.RunExamples + // /usr/lib/go/src/testing/example.go:38 + // testing.(*M).Run + // /usr/lib/go/src/testing/testing.go:744 + // main.main + // github.com/pkg/errors/_test/_testmain.go:106 + // runtime.main + // /usr/lib/go/src/runtime/proc.go:183 + // runtime.goexit + // /usr/lib/go/src/runtime/asm_amd64.s:2086 +} + +func ExampleWrap() { + cause := errors.New("whoops") + err := errors.Wrap(cause, "oh noes") + fmt.Println(err) + + // Output: oh noes: whoops +} + +func fn() error { + e1 := errors.New("error") + e2 := errors.Wrap(e1, "inner") + e3 := errors.Wrap(e2, "middle") + return errors.Wrap(e3, "outer") +} + +func ExampleCause() { + err := fn() + fmt.Println(err) + fmt.Println(errors.Cause(err)) + + // Output: outer: middle: inner: error + // error +} + +func ExampleWrap_extended() { + err := fn() + fmt.Printf("%+v\n", err) + + // Example output: + // error + // github.com/pkg/errors_test.fn + // /home/dfc/src/github.com/pkg/errors/example_test.go:47 + // github.com/pkg/errors_test.ExampleCause_printf + // /home/dfc/src/github.com/pkg/errors/example_test.go:63 + // testing.runExample + // /home/dfc/go/src/testing/example.go:114 + // testing.RunExamples + // /home/dfc/go/src/testing/example.go:38 + // testing.(*M).Run + // /home/dfc/go/src/testing/testing.go:744 + // main.main + // /github.com/pkg/errors/_test/_testmain.go:104 + // runtime.main + // /home/dfc/go/src/runtime/proc.go:183 + // runtime.goexit + // /home/dfc/go/src/runtime/asm_amd64.s:2059 + // github.com/pkg/errors_test.fn + // /home/dfc/src/github.com/pkg/errors/example_test.go:48: inner + // github.com/pkg/errors_test.fn + // /home/dfc/src/github.com/pkg/errors/example_test.go:49: middle + // github.com/pkg/errors_test.fn + // /home/dfc/src/github.com/pkg/errors/example_test.go:50: outer +} + +func ExampleWrapf() { + cause := errors.New("whoops") + err := errors.Wrapf(cause, "oh noes #%d", 2) + fmt.Println(err) + + // Output: oh noes #2: whoops +} + +func ExampleErrorf_extended() { + err := errors.Errorf("whoops: %s", "foo") + fmt.Printf("%+v", err) + + // Example output: + // whoops: foo + // github.com/pkg/errors_test.ExampleErrorf + // /home/dfc/src/github.com/pkg/errors/example_test.go:101 + // testing.runExample + // /home/dfc/go/src/testing/example.go:114 + // testing.RunExamples + // /home/dfc/go/src/testing/example.go:38 + // testing.(*M).Run + // /home/dfc/go/src/testing/testing.go:744 + // main.main + // /github.com/pkg/errors/_test/_testmain.go:102 + // runtime.main + // /home/dfc/go/src/runtime/proc.go:183 + // runtime.goexit + // /home/dfc/go/src/runtime/asm_amd64.s:2059 +} + +func Example_stackTrace() { + type stackTracer interface { + StackTrace() errors.StackTrace + } + + err, ok := errors.Cause(fn()).(stackTracer) + if !ok { + panic("oops, err does not implement stackTracer") + } + + st := err.StackTrace() + fmt.Printf("%+v", st[0:2]) // top two frames + + // Example output: + // github.com/pkg/errors_test.fn + // /home/dfc/src/github.com/pkg/errors/example_test.go:47 + // github.com/pkg/errors_test.Example_stackTrace + // /home/dfc/src/github.com/pkg/errors/example_test.go:127 +} + +func ExampleCause_printf() { + err := errors.Wrap(func() error { + return func() error { + return errors.Errorf("hello %s", fmt.Sprintf("world")) + }() + }(), "failed") + + fmt.Printf("%v", err) + + // Output: failed: hello world +} diff --git a/vendor/github.com/pkg/errors/format_test.go b/vendor/github.com/pkg/errors/format_test.go new file mode 100644 index 00000000..15fd7d89 --- /dev/null +++ b/vendor/github.com/pkg/errors/format_test.go @@ -0,0 +1,535 @@ +package errors + +import ( + "errors" + "fmt" + "io" + "regexp" + "strings" + "testing" +) + +func TestFormatNew(t *testing.T) { + tests := []struct { + error + format string + want string + }{{ + New("error"), + "%s", + "error", + }, { + New("error"), + "%v", + "error", + }, { + New("error"), + "%+v", + "error\n" + + "github.com/pkg/errors.TestFormatNew\n" + + "\t.+/github.com/pkg/errors/format_test.go:26", + }, { + New("error"), + "%q", + `"error"`, + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.error, tt.format, tt.want) + } +} + +func TestFormatErrorf(t *testing.T) { + tests := []struct { + error + format string + want string + }{{ + Errorf("%s", "error"), + "%s", + "error", + }, { + Errorf("%s", "error"), + "%v", + "error", + }, { + Errorf("%s", "error"), + "%+v", + "error\n" + + "github.com/pkg/errors.TestFormatErrorf\n" + + "\t.+/github.com/pkg/errors/format_test.go:56", + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.error, tt.format, tt.want) + } +} + +func TestFormatWrap(t *testing.T) { + tests := []struct { + error + format string + want string + }{{ + Wrap(New("error"), "error2"), + "%s", + "error2: error", + }, { + Wrap(New("error"), "error2"), + "%v", + "error2: error", + }, { + Wrap(New("error"), "error2"), + "%+v", + "error\n" + + "github.com/pkg/errors.TestFormatWrap\n" + + "\t.+/github.com/pkg/errors/format_test.go:82", + }, { + Wrap(io.EOF, "error"), + "%s", + "error: EOF", + }, { + Wrap(io.EOF, "error"), + "%v", + "error: EOF", + }, { + Wrap(io.EOF, "error"), + "%+v", + "EOF\n" + + "error\n" + + "github.com/pkg/errors.TestFormatWrap\n" + + "\t.+/github.com/pkg/errors/format_test.go:96", + }, { + Wrap(Wrap(io.EOF, "error1"), "error2"), + "%+v", + "EOF\n" + + "error1\n" + + "github.com/pkg/errors.TestFormatWrap\n" + + "\t.+/github.com/pkg/errors/format_test.go:103\n", + }, { + Wrap(New("error with space"), "context"), + "%q", + `"context: error with space"`, + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.error, tt.format, tt.want) + } +} + +func TestFormatWrapf(t *testing.T) { + tests := []struct { + error + format string + want string + }{{ + Wrapf(io.EOF, "error%d", 2), + "%s", + "error2: EOF", + }, { + Wrapf(io.EOF, "error%d", 2), + "%v", + "error2: EOF", + }, { + Wrapf(io.EOF, "error%d", 2), + "%+v", + "EOF\n" + + "error2\n" + + "github.com/pkg/errors.TestFormatWrapf\n" + + "\t.+/github.com/pkg/errors/format_test.go:134", + }, { + Wrapf(New("error"), "error%d", 2), + "%s", + "error2: error", + }, { + Wrapf(New("error"), "error%d", 2), + "%v", + "error2: error", + }, { + Wrapf(New("error"), "error%d", 2), + "%+v", + "error\n" + + "github.com/pkg/errors.TestFormatWrapf\n" + + "\t.+/github.com/pkg/errors/format_test.go:149", + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.error, tt.format, tt.want) + } +} + +func TestFormatWithStack(t *testing.T) { + tests := []struct { + error + format string + want []string + }{{ + WithStack(io.EOF), + "%s", + []string{"EOF"}, + }, { + WithStack(io.EOF), + "%v", + []string{"EOF"}, + }, { + WithStack(io.EOF), + "%+v", + []string{"EOF", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:175"}, + }, { + WithStack(New("error")), + "%s", + []string{"error"}, + }, { + WithStack(New("error")), + "%v", + []string{"error"}, + }, { + WithStack(New("error")), + "%+v", + []string{"error", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:189", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:189"}, + }, { + WithStack(WithStack(io.EOF)), + "%+v", + []string{"EOF", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:197", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:197"}, + }, { + WithStack(WithStack(Wrapf(io.EOF, "message"))), + "%+v", + []string{"EOF", + "message", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:205", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:205", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:205"}, + }, { + WithStack(Errorf("error%d", 1)), + "%+v", + []string{"error1", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:216", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:216"}, + }} + + for i, tt := range tests { + testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true) + } +} + +func TestFormatWithMessage(t *testing.T) { + tests := []struct { + error + format string + want []string + }{{ + WithMessage(New("error"), "error2"), + "%s", + []string{"error2: error"}, + }, { + WithMessage(New("error"), "error2"), + "%v", + []string{"error2: error"}, + }, { + WithMessage(New("error"), "error2"), + "%+v", + []string{ + "error", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:244", + "error2"}, + }, { + WithMessage(io.EOF, "addition1"), + "%s", + []string{"addition1: EOF"}, + }, { + WithMessage(io.EOF, "addition1"), + "%v", + []string{"addition1: EOF"}, + }, { + WithMessage(io.EOF, "addition1"), + "%+v", + []string{"EOF", "addition1"}, + }, { + WithMessage(WithMessage(io.EOF, "addition1"), "addition2"), + "%v", + []string{"addition2: addition1: EOF"}, + }, { + WithMessage(WithMessage(io.EOF, "addition1"), "addition2"), + "%+v", + []string{"EOF", "addition1", "addition2"}, + }, { + Wrap(WithMessage(io.EOF, "error1"), "error2"), + "%+v", + []string{"EOF", "error1", "error2", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:272"}, + }, { + WithMessage(Errorf("error%d", 1), "error2"), + "%+v", + []string{"error1", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:278", + "error2"}, + }, { + WithMessage(WithStack(io.EOF), "error"), + "%+v", + []string{ + "EOF", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:285", + "error"}, + }, { + WithMessage(Wrap(WithStack(io.EOF), "inside-error"), "outside-error"), + "%+v", + []string{ + "EOF", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:293", + "inside-error", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:293", + "outside-error"}, + }} + + for i, tt := range tests { + testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true) + } +} + +func TestFormatGeneric(t *testing.T) { + starts := []struct { + err error + want []string + }{ + {New("new-error"), []string{ + "new-error", + "github.com/pkg/errors.TestFormatGeneric\n" + + "\t.+/github.com/pkg/errors/format_test.go:315"}, + }, {Errorf("errorf-error"), []string{ + "errorf-error", + "github.com/pkg/errors.TestFormatGeneric\n" + + "\t.+/github.com/pkg/errors/format_test.go:319"}, + }, {errors.New("errors-new-error"), []string{ + "errors-new-error"}, + }, + } + + wrappers := []wrapper{ + { + func(err error) error { return WithMessage(err, "with-message") }, + []string{"with-message"}, + }, { + func(err error) error { return WithStack(err) }, + []string{ + "github.com/pkg/errors.(func·002|TestFormatGeneric.func2)\n\t" + + ".+/github.com/pkg/errors/format_test.go:333", + }, + }, { + func(err error) error { return Wrap(err, "wrap-error") }, + []string{ + "wrap-error", + "github.com/pkg/errors.(func·003|TestFormatGeneric.func3)\n\t" + + ".+/github.com/pkg/errors/format_test.go:339", + }, + }, { + func(err error) error { return Wrapf(err, "wrapf-error%d", 1) }, + []string{ + "wrapf-error1", + "github.com/pkg/errors.(func·004|TestFormatGeneric.func4)\n\t" + + ".+/github.com/pkg/errors/format_test.go:346", + }, + }, + } + + for s := range starts { + err := starts[s].err + want := starts[s].want + testFormatCompleteCompare(t, s, err, "%+v", want, false) + testGenericRecursive(t, err, want, wrappers, 3) + } +} + +func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) { + got := fmt.Sprintf(format, arg) + gotLines := strings.SplitN(got, "\n", -1) + wantLines := strings.SplitN(want, "\n", -1) + + if len(wantLines) > len(gotLines) { + t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want) + return + } + + for i, w := range wantLines { + match, err := regexp.MatchString(w, gotLines[i]) + if err != nil { + t.Fatal(err) + } + if !match { + t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want) + } + } +} + +var stackLineR = regexp.MustCompile(`\.`) + +// parseBlocks parses input into a slice, where: +// - incase entry contains a newline, its a stacktrace +// - incase entry contains no newline, its a solo line. +// +// Detecting stack boundaries only works incase the WithStack-calls are +// to be found on the same line, thats why it is optionally here. +// +// Example use: +// +// for _, e := range blocks { +// if strings.ContainsAny(e, "\n") { +// // Match as stack +// } else { +// // Match as line +// } +// } +// +func parseBlocks(input string, detectStackboundaries bool) ([]string, error) { + var blocks []string + + stack := "" + wasStack := false + lines := map[string]bool{} // already found lines + + for _, l := range strings.Split(input, "\n") { + isStackLine := stackLineR.MatchString(l) + + switch { + case !isStackLine && wasStack: + blocks = append(blocks, stack, l) + stack = "" + lines = map[string]bool{} + case isStackLine: + if wasStack { + // Detecting two stacks after another, possible cause lines match in + // our tests due to WithStack(WithStack(io.EOF)) on same line. + if detectStackboundaries { + if lines[l] { + if len(stack) == 0 { + return nil, errors.New("len of block must not be zero here") + } + + blocks = append(blocks, stack) + stack = l + lines = map[string]bool{l: true} + continue + } + } + + stack = stack + "\n" + l + } else { + stack = l + } + lines[l] = true + case !isStackLine && !wasStack: + blocks = append(blocks, l) + default: + return nil, errors.New("must not happen") + } + + wasStack = isStackLine + } + + // Use up stack + if stack != "" { + blocks = append(blocks, stack) + } + return blocks, nil +} + +func testFormatCompleteCompare(t *testing.T, n int, arg interface{}, format string, want []string, detectStackBoundaries bool) { + gotStr := fmt.Sprintf(format, arg) + + got, err := parseBlocks(gotStr, detectStackBoundaries) + if err != nil { + t.Fatal(err) + } + + if len(got) != len(want) { + t.Fatalf("test %d: fmt.Sprintf(%s, err) -> wrong number of blocks: got(%d) want(%d)\n got: %s\nwant: %s\ngotStr: %q", + n+1, format, len(got), len(want), prettyBlocks(got), prettyBlocks(want), gotStr) + } + + for i := range got { + if strings.ContainsAny(want[i], "\n") { + // Match as stack + match, err := regexp.MatchString(want[i], got[i]) + if err != nil { + t.Fatal(err) + } + if !match { + t.Fatalf("test %d: block %d: fmt.Sprintf(%q, err):\ngot:\n%q\nwant:\n%q\nall-got:\n%s\nall-want:\n%s\n", + n+1, i+1, format, got[i], want[i], prettyBlocks(got), prettyBlocks(want)) + } + } else { + // Match as message + if got[i] != want[i] { + t.Fatalf("test %d: fmt.Sprintf(%s, err) at block %d got != want:\n got: %q\nwant: %q", n+1, format, i+1, got[i], want[i]) + } + } + } +} + +type wrapper struct { + wrap func(err error) error + want []string +} + +func prettyBlocks(blocks []string, prefix ...string) string { + var out []string + + for _, b := range blocks { + out = append(out, fmt.Sprintf("%v", b)) + } + + return " " + strings.Join(out, "\n ") +} + +func testGenericRecursive(t *testing.T, beforeErr error, beforeWant []string, list []wrapper, maxDepth int) { + if len(beforeWant) == 0 { + panic("beforeWant must not be empty") + } + for _, w := range list { + if len(w.want) == 0 { + panic("want must not be empty") + } + + err := w.wrap(beforeErr) + + // Copy required cause append(beforeWant, ..) modified beforeWant subtly. + beforeCopy := make([]string, len(beforeWant)) + copy(beforeCopy, beforeWant) + + beforeWant := beforeCopy + last := len(beforeWant) - 1 + var want []string + + // Merge two stacks behind each other. + if strings.ContainsAny(beforeWant[last], "\n") && strings.ContainsAny(w.want[0], "\n") { + want = append(beforeWant[:last], append([]string{beforeWant[last] + "((?s).*)" + w.want[0]}, w.want[1:]...)...) + } else { + want = append(beforeWant, w.want...) + } + + testFormatCompleteCompare(t, maxDepth, err, "%+v", want, false) + if maxDepth > 0 { + testGenericRecursive(t, err, want, list, maxDepth-1) + } + } +} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go new file mode 100644 index 00000000..6b1f2891 --- /dev/null +++ b/vendor/github.com/pkg/errors/stack.go @@ -0,0 +1,178 @@ +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strings" +) + +// Frame represents a program counter inside a stack frame. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s path of source file relative to the compile time GOPATH +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + pc := f.pc() + fn := runtime.FuncForPC(pc) + if fn == nil { + io.WriteString(s, "unknown") + } else { + file, _ := fn.FileLine(pc) + fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + } + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + fmt.Fprintf(s, "%d", f.line()) + case 'n': + name := runtime.FuncForPC(f.pc()).Name() + io.WriteString(s, funcname(name)) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + fmt.Fprintf(s, "%v", []Frame(st)) + } + case 's': + fmt.Fprintf(s, "%s", []Frame(st)) + } +} + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} + +func trimGOPATH(name, file string) string { + // Here we want to get the source file path relative to the compile time + // GOPATH. As of Go 1.6.x there is no direct way to know the compiled + // GOPATH at runtime, but we can infer the number of path segments in the + // GOPATH. We note that fn.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired output. We count separators from the end of the file + // path until it finds two more than in the function name and then move + // one character forward to preserve the initial path segment without a + // leading separator. + const sep = "/" + goal := strings.Count(name, sep) + 2 + i := len(file) + for n := 0; n < goal; n++ { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + // not enough separators found, set i so that the slice expression + // below leaves file unmodified + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + file = file[i+len(sep):] + return file +} diff --git a/vendor/github.com/pkg/errors/stack_test.go b/vendor/github.com/pkg/errors/stack_test.go new file mode 100644 index 00000000..510c27a9 --- /dev/null +++ b/vendor/github.com/pkg/errors/stack_test.go @@ -0,0 +1,292 @@ +package errors + +import ( + "fmt" + "runtime" + "testing" +) + +var initpc, _, _, _ = runtime.Caller(0) + +func TestFrameLine(t *testing.T) { + var tests = []struct { + Frame + want int + }{{ + Frame(initpc), + 9, + }, { + func() Frame { + var pc, _, _, _ = runtime.Caller(0) + return Frame(pc) + }(), + 20, + }, { + func() Frame { + var pc, _, _, _ = runtime.Caller(1) + return Frame(pc) + }(), + 28, + }, { + Frame(0), // invalid PC + 0, + }} + + for _, tt := range tests { + got := tt.Frame.line() + want := tt.want + if want != got { + t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got) + } + } +} + +type X struct{} + +func (x X) val() Frame { + var pc, _, _, _ = runtime.Caller(0) + return Frame(pc) +} + +func (x *X) ptr() Frame { + var pc, _, _, _ = runtime.Caller(0) + return Frame(pc) +} + +func TestFrameFormat(t *testing.T) { + var tests = []struct { + Frame + format string + want string + }{{ + Frame(initpc), + "%s", + "stack_test.go", + }, { + Frame(initpc), + "%+s", + "github.com/pkg/errors.init\n" + + "\t.+/github.com/pkg/errors/stack_test.go", + }, { + Frame(0), + "%s", + "unknown", + }, { + Frame(0), + "%+s", + "unknown", + }, { + Frame(initpc), + "%d", + "9", + }, { + Frame(0), + "%d", + "0", + }, { + Frame(initpc), + "%n", + "init", + }, { + func() Frame { + var x X + return x.ptr() + }(), + "%n", + `\(\*X\).ptr`, + }, { + func() Frame { + var x X + return x.val() + }(), + "%n", + "X.val", + }, { + Frame(0), + "%n", + "", + }, { + Frame(initpc), + "%v", + "stack_test.go:9", + }, { + Frame(initpc), + "%+v", + "github.com/pkg/errors.init\n" + + "\t.+/github.com/pkg/errors/stack_test.go:9", + }, { + Frame(0), + "%v", + "unknown:0", + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.Frame, tt.format, tt.want) + } +} + +func TestFuncname(t *testing.T) { + tests := []struct { + name, want string + }{ + {"", ""}, + {"runtime.main", "main"}, + {"github.com/pkg/errors.funcname", "funcname"}, + {"funcname", "funcname"}, + {"io.copyBuffer", "copyBuffer"}, + {"main.(*R).Write", "(*R).Write"}, + } + + for _, tt := range tests { + got := funcname(tt.name) + want := tt.want + if got != want { + t.Errorf("funcname(%q): want: %q, got %q", tt.name, want, got) + } + } +} + +func TestTrimGOPATH(t *testing.T) { + var tests = []struct { + Frame + want string + }{{ + Frame(initpc), + "github.com/pkg/errors/stack_test.go", + }} + + for i, tt := range tests { + pc := tt.Frame.pc() + fn := runtime.FuncForPC(pc) + file, _ := fn.FileLine(pc) + got := trimGOPATH(fn.Name(), file) + testFormatRegexp(t, i, got, "%s", tt.want) + } +} + +func TestStackTrace(t *testing.T) { + tests := []struct { + err error + want []string + }{{ + New("ooh"), []string{ + "github.com/pkg/errors.TestStackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:172", + }, + }, { + Wrap(New("ooh"), "ahh"), []string{ + "github.com/pkg/errors.TestStackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:177", // this is the stack of Wrap, not New + }, + }, { + Cause(Wrap(New("ooh"), "ahh")), []string{ + "github.com/pkg/errors.TestStackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:182", // this is the stack of New + }, + }, { + func() error { return New("ooh") }(), []string{ + `github.com/pkg/errors.(func·009|TestStackTrace.func1)` + + "\n\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New + "github.com/pkg/errors.TestStackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New's caller + }, + }, { + Cause(func() error { + return func() error { + return Errorf("hello %s", fmt.Sprintf("world")) + }() + }()), []string{ + `github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` + + "\n\t.+/github.com/pkg/errors/stack_test.go:196", // this is the stack of Errorf + `github.com/pkg/errors.(func·011|TestStackTrace.func2)` + + "\n\t.+/github.com/pkg/errors/stack_test.go:197", // this is the stack of Errorf's caller + "github.com/pkg/errors.TestStackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:198", // this is the stack of Errorf's caller's caller + }, + }} + for i, tt := range tests { + x, ok := tt.err.(interface { + StackTrace() StackTrace + }) + if !ok { + t.Errorf("expected %#v to implement StackTrace() StackTrace", tt.err) + continue + } + st := x.StackTrace() + for j, want := range tt.want { + testFormatRegexp(t, i, st[j], "%+v", want) + } + } +} + +func stackTrace() StackTrace { + const depth = 8 + var pcs [depth]uintptr + n := runtime.Callers(1, pcs[:]) + var st stack = pcs[0:n] + return st.StackTrace() +} + +func TestStackTraceFormat(t *testing.T) { + tests := []struct { + StackTrace + format string + want string + }{{ + nil, + "%s", + `\[\]`, + }, { + nil, + "%v", + `\[\]`, + }, { + nil, + "%+v", + "", + }, { + nil, + "%#v", + `\[\]errors.Frame\(nil\)`, + }, { + make(StackTrace, 0), + "%s", + `\[\]`, + }, { + make(StackTrace, 0), + "%v", + `\[\]`, + }, { + make(StackTrace, 0), + "%+v", + "", + }, { + make(StackTrace, 0), + "%#v", + `\[\]errors.Frame{}`, + }, { + stackTrace()[:2], + "%s", + `\[stack_test.go stack_test.go\]`, + }, { + stackTrace()[:2], + "%v", + `\[stack_test.go:225 stack_test.go:272\]`, + }, { + stackTrace()[:2], + "%+v", + "\n" + + "github.com/pkg/errors.stackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:225\n" + + "github.com/pkg/errors.TestStackTraceFormat\n" + + "\t.+/github.com/pkg/errors/stack_test.go:276", + }, { + stackTrace()[:2], + "%#v", + `\[\]errors.Frame{stack_test.go:225, stack_test.go:284}`, + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want) + } +} From 66469a7870c98a3323927fa946d4bf35c3d105ed Mon Sep 17 00:00:00 2001 From: tyru Date: Thu, 19 Apr 2018 23:46:51 +0900 Subject: [PATCH 14/79] feat: implement "do" function --- dsl/op/do.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/dsl/op/do.go b/dsl/op/do.go index 673adb88..8e8fa88d 100644 --- a/dsl/op/do.go +++ b/dsl/op/do.go @@ -24,7 +24,7 @@ func (*doOp) String() string { func (*doOp) Bind(args ...types.Value) (*types.Expr, error) { sig := make([]types.Type, 0, len(args)) for i := 0; i < len(args); i++ { - sig = append(sig, types.ArrayType) + sig = append(sig, &types.AnyType{}) } if err := signature(sig...).check(args); err != nil { return nil, err @@ -52,7 +52,19 @@ func (*doOp) InvertExpr(args []types.Value) (*types.Expr, error) { } // Execute executes "do" operation -func (*doOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { - // TODO - return nil, func() {}, nil +func (*doOp) Execute(ctx context.Context, args []types.Value) (val types.Value, rollback func(), err error) { + g := funcGuard(string(DoOp)) + defer func() { err = g.rollback(recover()) }() + rollback = g.rollbackForcefully + + for i := range args { + v, rbFunc, e := args[i].Eval(ctx) + g.add(rbFunc) + if e != nil { + err = g.rollback(e) + return + } + val = v + } + return } From 083ce4ac642cbe2100d0a42b3936679174516328 Mon Sep 17 00:00:00 2001 From: tyru Date: Fri, 20 Apr 2018 00:01:24 +0900 Subject: [PATCH 15/79] doc: add @ macro --- _docs/json-dsl.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/_docs/json-dsl.md b/_docs/json-dsl.md index 098e4369..29607815 100644 --- a/_docs/json-dsl.md +++ b/_docs/json-dsl.md @@ -322,7 +322,13 @@ TODO: Move to Godoc. All macros has `$` prefixed name for readability. Macros are not saved in transaction log (expanded before saving). -* `["$invert", expr Expr[* => *]] Expr[* => *]` +* `["@", v1 Value, ...] Array` + * Returns inverse expression of given expression. + * Internally, this macro calls `InvertExpr()` method of each operator struct. + * What value is returned depends on each operator's `InvertExpr()` + implementation. + +* `["$invert", expr Value] Value` * Returns inverse expression of given expression. * Internally, this macro calls `InvertExpr()` method of each operator struct. * What value is returned depends on each operator's `InvertExpr()` @@ -343,14 +349,14 @@ Macros are not saved in transaction log (expanded before saving). * `["$invert", ["label", linenum, "msg", expr]]` = `["label", ["$invert", linenum], "revert: \"msg\"", ["$invert", expr]]` * See `Label examples` section for more details -* `["do", expr1 Expr[* => R1], ..., expr_last Expr[* => R2]] R2` +* `["do", expr1 R1, ..., expr_last R2] R2` * Executes multiple expressions in series. * Returns the evaluated value of the last expression. * e.g. * `["$invert", ["do", expr1, expr2]]` = `["do", ["$invert", expr1], ["$invert", expr2]]` * Note that the arguments are reversed. -* `["parallel", msg string, expr1 Expr[* => R1], ..., expr_last Expr[* => R2]] R2` +* `["parallel", msg string, expr1 R1, ..., expr_last R2] R2` * Executes multiple expressions in parallel. * Returns the evaluated value of the last expression. * e.g. From 6b9f691545da1b62a6050bc7286f024fd59b13b2 Mon Sep 17 00:00:00 2001 From: tyru Date: Fri, 20 Apr 2018 00:24:18 +0900 Subject: [PATCH 16/79] refactor: fix comment --- dsl/types/types.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsl/types/types.go b/dsl/types/types.go index 67bca594..5be1e8a1 100644 --- a/dsl/types/types.go +++ b/dsl/types/types.go @@ -105,7 +105,7 @@ func (t *ArrayType) String() string { return "Array[" + t.Arg.String() + "]" } -// InstanceOf returns true if t2 is exactly same type as t +// InstanceOf returns true if t is instance of t2 func (t *ArrayType) InstanceOf(t2 Type) bool { if array, ok := t2.(*ArrayType); ok { return t.Arg.InstanceOf(array.Arg) @@ -124,7 +124,7 @@ func (t *ObjectType) String() string { return "Object[" + t.Arg.String() + "]" } -// InstanceOf returns true if t2 is exactly same type as t +// InstanceOf returns true if t is instance of t2 func (t *ObjectType) InstanceOf(t2 Type) bool { if array, ok := t2.(*ObjectType); ok { return t.Arg.InstanceOf(array.Arg) From aa8b9bcdd1bafb45eb84f7db37a1f77d30e33458 Mon Sep 17 00:00:00 2001 From: tyru Date: Fri, 20 Apr 2018 22:31:25 +0900 Subject: [PATCH 17/79] fix: store arg type of array,object to its struct --- dsl/op/array.go | 5 ++++- dsl/parse.go | 2 +- dsl/types/json.go | 18 ++++++++++-------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/dsl/op/array.go b/dsl/op/array.go index 62b747fe..52fd9f5b 100644 --- a/dsl/op/array.go +++ b/dsl/op/array.go @@ -20,5 +20,8 @@ func (*arrayOp) String() string { // Execute executes "@" operation func (*arrayOp) Expand(args []types.Value) (types.Value, error) { - return &types.Array{Value: args}, nil + return &types.Array{ + Elems: args, + ArgType: &types.AnyType{}, + }, nil } diff --git a/dsl/parse.go b/dsl/parse.go index 854049d1..8b1a9d6e 100644 --- a/dsl/parse.go +++ b/dsl/parse.go @@ -81,7 +81,7 @@ func parse(value interface{}) (types.Value, error) { } m[k] = v } - return &types.Object{m}, nil + return &types.Object{Map: m, ArgType: &types.AnyType{}}, nil case []interface{}: return parseArray(val) default: diff --git a/dsl/types/json.go b/dsl/types/json.go index 88f801d2..7076f620 100644 --- a/dsl/types/json.go +++ b/dsl/types/json.go @@ -38,7 +38,7 @@ func (v *Null) Eval(context.Context) (val Value, rollback func(), err error) { // Type returns the type. func (v *Null) Type() Type { - return NullType + return &NullType{} } // ================ Bool ================ @@ -60,7 +60,7 @@ func (v *Bool) Eval(context.Context) (val Value, rollback func(), err error) { // Type returns the type. func (v *Bool) Type() Type { - return BoolType + return &BoolType{} } // TrueValue is the JSON true value @@ -88,7 +88,7 @@ func (v *Number) Eval(context.Context) (val Value, rollback func(), err error) { // Type returns the type. func (v *Number) Type() Type { - return NumberType + return &NumberType{} } // ================ String ================ @@ -110,14 +110,15 @@ func (v *String) Eval(context.Context) (val Value, rollback func(), err error) { // Type returns the type. func (v *String) Type() Type { - return StringType + return &StringType{} } // ================ Array ================ // Array is JSON array struct type Array struct { - Value []Value + Elems []Value + ArgType Type } // Invert returns itself as-is. All literal types of JSON values are the same. @@ -132,14 +133,15 @@ func (v *Array) Eval(context.Context) (val Value, rollback func(), err error) { // Type returns the type. func (v *Array) Type() Type { - return ArrayType + return &ArrayType{Arg: v.ArgType} } // ================ Object ================ // Object is JSON object struct type Object struct { - Value map[string]Value + Map map[string]Value + ArgType Type } // Invert returns itself as-is. All literal types of JSON values are the same. @@ -154,5 +156,5 @@ func (v *Object) Eval(context.Context) (val Value, rollback func(), err error) { // Type returns the type. func (v *Object) Type() Type { - return ObjectType + return &ObjectType{Arg: v.ArgType} } From 986e3329cfa8cbd33dc38690e26f731fdd581cf6 Mon Sep 17 00:00:00 2001 From: tyru Date: Fri, 20 Apr 2018 23:22:01 +0900 Subject: [PATCH 18/79] refactor: make the fields of structs of types package readonly --- dsl/deparse.go | 18 +++++------ dsl/op/array.go | 5 +--- dsl/op/do.go | 10 ++----- dsl/op/invert.go | 2 +- dsl/parse.go | 11 +++---- dsl/types/expr.go | 35 +++++++++++++++------- dsl/types/json.go | 75 ++++++++++++++++++++++++++++++++++++++++------ dsl/types/trxid.go | 6 ++++ dsl/types/types.go | 32 +++++++++++++------- 9 files changed, 137 insertions(+), 57 deletions(-) create mode 100644 dsl/types/trxid.go diff --git a/dsl/deparse.go b/dsl/deparse.go index c23fca57..b0a08ce3 100644 --- a/dsl/deparse.go +++ b/dsl/deparse.go @@ -23,14 +23,14 @@ func deparse(value types.Value) (interface{}, error) { case *types.Null: return nil, nil case *types.Bool: - return val.Value, nil + return val.Value(), nil case *types.String: - return val.Value, nil + return val.Value(), nil case *types.Number: - return val.Value, nil + return val.Value(), nil case *types.Object: - m := make(map[string]interface{}, len(val.Value)) - for k, o := range val.Value { + m := make(map[string]interface{}, len(val.Value())) + for k, o := range val.Value() { v, err := deparse(o) if err != nil { return nil, err @@ -39,13 +39,13 @@ func deparse(value types.Value) (interface{}, error) { } return m, nil case *types.Expr: - a := make([]interface{}, 0, len(val.Args)+1) + a := make([]interface{}, 0, len(val.Args())+1) // Do not include "@" in array literal - if val.Func.String() != op.ArrayOp.String() { - a = append(a, &types.String{Value: val.Func.String()}) + if val.Func().String() != op.ArrayOp.String() { + a = append(a, types.NewString(val.Func().String())) } for i := range a { - v, err := deparse(val.Args[i]) + v, err := deparse(val.Args()[i]) if err != nil { return nil, err } diff --git a/dsl/op/array.go b/dsl/op/array.go index 52fd9f5b..e7fdc977 100644 --- a/dsl/op/array.go +++ b/dsl/op/array.go @@ -20,8 +20,5 @@ func (*arrayOp) String() string { // Execute executes "@" operation func (*arrayOp) Expand(args []types.Value) (types.Value, error) { - return &types.Array{ - Elems: args, - ArgType: &types.AnyType{}, - }, nil + return types.NewArray(args, types.AnyValue), nil } diff --git a/dsl/op/do.go b/dsl/op/do.go index 8e8fa88d..400f716e 100644 --- a/dsl/op/do.go +++ b/dsl/op/do.go @@ -24,17 +24,13 @@ func (*doOp) String() string { func (*doOp) Bind(args ...types.Value) (*types.Expr, error) { sig := make([]types.Type, 0, len(args)) for i := 0; i < len(args); i++ { - sig = append(sig, &types.AnyType{}) + sig = append(sig, types.AnyValue) } if err := signature(sig...).check(args); err != nil { return nil, err } retType := args[len(args)-1].Type() - return &types.Expr{ - Func: &DoOp, - Args: args, - RetType: retType, - }, nil + return types.NewExpr(&DoOp, args, retType), nil } // InvertExpr returns inverted expression: Call Value.Invert() for each argument, @@ -53,7 +49,7 @@ func (*doOp) InvertExpr(args []types.Value) (*types.Expr, error) { // Execute executes "do" operation func (*doOp) Execute(ctx context.Context, args []types.Value) (val types.Value, rollback func(), err error) { - g := funcGuard(string(DoOp)) + g := funcGuard(DoOp.String()) defer func() { err = g.rollback(recover()) }() rollback = g.rollbackForcefully diff --git a/dsl/op/invert.go b/dsl/op/invert.go index e330243c..4415206d 100644 --- a/dsl/op/invert.go +++ b/dsl/op/invert.go @@ -20,7 +20,7 @@ func (*invertOp) String() string { // Execute executes "$invert" operation func (*invertOp) Expand(args []types.Value) (types.Value, error) { - if err := signature(&types.AnyType{}).check(args); err != nil { + if err := signature(types.AnyValue).check(args); err != nil { return nil, err } return args[0].Invert() diff --git a/dsl/parse.go b/dsl/parse.go index 8b1a9d6e..143c967b 100644 --- a/dsl/parse.go +++ b/dsl/parse.go @@ -64,14 +64,11 @@ func parse(value interface{}) (types.Value, error) { case nil: return types.NullValue, nil case bool: - if val { - return types.TrueValue, nil - } - return types.FalseValue, nil + return types.NewBool(val), nil case string: - return &types.String{val}, nil + return types.NewString(val), nil case float64: - return &types.Number{val}, nil + return types.NewNumber(val), nil case map[string]interface{}: m := make(map[string]types.Value, len(val)) for k, o := range m { @@ -81,7 +78,7 @@ func parse(value interface{}) (types.Value, error) { } m[k] = v } - return &types.Object{Map: m, ArgType: &types.AnyType{}}, nil + return types.NewObject(m, types.AnyValue), nil case []interface{}: return parseArray(val) default: diff --git a/dsl/types/expr.go b/dsl/types/expr.go index 16639e44..612fb615 100644 --- a/dsl/types/expr.go +++ b/dsl/types/expr.go @@ -4,28 +4,43 @@ import "context" // Expr has an operation and its arguments type Expr struct { - Func Func - Args []Value - RetType Type + fun Func + args []Value + retType Type } -// TrxID is a transaction ID, which is a serial number and directory name of -// transaction log file. -// XXX: this should be in transaction package? -type TrxID int64 +// Func returns function of Expr +func (expr *Expr) Func() Func { + return expr.fun +} + +// Args returns arguments of Expr +func (expr *Expr) Args() []Value { + return expr.args +} + +// RetType returns return type of Expr +func (expr *Expr) RetType() Type { + return expr.retType +} + +// NewExpr creates Expr instance +func NewExpr(fun Func, args []Value, retType Type) *Expr { + return &Expr{fun: fun, args: args, retType: retType} +} // Eval evaluates given expression expr with given transaction ID trxID. func (expr *Expr) Eval(ctx context.Context) (val Value, rollback func(), err error) { - return expr.Func.Execute(ctx, expr.Args) + return expr.fun.Execute(ctx, expr.args) } // Invert inverts this expression. // This just calls Func.InvertExpr() with arguments. func (expr *Expr) Invert() (Value, error) { - return expr.Func.InvertExpr(expr.Args) + return expr.fun.InvertExpr(expr.args) } // Type returns the type. func (expr *Expr) Type() Type { - return expr.RetType + return expr.retType } diff --git a/dsl/types/json.go b/dsl/types/json.go index 7076f620..0c4798bb 100644 --- a/dsl/types/json.go +++ b/dsl/types/json.go @@ -45,7 +45,20 @@ func (v *Null) Type() Type { // Bool is JSON boolean struct type Bool struct { - Value bool + value bool +} + +// NewBool creates Bool instance +func NewBool(value bool) *Bool { + if value { + return TrueValue + } + return FalseValue +} + +// Value returns the holding internal value +func (v *Bool) Value() bool { + return v.value } // Invert returns itself as-is. All literal types of JSON values are the same. @@ -73,7 +86,17 @@ var FalseValue = &Bool{false} // Number is JSON number struct type Number struct { - Value float64 + value float64 +} + +// Value returns the holding internal value +func (v *Number) Value() float64 { + return v.value +} + +// NewNumber creates Number instance +func NewNumber(value float64) *Number { + return &Number{value: value} } // Invert returns itself as-is. All literal types of JSON values are the same. @@ -95,7 +118,17 @@ func (v *Number) Type() Type { // String is JSON string struct type String struct { - Value string + value string +} + +// Value returns the holding internal value +func (v *String) Value() string { + return v.value +} + +// NewString creates String instance +func NewString(value string) *String { + return &String{value: value} } // Invert returns itself as-is. All literal types of JSON values are the same. @@ -117,8 +150,20 @@ func (v *String) Type() Type { // Array is JSON array struct type Array struct { - Elems []Value - ArgType Type + value []Value + argType Type +} + +// Value returns the holding internal value. +// DO NOT CHANGE THE RETURN VALUE DIRECTLY! +// Copy the slice before changing the value. +func (v *Array) Value() []Value { + return v.value +} + +// NewArray creates Array instance +func NewArray(value []Value, argType Type) *Array { + return &Array{value: value, argType: argType} } // Invert returns itself as-is. All literal types of JSON values are the same. @@ -133,15 +178,27 @@ func (v *Array) Eval(context.Context) (val Value, rollback func(), err error) { // Type returns the type. func (v *Array) Type() Type { - return &ArrayType{Arg: v.ArgType} + return NewArrayType(v.argType) } // ================ Object ================ // Object is JSON object struct type Object struct { - Map map[string]Value - ArgType Type + value map[string]Value + argType Type +} + +// Value returns the holding internal value. +// DO NOT CHANGE THE RETURN VALUE DIRECTLY! +// Copy the map instance before changing the value. +func (v *Object) Value() map[string]Value { + return v.value +} + +// NewObject creates Object instance +func NewObject(value map[string]Value, argType Type) *Object { + return &Object{value: value, argType: argType} } // Invert returns itself as-is. All literal types of JSON values are the same. @@ -156,5 +213,5 @@ func (v *Object) Eval(context.Context) (val Value, rollback func(), err error) { // Type returns the type. func (v *Object) Type() Type { - return &ObjectType{Arg: v.ArgType} + return NewObjectType(v.argType) } diff --git a/dsl/types/trxid.go b/dsl/types/trxid.go new file mode 100644 index 00000000..78200099 --- /dev/null +++ b/dsl/types/trxid.go @@ -0,0 +1,6 @@ +package types + +// TrxID is a transaction ID, which is a serial number and directory name of +// transaction log file. +// XXX: this should be in transaction package? +type TrxID int64 diff --git a/dsl/types/types.go b/dsl/types/types.go index 5be1e8a1..02a5d9c6 100644 --- a/dsl/types/types.go +++ b/dsl/types/types.go @@ -98,17 +98,22 @@ func (*StringType) InstanceOf(t Type) bool { // ArrayType is JSON array type type ArrayType struct { - Arg Type + arg Type +} + +// NewArrayType creates ArrayType instance +func NewArrayType(arg Type) *ArrayType { + return &ArrayType{arg: arg} } func (t *ArrayType) String() string { - return "Array[" + t.Arg.String() + "]" + return "Array[" + t.arg.String() + "]" } // InstanceOf returns true if t is instance of t2 func (t *ArrayType) InstanceOf(t2 Type) bool { if array, ok := t2.(*ArrayType); ok { - return t.Arg.InstanceOf(array.Arg) + return t.arg.InstanceOf(array.arg) } return false } @@ -117,31 +122,38 @@ func (t *ArrayType) InstanceOf(t2 Type) bool { // ObjectType is JSON object type type ObjectType struct { - Arg Type + arg Type +} + +// NewObjectType creates ObjectType instance +func NewObjectType(arg Type) *ObjectType { + return &ObjectType{arg: arg} } func (t *ObjectType) String() string { - return "Object[" + t.Arg.String() + "]" + return "Object[" + t.arg.String() + "]" } // InstanceOf returns true if t is instance of t2 func (t *ObjectType) InstanceOf(t2 Type) bool { if array, ok := t2.(*ObjectType); ok { - return t.Arg.InstanceOf(array.Arg) + return t.arg.InstanceOf(array.arg) } return false } // ===================== AnyType ===================== // -// AnyType allows any type -type AnyType struct{} +// AnyValue allows any type +var AnyValue = &anyType{} + +type anyType struct{} -func (*AnyType) String() string { +func (*anyType) String() string { return "Any" } // InstanceOf always returns true -func (*AnyType) InstanceOf(_ Type) bool { +func (*anyType) InstanceOf(_ Type) bool { return true } From afb00932ba514efbc8c25fbe6ea657f283a28469 Mon Sep 17 00:00:00 2001 From: tyru Date: Fri, 20 Apr 2018 23:36:34 +0900 Subject: [PATCH 19/79] fix: fix warning message of auto-migration of lock.json --- lockjson/lockjson.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lockjson/lockjson.go b/lockjson/lockjson.go index 90899890..32c3da06 100644 --- a/lockjson/lockjson.go +++ b/lockjson/lockjson.go @@ -101,7 +101,7 @@ func read(doLog bool) (*LockJSON, error) { if lockJSON.Version < lockJSONVersion { if doLog { logger.Warnf("Performing auto-migration of lock.json: v%d -> v%d", lockJSON.Version, lockJSONVersion) - logger.Warn("Please run 'volt migrate' to migrate explicitly if it's not updated by after operations") + logger.Warn("If this warning persists, please run 'volt migrate lockjson'") } err = migrate(bytes, &lockJSON) if err != nil { From 112eddc32fcc49c4544bcee28cfdd358f080ec18 Mon Sep 17 00:00:00 2001 From: tyru Date: Fri, 20 Apr 2018 23:39:41 +0900 Subject: [PATCH 20/79] refactor: move subcmd/migrate to migration package --- {subcmd/migrate => migration}/lockjson.go | 2 +- {subcmd/migrate => migration}/migrater.go | 2 +- {subcmd/migrate => migration}/plugconf-config-func.go | 2 +- subcmd/migrate.go | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) rename {subcmd/migrate => migration}/lockjson.go (98%) rename {subcmd/migrate => migration}/migrater.go (97%) rename {subcmd/migrate => migration}/plugconf-config-func.go (99%) diff --git a/subcmd/migrate/lockjson.go b/migration/lockjson.go similarity index 98% rename from subcmd/migrate/lockjson.go rename to migration/lockjson.go index 0739c4a2..c8372b2d 100644 --- a/subcmd/migrate/lockjson.go +++ b/migration/lockjson.go @@ -1,4 +1,4 @@ -package migrate +package migration import ( "errors" diff --git a/subcmd/migrate/migrater.go b/migration/migrater.go similarity index 97% rename from subcmd/migrate/migrater.go rename to migration/migrater.go index d0c0197d..75ed8545 100644 --- a/subcmd/migrate/migrater.go +++ b/migration/migrater.go @@ -1,4 +1,4 @@ -package migrate +package migration import ( "errors" diff --git a/subcmd/migrate/plugconf-config-func.go b/migration/plugconf-config-func.go similarity index 99% rename from subcmd/migrate/plugconf-config-func.go rename to migration/plugconf-config-func.go index 6033c783..1a2ff5c7 100644 --- a/subcmd/migrate/plugconf-config-func.go +++ b/migration/plugconf-config-func.go @@ -1,4 +1,4 @@ -package migrate +package migration import ( "errors" diff --git a/subcmd/migrate.go b/subcmd/migrate.go index 0fe90a2c..1ef5de16 100644 --- a/subcmd/migrate.go +++ b/subcmd/migrate.go @@ -7,7 +7,7 @@ import ( "os" "github.com/vim-volt/volt/logger" - "github.com/vim-volt/volt/subcmd/migrate" + "github.com/vim-volt/volt/migration" ) func init() { @@ -26,7 +26,7 @@ func (cmd *migrateCmd) FlagSet() *flag.FlagSet { fs.Usage = func() { args := fs.Args() if len(args) > 0 { - m, err := migrate.GetMigrater(args[0]) + m, err := migration.GetMigrater(args[0]) if err != nil { return } @@ -72,7 +72,7 @@ func (cmd *migrateCmd) Run(args []string) *Error { return nil } -func (cmd *migrateCmd) parseArgs(args []string) (migrate.Migrater, error) { +func (cmd *migrateCmd) parseArgs(args []string) (migration.Migrater, error) { fs := cmd.FlagSet() fs.Parse(args) if cmd.helped { @@ -82,11 +82,11 @@ func (cmd *migrateCmd) parseArgs(args []string) (migrate.Migrater, error) { if len(args) == 0 { return nil, errors.New("please specify migration operation") } - return migrate.GetMigrater(args[0]) + return migration.GetMigrater(args[0]) } func (cmd *migrateCmd) showAvailableOps(write func(string)) { - for _, m := range migrate.ListMigraters() { + for _, m := range migration.ListMigraters() { write(fmt.Sprintf(" %s", m.Name())) write(fmt.Sprintf(" %s", m.Description(true))) } From 0840e9b6a225d1ccd43b5f3325e2ac645842969c Mon Sep 17 00:00:00 2001 From: tyru Date: Sat, 21 Apr 2018 00:16:38 +0900 Subject: [PATCH 21/79] fix: suppress lint errors --- internal/testutil/testutil.go | 19 +++++++++++++++++-- lockjson/migrate.go | 2 +- subcmd/buildinfo/buildinfo.go | 9 ++++++++- subcmd/get_test.go | 17 +++++++++++------ 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/internal/testutil/testutil.go b/internal/testutil/testutil.go index 0b474987..69e27726 100644 --- a/internal/testutil/testutil.go +++ b/internal/testutil/testutil.go @@ -37,10 +37,12 @@ func init() { os.RemoveAll(filepath.Join(testdataDir, "voltpath")) } +// TestdataDir returns the fullpath of "testdata" directory func TestdataDir() string { return testdataDir } +// SetUpEnv sets up environment variables related to volt. func SetUpEnv(t *testing.T) { tempDir, err := ioutil.TempDir("", "volt-test-") if err != nil { @@ -58,12 +60,16 @@ func SetUpEnv(t *testing.T) { } } +// RunVolt invokes volt command by os/exec.Command() and returns cmd.CombinedOutput() func RunVolt(args ...string) ([]byte, error) { cmd := exec.Command(voltCommand, args...) // cmd.Env = append(os.Environ(), "VOLTPATH="+voltpath) return cmd.CombinedOutput() } +// SuccessExit fails if any of the following conditions met: +// * out has "[WARN]" or "[ERROR]" +// * err is non-nil func SuccessExit(t *testing.T, out []byte, err error) { t.Helper() outstr := string(out) @@ -75,6 +81,9 @@ func SuccessExit(t *testing.T, out []byte, err error) { } } +// FailExit fails if any of the following conditions met: +// * out does not has "[WARN]" nor "[ERROR]" +// * err is nil func FailExit(t *testing.T, out []byte, err error) { t.Helper() outstr := string(out) @@ -86,7 +95,7 @@ func FailExit(t *testing.T, out []byte, err error) { } } -// Return sorted list of command names list +// GetCmdList returns sorted list of command names list func GetCmdList() ([]string, error) { out, err := RunVolt("help") if err != nil { @@ -117,7 +126,7 @@ func GetCmdList() ([]string, error) { return cmdList, nil } -// Set up $VOLTPATH after "volt get " +// SetUpRepos sets up $VOLTPATH after "volt get " // but the same repository is cloned only at first time // under testdata/voltpath/{testdataName}/repos/ func SetUpRepos(t *testing.T, testdataName string, rType lockjson.ReposType, reposPathList []pathutil.ReposPath, strategy string) func() { @@ -196,6 +205,8 @@ func SetUpRepos(t *testing.T, testdataName string, rType lockjson.ReposType, rep return func() {} } +// InstallConfig installs config file of "testdata/config/{filename}" +// to $VOLTPATH/config.toml func InstallConfig(t *testing.T, filename string) { configFile := filepath.Join(testdataDir, "config", filename) voltpath := os.Getenv("VOLTPATH") @@ -206,6 +217,9 @@ func InstallConfig(t *testing.T, filename string) { } } +// DefaultMatrix enumerates the combination of: +// * strategy (symlink,copy) +// * full (true,false) func DefaultMatrix(t *testing.T, f func(*testing.T, bool, string)) { for _, tt := range []struct { full bool @@ -222,6 +236,7 @@ func DefaultMatrix(t *testing.T, f func(*testing.T, bool, string)) { } } +// AvailableStrategies returns all avaiable strategies func AvailableStrategies() []string { return []string{config.SymlinkBuilder, config.CopyBuilder} } diff --git a/lockjson/migrate.go b/lockjson/migrate.go index 0c14dcfb..56797679 100644 --- a/lockjson/migrate.go +++ b/lockjson/migrate.go @@ -35,7 +35,7 @@ func migrate1To2(rawJSON []byte, lockJSON *LockJSON) error { return err } lockJSON.CurrentProfileName = j.ActiveProfile - lockJSON.Version += 1 + lockJSON.Version++ return nil } diff --git a/subcmd/buildinfo/buildinfo.go b/subcmd/buildinfo/buildinfo.go index 281e06cf..9cc03e40 100644 --- a/subcmd/buildinfo/buildinfo.go +++ b/subcmd/buildinfo/buildinfo.go @@ -9,14 +9,18 @@ import ( "github.com/vim-volt/volt/pathutil" ) +// BuildInfo is a struct for build-info.json, which saves the cache information +// of 'volt build'. type BuildInfo struct { Repos ReposList `json:"repos"` Version int64 `json:"version"` Strategy string `json:"strategy"` } +// ReposList = []Repos type ReposList []Repos +// Repos is a struct for repository information of build-info.json type Repos struct { Type lockjson.ReposType `json:"type"` Path pathutil.ReposPath `json:"path"` @@ -25,9 +29,10 @@ type Repos struct { DirtyWorktree bool `json:"dirty_worktree,omitempty"` } -// key: filepath, value: version +// FileMap is a map[string]string (key: filepath, value: version) type FileMap map[string]string +// Read reads build-info.json func Read() (*BuildInfo, error) { // Return initial build-info.json struct // if the file does not exist @@ -84,6 +89,7 @@ func (buildInfo *BuildInfo) validate() error { return nil } +// FindByReposPath finds reposPath from reposList func (reposList *ReposList) FindByReposPath(reposPath pathutil.ReposPath) *Repos { for i := range *reposList { repos := &(*reposList)[i] @@ -94,6 +100,7 @@ func (reposList *ReposList) FindByReposPath(reposPath pathutil.ReposPath) *Repos return nil } +// RemoveByReposPath removes reposPath from reposList func (reposList *ReposList) RemoveByReposPath(reposPath pathutil.ReposPath) { for i := range *reposList { repos := &(*reposList)[i] diff --git a/subcmd/get_test.go b/subcmd/get_test.go index a2ee3224..cd8cba6f 100644 --- a/subcmd/get_test.go +++ b/subcmd/get_test.go @@ -586,6 +586,8 @@ func gitCommitOne(reposPath pathutil.ReposPath) (prev plumbing.Hash, current plu err = errors.New("ioutil.WriteFile() failed: " + err.Error()) return } + + // Set previous HEAD hash r, err := git.PlainOpen(reposPath.FullPath()) if err != nil { return @@ -593,9 +595,10 @@ func gitCommitOne(reposPath pathutil.ReposPath) (prev plumbing.Hash, current plu head, err := r.Head() if err != nil { return - } else { - prev = head.Hash() } + prev = head.Hash() + + // Set current HEAD hash w, err := r.Worktree() if err != nil { return @@ -617,18 +620,20 @@ func gitResetHard(reposPath pathutil.ReposPath, ref string) (current plumbing.Ha if err != nil { return } + + // Set next HEAD hash head, err := r.Head() if err != nil { return - } else { - next = head.Hash() } + next = head.Hash() + + // Set current and 'git reset --hard {current}' rev, err := r.ResolveRevision(plumbing.Revision(ref)) if err != nil { return - } else { - current = *rev } + current = *rev err = w.Reset(&git.ResetOptions{ Commit: current, Mode: git.HardReset, From ec4ed57a3060b20a4e16a294ea198655ca88a059 Mon Sep 17 00:00:00 2001 From: tyru Date: Sat, 21 Apr 2018 00:19:22 +0900 Subject: [PATCH 22/79] refactor: move subcmd/buildinfo to buildinfo package --- {subcmd/buildinfo => buildinfo}/buildinfo.go | 0 subcmd/builder/base.go | 2 +- subcmd/builder/builder.go | 2 +- subcmd/builder/copy.go | 2 +- subcmd/builder/symlink.go | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename {subcmd/buildinfo => buildinfo}/buildinfo.go (100%) diff --git a/subcmd/buildinfo/buildinfo.go b/buildinfo/buildinfo.go similarity index 100% rename from subcmd/buildinfo/buildinfo.go rename to buildinfo/buildinfo.go diff --git a/subcmd/builder/base.go b/subcmd/builder/base.go index d0d2b87b..63a55c76 100644 --- a/subcmd/builder/base.go +++ b/subcmd/builder/base.go @@ -10,11 +10,11 @@ import ( "strings" "github.com/hashicorp/go-multierror" + "github.com/vim-volt/volt/buildinfo" "github.com/vim-volt/volt/fileutil" "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/logger" "github.com/vim-volt/volt/pathutil" - "github.com/vim-volt/volt/subcmd/buildinfo" ) // BaseBuilder is a base struct which all builders must implement diff --git a/subcmd/builder/builder.go b/subcmd/builder/builder.go index 3730b783..e386ed78 100644 --- a/subcmd/builder/builder.go +++ b/subcmd/builder/builder.go @@ -4,10 +4,10 @@ import ( "errors" "os" + "github.com/vim-volt/volt/buildinfo" "github.com/vim-volt/volt/config" "github.com/vim-volt/volt/logger" "github.com/vim-volt/volt/pathutil" - "github.com/vim-volt/volt/subcmd/buildinfo" ) // Builder creates/updates ~/.vim/pack/volt directory diff --git a/subcmd/builder/copy.go b/subcmd/builder/copy.go index b579f62e..86022657 100644 --- a/subcmd/builder/copy.go +++ b/subcmd/builder/copy.go @@ -9,13 +9,13 @@ import ( "time" "github.com/hashicorp/go-multierror" + "github.com/vim-volt/volt/buildinfo" "github.com/vim-volt/volt/fileutil" "github.com/vim-volt/volt/gitutil" "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/logger" "github.com/vim-volt/volt/pathutil" "github.com/vim-volt/volt/plugconf" - "github.com/vim-volt/volt/subcmd/buildinfo" "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/object" diff --git a/subcmd/builder/symlink.go b/subcmd/builder/symlink.go index eb94e5e5..f2ac8789 100644 --- a/subcmd/builder/symlink.go +++ b/subcmd/builder/symlink.go @@ -11,12 +11,12 @@ import ( "gopkg.in/src-d/go-git.v4" + "github.com/vim-volt/volt/buildinfo" "github.com/vim-volt/volt/gitutil" "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/logger" "github.com/vim-volt/volt/pathutil" "github.com/vim-volt/volt/plugconf" - "github.com/vim-volt/volt/subcmd/buildinfo" ) type symlinkBuilder struct { From 69862ce4941e18c9a239755ac703f24dac8399bc Mon Sep 17 00:00:00 2001 From: tyru Date: Sat, 21 Apr 2018 01:29:43 +0900 Subject: [PATCH 23/79] refactor: don't read lock.json,config.toml in subcmd package See _docs/json-dsl.md for layered architecture: 1. (Gateway layer): pass subcommand arguments, lock.json & config.toml structure to Subcmd layer 2. (Subcmd layer): Create an AST according to given information * This layer cannot touch filesystem, because it makes unit testing difficult --- subcmd/cmd.go => gateway/gateway.go | 78 ++++++++--------- main.go | 4 +- migration/lockjson.go | 11 +-- migration/migrater.go | 5 +- migration/plugconf-config-func.go | 15 ++-- subcmd/build.go | 6 +- subcmd/builder/base.go | 4 +- subcmd/builder/builder.go | 18 ++-- subcmd/builder/copy.go | 14 +-- subcmd/builder/symlink.go | 12 +-- subcmd/disable.go | 9 +- subcmd/enable.go | 9 +- subcmd/get.go | 24 ++--- subcmd/help.go | 7 +- subcmd/list.go | 14 +-- subcmd/migrate.go | 6 +- subcmd/profile.go | 130 +++++++++++----------------- subcmd/rm.go | 18 ++-- subcmd/self_upgrade.go | 4 +- subcmd/self_upgrade_test.go | 32 ++++++- subcmd/subcmd.go | 43 +++++++++ subcmd/version.go | 4 +- 22 files changed, 230 insertions(+), 237 deletions(-) rename subcmd/cmd.go => gateway/gateway.go (52%) create mode 100644 subcmd/subcmd.go diff --git a/subcmd/cmd.go b/gateway/gateway.go similarity index 52% rename from subcmd/cmd.go rename to gateway/gateway.go index 9e1a7983..51d43cbe 100644 --- a/subcmd/cmd.go +++ b/gateway/gateway.go @@ -1,48 +1,28 @@ -package subcmd +package gateway import ( "errors" - "flag" "os" "os/user" "runtime" "github.com/vim-volt/volt/config" + "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/logger" + "github.com/vim-volt/volt/subcmd" ) -var cmdMap = make(map[string]Cmd) - -// Cmd represents volt's subcommand interface. -// All subcommands must implement this. -type Cmd interface { - ProhibitRootExecution(args []string) bool - Run(args []string) *Error - FlagSet() *flag.FlagSet -} - // RunnerFunc invokes c with args. // On unit testing, a mock function was given. -type RunnerFunc func(c Cmd, args []string) *Error - -// Error is a command error. -// It also has a exit code. -type Error struct { - Code int - Msg string -} - -func (e *Error) Error() string { - return e.Msg -} +type RunnerFunc func(c subcmd.Cmd, runctx *subcmd.RunContext) *subcmd.Error // DefaultRunner simply runs command with args -func DefaultRunner(c Cmd, args []string) *Error { - return c.Run(args) +func DefaultRunner(c subcmd.Cmd, runctx *subcmd.RunContext) *subcmd.Error { + return c.Run(runctx) } // Run is invoked by main(), each argument means 'volt {subcmd} {args}'. -func Run(args []string, cont RunnerFunc) *Error { +func Run(args []string, cont RunnerFunc) *subcmd.Error { if os.Getenv("VOLT_DEBUG") != "" { logger.SetLevel(logger.DebugLevel) } @@ -50,41 +30,55 @@ func Run(args []string, cont RunnerFunc) *Error { if len(args) <= 1 { args = append(args, "help") } - subCmd := args[1] + cmdname := args[1] args = args[2:] + // Read config.toml + cfg, err := config.Read() + if err != nil { + err = errors.New("could not read config.toml: " + err.Error()) + return &subcmd.Error{Code: 2, Msg: err.Error()} + } + // Expand subcommand alias - subCmd, args, err := expandAlias(subCmd, args) + cmdname, args, err = expandAlias(cmdname, args, cfg) if err != nil { - return &Error{Code: 1, Msg: err.Error()} + return &subcmd.Error{Code: 1, Msg: err.Error()} } - c, exists := cmdMap[subCmd] + c, exists := subcmd.LookupSubcmd(cmdname) if !exists { - return &Error{Code: 3, Msg: "Unknown command '" + subCmd + "'"} + return &subcmd.Error{Code: 3, Msg: "Unknown command '" + cmdname + "'"} } // Disallow executing the commands which may modify files in root priviledge if c.ProhibitRootExecution(args) { err := detectPriviledgedUser() if err != nil { - return &Error{Code: 4, Msg: err.Error()} + return &subcmd.Error{Code: 4, Msg: err.Error()} } } - return cont(c, args) -} - -func expandAlias(subCmd string, args []string) (string, []string, error) { - cfg, err := config.Read() + // Read lock.json + lockJSON, err := lockjson.Read() if err != nil { - return "", nil, errors.New("could not read config.toml: " + err.Error()) + err = errors.New("failed to read lock.json: " + err.Error()) + return &subcmd.Error{Code: 5, Msg: err.Error()} } - if newArgs, exists := cfg.Alias[subCmd]; exists && len(newArgs) > 0 { - subCmd = newArgs[0] + + return cont(c, &subcmd.RunContext{ + Args: args, + LockJSON: lockJSON, + Config: cfg, + }) +} + +func expandAlias(cmdname string, args []string, cfg *config.Config) (string, []string, error) { + if newArgs, exists := cfg.Alias[cmdname]; exists && len(newArgs) > 0 { + cmdname = newArgs[0] args = append(newArgs[1:], args...) } - return subCmd, args, nil + return cmdname, args, nil } // On Windows, this function always returns nil. diff --git a/main.go b/main.go index 70334e02..900a33ef 100644 --- a/main.go +++ b/main.go @@ -5,12 +5,12 @@ package main import ( "os" + "github.com/vim-volt/volt/gateway" "github.com/vim-volt/volt/logger" - "github.com/vim-volt/volt/subcmd" ) func main() { - err := subcmd.Run(os.Args, subcmd.DefaultRunner) + err := gateway.Run(os.Args, gateway.DefaultRunner) if err != nil { logger.Error(err.Msg) os.Exit(err.Code) diff --git a/migration/lockjson.go b/migration/lockjson.go index c8372b2d..d08952e8 100644 --- a/migration/lockjson.go +++ b/migration/lockjson.go @@ -3,6 +3,7 @@ package migration import ( "errors" + "github.com/vim-volt/volt/config" "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/transaction" ) @@ -31,15 +32,9 @@ Description To suppress this, running this command simply reads and writes migrated structure to lock.json.` } -func (*lockjsonMigrater) Migrate() error { - // Read lock.json - lockJSON, err := lockjson.ReadNoMigrationMsg() - if err != nil { - return errors.New("could not read lock.json: " + err.Error()) - } - +func (*lockjsonMigrater) Migrate(lockJSON *lockjson.LockJSON, cfg *config.Config) error { // Begin transaction - err = transaction.Create() + err := transaction.Create() if err != nil { return err } diff --git a/migration/migrater.go b/migration/migrater.go index 75ed8545..f198afcb 100644 --- a/migration/migrater.go +++ b/migration/migrater.go @@ -3,11 +3,14 @@ package migration import ( "errors" "sort" + + "github.com/vim-volt/volt/config" + "github.com/vim-volt/volt/lockjson" ) // Migrater migrates many kinds of data. type Migrater interface { - Migrate() error + Migrate(*lockjson.LockJSON, *config.Config) error Name() string Description(brief bool) string } diff --git a/migration/plugconf-config-func.go b/migration/plugconf-config-func.go index 1a2ff5c7..30b5fc34 100644 --- a/migration/plugconf-config-func.go +++ b/migration/plugconf-config-func.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/vim-volt/volt/config" "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/logger" "github.com/vim-volt/volt/pathutil" @@ -39,13 +40,7 @@ Description All plugconf files are replaced with new contents.` } -func (*plugconfConfigMigrater) Migrate() error { - // Read lock.json - lockJSON, err := lockjson.ReadNoMigrationMsg() - if err != nil { - return errors.New("could not read lock.json: " + err.Error()) - } - +func (*plugconfConfigMigrater) Migrate(lockJSON *lockjson.LockJSON, cfg *config.Config) error { results, parseErr := plugconf.ParseMultiPlugconf(lockJSON.Repos) if parseErr.HasErrs() { logger.Error("Please fix the following errors before migration:") @@ -82,21 +77,21 @@ func (*plugconfConfigMigrater) Migrate() error { // After checking errors, write the content to files for _, info := range infoList { os.MkdirAll(filepath.Dir(info.path), 0755) - err = ioutil.WriteFile(info.path, info.content, 0644) + err := ioutil.WriteFile(info.path, info.content, 0644) if err != nil { return err } } // Begin transaction - err = transaction.Create() + err := transaction.Create() if err != nil { return err } defer transaction.Remove() // Build ~/.vim/pack/volt dir - err = builder.Build(false) + err = builder.Build(false, lockJSON, cfg) if err != nil { return errors.New("could not build " + pathutil.VimVoltDir() + ": " + err.Error()) } diff --git a/subcmd/build.go b/subcmd/build.go index 0b871d60..16ba8f67 100644 --- a/subcmd/build.go +++ b/subcmd/build.go @@ -53,10 +53,10 @@ Description return fs } -func (cmd *buildCmd) Run(args []string) *Error { +func (cmd *buildCmd) Run(runctx *RunContext) *Error { // Parse args fs := cmd.FlagSet() - fs.Parse(args) + fs.Parse(runctx.Args) if cmd.helped { return nil } @@ -69,7 +69,7 @@ func (cmd *buildCmd) Run(args []string) *Error { } defer transaction.Remove() - err = builder.Build(cmd.full) + err = builder.Build(cmd.full, runctx.LockJSON, runctx.Config) if err != nil { logger.Error() return &Error{Code: 12, Msg: "Failed to build: " + err.Error()} diff --git a/subcmd/builder/base.go b/subcmd/builder/base.go index 63a55c76..aec54f07 100644 --- a/subcmd/builder/base.go +++ b/subcmd/builder/base.go @@ -18,7 +18,9 @@ import ( ) // BaseBuilder is a base struct which all builders must implement -type BaseBuilder struct{} +type BaseBuilder struct { + lockJSON *lockjson.LockJSON +} func (builder *BaseBuilder) installVimrcAndGvimrc(profileName, vimrcPath, gvimrcPath string) error { // Save old vimrc file as {vimrc}.bak diff --git a/subcmd/builder/builder.go b/subcmd/builder/builder.go index e386ed78..80e26fc5 100644 --- a/subcmd/builder/builder.go +++ b/subcmd/builder/builder.go @@ -6,6 +6,7 @@ import ( "github.com/vim-volt/volt/buildinfo" "github.com/vim-volt/volt/config" + "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/logger" "github.com/vim-volt/volt/pathutil" ) @@ -18,15 +19,9 @@ type Builder interface { const currentBuildInfoVersion = 2 // Build creates/updates ~/.vim/pack/volt directory -func Build(full bool) error { - // Read config.toml - cfg, err := config.Read() - if err != nil { - return errors.New("could not read config.toml: " + err.Error()) - } - +func Build(full bool, lockJSON *lockjson.LockJSON, cfg *config.Config) error { // Get builder - blder, err := getBuilder(cfg.Build.Strategy) + blder, err := getBuilder(cfg.Build.Strategy, lockJSON) if err != nil { return err } @@ -78,12 +73,13 @@ func Build(full bool) error { return blder.Build(buildInfo, buildReposMap) } -func getBuilder(strategy string) (Builder, error) { +func getBuilder(strategy string, lockJSON *lockjson.LockJSON) (Builder, error) { + base := &BaseBuilder{lockJSON: lockJSON} switch strategy { case config.SymlinkBuilder: - return &symlinkBuilder{}, nil + return &symlinkBuilder{base}, nil case config.CopyBuilder: - return ©Builder{}, nil + return ©Builder{base}, nil default: return nil, errors.New("unknown builder type: " + strategy) } diff --git a/subcmd/builder/copy.go b/subcmd/builder/copy.go index 86022657..4265f141 100644 --- a/subcmd/builder/copy.go +++ b/subcmd/builder/copy.go @@ -22,7 +22,7 @@ import ( ) type copyBuilder struct { - BaseBuilder + *BaseBuilder } func (builder *copyBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposMap map[pathutil.ReposPath]*buildinfo.Repos) error { @@ -32,14 +32,8 @@ func (builder *copyBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposMap return err } - // Read lock.json - lockJSON, err := lockjson.Read() - if err != nil { - return errors.New("could not read lock.json: " + err.Error()) - } - // Get current profile's repos list - reposList, err := lockJSON.GetCurrentReposList() + reposList, err := builder.lockJSON.GetCurrentReposList() if err != nil { return err } @@ -50,7 +44,7 @@ func (builder *copyBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposMap vimrcPath := filepath.Join(vimDir, pathutil.Vimrc) gvimrcPath := filepath.Join(vimDir, pathutil.Gvimrc) err = builder.installVimrcAndGvimrc( - lockJSON.CurrentProfileName, vimrcPath, gvimrcPath, + builder.lockJSON.CurrentProfileName, vimrcPath, gvimrcPath, ) if err != nil { return err @@ -98,7 +92,7 @@ func (builder *copyBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposMap } // Write bundled plugconf file - rcDir := pathutil.RCDir(lockJSON.CurrentProfileName) + rcDir := pathutil.RCDir(builder.lockJSON.CurrentProfileName) vimrc := "" if path := filepath.Join(rcDir, pathutil.ProfileVimrc); pathutil.Exists(path) { vimrc = path diff --git a/subcmd/builder/symlink.go b/subcmd/builder/symlink.go index f2ac8789..20a35f74 100644 --- a/subcmd/builder/symlink.go +++ b/subcmd/builder/symlink.go @@ -20,7 +20,7 @@ import ( ) type symlinkBuilder struct { - BaseBuilder + *BaseBuilder } // TODO: rollback when return err (!= nil) @@ -31,11 +31,7 @@ func (builder *symlinkBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposM } // Get current profile's repos list - lockJSON, err := lockjson.Read() - if err != nil { - return errors.New("could not read lock.json: " + err.Error()) - } - reposList, err := lockJSON.GetCurrentReposList() + reposList, err := builder.lockJSON.GetCurrentReposList() if err != nil { return err } @@ -46,7 +42,7 @@ func (builder *symlinkBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposM vimrcPath := filepath.Join(vimDir, pathutil.Vimrc) gvimrcPath := filepath.Join(vimDir, pathutil.Gvimrc) err = builder.installVimrcAndGvimrc( - lockJSON.CurrentProfileName, vimrcPath, gvimrcPath, + builder.lockJSON.CurrentProfileName, vimrcPath, gvimrcPath, ) if err != nil { return err @@ -86,7 +82,7 @@ func (builder *symlinkBuilder) Build(buildInfo *buildinfo.BuildInfo, buildReposM } // Write bundled plugconf file - rcDir := pathutil.RCDir(lockJSON.CurrentProfileName) + rcDir := pathutil.RCDir(builder.lockJSON.CurrentProfileName) vimrc := "" if path := filepath.Join(rcDir, pathutil.ProfileVimrc); pathutil.Exists(path) { vimrc = path diff --git a/subcmd/disable.go b/subcmd/disable.go index c3177243..a9cc2cff 100644 --- a/subcmd/disable.go +++ b/subcmd/disable.go @@ -41,8 +41,8 @@ Description return fs } -func (cmd *disableCmd) Run(args []string) *Error { - reposPathList, err := cmd.parseArgs(args) +func (cmd *disableCmd) Run(runctx *RunContext) *Error { + reposPathList, err := cmd.parseArgs(runctx.Args) if err == ErrShowedHelp { return nil } @@ -51,10 +51,11 @@ func (cmd *disableCmd) Run(args []string) *Error { } profCmd := profileCmd{} - err = profCmd.doRm(append( + runctx.Args = append( []string{"-current"}, reposPathList.Strings()..., - )) + ) + err = profCmd.doRm(runctx) if err != nil { return &Error{Code: 11, Msg: err.Error()} } diff --git a/subcmd/enable.go b/subcmd/enable.go index e51c8186..35f445a5 100644 --- a/subcmd/enable.go +++ b/subcmd/enable.go @@ -41,8 +41,8 @@ Description return fs } -func (cmd *enableCmd) Run(args []string) *Error { - reposPathList, err := cmd.parseArgs(args) +func (cmd *enableCmd) Run(runctx *RunContext) *Error { + reposPathList, err := cmd.parseArgs(runctx.Args) if err == ErrShowedHelp { return nil } @@ -51,10 +51,11 @@ func (cmd *enableCmd) Run(args []string) *Error { } profCmd := profileCmd{} - err = profCmd.doAdd(append( + runctx.Args = append( []string{"-current"}, reposPathList.Strings()..., - )) + ) + err = profCmd.doAdd(runctx) if err != nil { return &Error{Code: 11, Msg: err.Error()} } diff --git a/subcmd/get.go b/subcmd/get.go index b20796f7..017c8681 100644 --- a/subcmd/get.go +++ b/subcmd/get.go @@ -115,9 +115,9 @@ Options`) return fs } -func (cmd *getCmd) Run(args []string) *Error { +func (cmd *getCmd) Run(runctx *RunContext) *Error { // Parse args - args, err := cmd.parseArgs(args) + args, err := cmd.parseArgs(runctx.Args) if err == ErrShowedHelp { return nil } @@ -125,13 +125,7 @@ func (cmd *getCmd) Run(args []string) *Error { return &Error{Code: 10, Msg: "Failed to parse args: " + err.Error()} } - // Read lock.json - lockJSON, err := lockjson.Read() - if err != nil { - return &Error{Code: 11, Msg: "Could not read lock.json: " + err.Error()} - } - - reposPathList, err := cmd.getReposPathList(args, lockJSON) + reposPathList, err := cmd.getReposPathList(args, runctx.LockJSON) if err != nil { return &Error{Code: 12, Msg: "Could not get repos list: " + err.Error()} } @@ -139,7 +133,7 @@ func (cmd *getCmd) Run(args []string) *Error { return &Error{Code: 13, Msg: "No repositories are specified"} } - err = cmd.doGet(reposPathList, lockJSON) + err = cmd.doGet(reposPathList, runctx.LockJSON, runctx.Config) if err != nil { return &Error{Code: 20, Msg: err.Error()} } @@ -186,7 +180,7 @@ func (cmd *getCmd) getReposPathList(args []string, lockJSON *lockjson.LockJSON) return reposPathList, nil } -func (cmd *getCmd) doGet(reposPathList []pathutil.ReposPath, lockJSON *lockjson.LockJSON) error { +func (cmd *getCmd) doGet(reposPathList []pathutil.ReposPath, lockJSON *lockjson.LockJSON, cfg *config.Config) error { // Find matching profile profile, err := lockJSON.Profiles.FindByName(lockJSON.CurrentProfileName) if err != nil { @@ -202,12 +196,6 @@ func (cmd *getCmd) doGet(reposPathList []pathutil.ReposPath, lockJSON *lockjson. } defer transaction.Remove() - // Read config.toml - cfg, err := config.Read() - if err != nil { - return errors.New("could not read config.toml: " + err.Error()) - } - done := make(chan getParallelResult, len(reposPathList)) getCount := 0 // Invoke installing / upgrading tasks @@ -254,7 +242,7 @@ func (cmd *getCmd) doGet(reposPathList []pathutil.ReposPath, lockJSON *lockjson. } // Build ~/.vim/pack/volt dir - err = builder.Build(false) + err = builder.Build(false, lockJSON, cfg) if err != nil { return errors.New("could not build " + pathutil.VimVoltDir() + ": " + err.Error()) } diff --git a/subcmd/help.go b/subcmd/help.go index 49bf679a..56884ae2 100644 --- a/subcmd/help.go +++ b/subcmd/help.go @@ -99,7 +99,8 @@ Command return fs } -func (cmd *helpCmd) Run(args []string) *Error { +func (cmd *helpCmd) Run(runctx *RunContext) *Error { + args := runctx.Args if len(args) == 0 { cmd.FlagSet().Usage() return nil @@ -112,7 +113,7 @@ func (cmd *helpCmd) Run(args []string) *Error { if !exists { return &Error{Code: 1, Msg: fmt.Sprintf("Unknown command '%s'", args[0])} } - args = append([]string{"-help"}, args[1:]...) - fs.Run(args) + runctx.Args = append([]string{"-help"}, args[1:]...) + fs.Run(runctx) return nil } diff --git a/subcmd/list.go b/subcmd/list.go index f3578547..dd463e9b 100644 --- a/subcmd/list.go +++ b/subcmd/list.go @@ -2,7 +2,6 @@ package subcmd import ( "encoding/json" - "errors" "flag" "fmt" "os" @@ -125,24 +124,19 @@ repos path: ` } -func (cmd *listCmd) Run(args []string) *Error { +func (cmd *listCmd) Run(runctx *RunContext) *Error { fs := cmd.FlagSet() - fs.Parse(args) + fs.Parse(runctx.Args) if cmd.helped { return nil } - if err := cmd.list(cmd.format); err != nil { + if err := cmd.list(cmd.format, runctx.LockJSON); err != nil { return &Error{Code: 10, Msg: "Failed to render template: " + err.Error()} } return nil } -func (cmd *listCmd) list(format string) error { - // Read lock.json - lockJSON, err := lockjson.Read() - if err != nil { - return errors.New("failed to read lock.json: " + err.Error()) - } +func (cmd *listCmd) list(format string, lockJSON *lockjson.LockJSON) error { // Parse template string t, err := template.New("volt").Funcs(cmd.funcMap(lockJSON)).Parse(format) if err != nil { diff --git a/subcmd/migrate.go b/subcmd/migrate.go index 1ef5de16..92baae60 100644 --- a/subcmd/migrate.go +++ b/subcmd/migrate.go @@ -55,8 +55,8 @@ Available operations`) return fs } -func (cmd *migrateCmd) Run(args []string) *Error { - op, err := cmd.parseArgs(args) +func (cmd *migrateCmd) Run(runctx *RunContext) *Error { + op, err := cmd.parseArgs(runctx.Args) if err == ErrShowedHelp { return nil } @@ -64,7 +64,7 @@ func (cmd *migrateCmd) Run(args []string) *Error { return &Error{Code: 10, Msg: "Failed to parse args: " + err.Error()} } - if err := op.Migrate(); err != nil { + if err := op.Migrate(runctx.LockJSON, runctx.Config); err != nil { return &Error{Code: 11, Msg: "Failed to migrate: " + err.Error()} } diff --git a/subcmd/profile.go b/subcmd/profile.go index 6fb4a6c5..db9b0f4d 100644 --- a/subcmd/profile.go +++ b/subcmd/profile.go @@ -99,9 +99,9 @@ Quick example return fs } -func (cmd *profileCmd) Run(args []string) *Error { +func (cmd *profileCmd) Run(runctx *RunContext) *Error { // Parse args - args, err := cmd.parseArgs(args) + args, err := cmd.parseArgs(runctx.Args) if err == ErrShowedHelp { return nil } @@ -110,23 +110,24 @@ func (cmd *profileCmd) Run(args []string) *Error { } subCmd := args[0] + runctx.Args = args[1:] switch subCmd { case "set": - err = cmd.doSet(args[1:]) + err = cmd.doSet(runctx) case "show": - err = cmd.doShow(args[1:]) + err = cmd.doShow(runctx) case "list": - err = cmd.doList(args[1:]) + err = cmd.doList(runctx) case "new": - err = cmd.doNew(args[1:]) + err = cmd.doNew(runctx) case "destroy": - err = cmd.doDestroy(args[1:]) + err = cmd.doDestroy(runctx) case "rename": - err = cmd.doRename(args[1:]) + err = cmd.doRename(runctx) case "add": - err = cmd.doAdd(args[1:]) + err = cmd.doAdd(runctx) case "rm": - err = cmd.doRm(args[1:]) + err = cmd.doRm(runctx) default: return &Error{Code: 11, Msg: "Unknown subcommand: " + subCmd} } @@ -152,15 +153,10 @@ func (cmd *profileCmd) parseArgs(args []string) ([]string, error) { return fs.Args(), nil } -func (*profileCmd) getCurrentProfile() (string, error) { - lockJSON, err := lockjson.Read() - if err != nil { - return "", errors.New("failed to read lock.json: " + err.Error()) - } - return lockJSON.CurrentProfileName, nil -} +func (cmd *profileCmd) doSet(runctx *RunContext) error { + args := runctx.Args + lockJSON := runctx.LockJSON -func (cmd *profileCmd) doSet(args []string) error { // Parse args createProfile := false if len(args) > 0 && args[0] == "-n" { @@ -174,37 +170,27 @@ func (cmd *profileCmd) doSet(args []string) error { } profileName := args[0] - // Read lock.json - lockJSON, err := lockjson.Read() - if err != nil { - return errors.New("failed to read lock.json: " + err.Error()) - } - // Exit if current profile is same as profileName if lockJSON.CurrentProfileName == profileName { return fmt.Errorf("'%s' is current profile", profileName) } // Create given profile unless the profile exists - if _, err = lockJSON.Profiles.FindByName(profileName); err != nil { + if _, err := lockJSON.Profiles.FindByName(profileName); err != nil { if !createProfile { return err } - if err = cmd.doNew([]string{profileName}); err != nil { + runctx.Args = []string{profileName} + if err := cmd.doNew(runctx); err != nil { return err } - // Read lock.json again - lockJSON, err = lockjson.Read() - if err != nil { - return errors.New("failed to read lock.json: " + err.Error()) - } if _, err = lockJSON.Profiles.FindByName(profileName); err != nil { return err } } // Begin transaction - err = transaction.Create() + err := transaction.Create() if err != nil { return err } @@ -222,7 +208,7 @@ func (cmd *profileCmd) doSet(args []string) error { logger.Info("Changed current profile: " + profileName) // Build ~/.vim/pack/volt dir - err = builder.Build(false) + err = builder.Build(false, lockJSON, runctx.Config) if err != nil { return errors.New("could not build " + pathutil.VimVoltDir() + ": " + err.Error()) } @@ -230,19 +216,16 @@ func (cmd *profileCmd) doSet(args []string) error { return nil } -func (cmd *profileCmd) doShow(args []string) error { +func (cmd *profileCmd) doShow(runctx *RunContext) error { + args := runctx.Args + lockJSON := runctx.LockJSON + if len(args) == 0 { cmd.FlagSet().Usage() logger.Error("'volt profile show' receives profile name.") return nil } - // Read lock.json - lockJSON, err := lockjson.Read() - if err != nil { - return errors.New("failed to read lock.json: " + err.Error()) - } - var profileName string if args[0] == "-current" { profileName = lockJSON.CurrentProfileName @@ -260,18 +243,21 @@ repos path: {{ . }} {{- end -}} {{- end }} -`, profileName, profileName)) +`, profileName, profileName), lockJSON) } -func (cmd *profileCmd) doList(args []string) error { +func (cmd *profileCmd) doList(runctx *RunContext) error { return (&listCmd{}).list(` {{- range .Profiles -}} {{- if eq .Name $.CurrentProfileName -}}*{{- else }} {{ end }} {{ .Name }} {{ end -}} -`) +`, runctx.LockJSON) } -func (cmd *profileCmd) doNew(args []string) error { +func (cmd *profileCmd) doNew(runctx *RunContext) error { + args := runctx.Args + lockJSON := runctx.LockJSON + if len(args) == 0 { cmd.FlagSet().Usage() logger.Error("'volt profile new' receives profile name.") @@ -279,14 +265,8 @@ func (cmd *profileCmd) doNew(args []string) error { } profileName := args[0] - // Read lock.json - lockJSON, err := lockjson.Read() - if err != nil { - return errors.New("failed to read lock.json: " + err.Error()) - } - // Return error if profiles[]/name matches profileName - _, err = lockJSON.Profiles.FindByName(profileName) + _, err := lockJSON.Profiles.FindByName(profileName) if err == nil { return errors.New("profile '" + profileName + "' already exists") } @@ -315,21 +295,18 @@ func (cmd *profileCmd) doNew(args []string) error { return nil } -func (cmd *profileCmd) doDestroy(args []string) error { +func (cmd *profileCmd) doDestroy(runctx *RunContext) error { + args := runctx.Args + lockJSON := runctx.LockJSON + if len(args) == 0 { cmd.FlagSet().Usage() logger.Error("'volt profile destroy' receives profile name.") return nil } - // Read lock.json - lockJSON, err := lockjson.Read() - if err != nil { - return errors.New("failed to read lock.json: " + err.Error()) - } - // Begin transaction - err = transaction.Create() + err := transaction.Create() if err != nil { return err } @@ -373,7 +350,10 @@ func (cmd *profileCmd) doDestroy(args []string) error { return merr.ErrorOrNil() } -func (cmd *profileCmd) doRename(args []string) error { +func (cmd *profileCmd) doRename(runctx *RunContext) error { + args := runctx.Args + lockJSON := runctx.LockJSON + if len(args) != 2 { cmd.FlagSet().Usage() logger.Error("'volt profile rename' receives profile name.") @@ -382,12 +362,6 @@ func (cmd *profileCmd) doRename(args []string) error { oldName := args[0] newName := args[1] - // Read lock.json - lockJSON, err := lockjson.Read() - if err != nil { - return errors.New("failed to read lock.json: " + err.Error()) - } - // Return error if profiles[]/name does not match oldName index := lockJSON.Profiles.FindIndexByName(oldName) if index < 0 { @@ -400,7 +374,7 @@ func (cmd *profileCmd) doRename(args []string) error { } // Begin transaction - err = transaction.Create() + err := transaction.Create() if err != nil { return err } @@ -432,12 +406,9 @@ func (cmd *profileCmd) doRename(args []string) error { return nil } -func (cmd *profileCmd) doAdd(args []string) error { - // Read lock.json - lockJSON, err := lockjson.Read() - if err != nil { - return errors.New("failed to read lock.json: " + err.Error()) - } +func (cmd *profileCmd) doAdd(runctx *RunContext) error { + args := runctx.Args + lockJSON := runctx.LockJSON // Parse args profileName, reposPathList, err := cmd.parseAddArgs(lockJSON, "add", args) @@ -466,7 +437,7 @@ func (cmd *profileCmd) doAdd(args []string) error { } // Build ~/.vim/pack/volt dir - err = builder.Build(false) + err = builder.Build(false, lockJSON, runctx.Config) if err != nil { return errors.New("could not build " + pathutil.VimVoltDir() + ": " + err.Error()) } @@ -474,12 +445,9 @@ func (cmd *profileCmd) doAdd(args []string) error { return nil } -func (cmd *profileCmd) doRm(args []string) error { - // Read lock.json - lockJSON, err := lockjson.Read() - if err != nil { - return errors.New("failed to read lock.json: " + err.Error()) - } +func (cmd *profileCmd) doRm(runctx *RunContext) error { + args := runctx.Args + lockJSON := runctx.LockJSON // Parse args profileName, reposPathList, err := cmd.parseAddArgs(lockJSON, "rm", args) @@ -510,7 +478,7 @@ func (cmd *profileCmd) doRm(args []string) error { } // Build ~/.vim/pack/volt dir - err = builder.Build(false) + err = builder.Build(false, lockJSON, runctx.Config) if err != nil { return errors.New("could not build " + pathutil.VimVoltDir() + ": " + err.Error()) } diff --git a/subcmd/rm.go b/subcmd/rm.go index f805b2fd..a09566b3 100644 --- a/subcmd/rm.go +++ b/subcmd/rm.go @@ -62,8 +62,8 @@ Description return fs } -func (cmd *rmCmd) Run(args []string) *Error { - reposPathList, err := cmd.parseArgs(args) +func (cmd *rmCmd) Run(runctx *RunContext) *Error { + reposPathList, err := cmd.parseArgs(runctx.Args) if err == ErrShowedHelp { return nil } @@ -71,13 +71,13 @@ func (cmd *rmCmd) Run(args []string) *Error { return &Error{Code: 10, Msg: err.Error()} } - err = cmd.doRemove(reposPathList) + err = cmd.doRemove(reposPathList, runctx.LockJSON) if err != nil { return &Error{Code: 11, Msg: "Failed to remove repository: " + err.Error()} } // Build opt dir - err = builder.Build(false) + err = builder.Build(false, runctx.LockJSON, runctx.Config) if err != nil { return &Error{Code: 12, Msg: "Could not build " + pathutil.VimVoltDir() + ": " + err.Error()} } @@ -108,15 +108,9 @@ func (cmd *rmCmd) parseArgs(args []string) ([]pathutil.ReposPath, error) { return reposPathList, nil } -func (cmd *rmCmd) doRemove(reposPathList []pathutil.ReposPath) error { - // Read lock.json - lockJSON, err := lockjson.Read() - if err != nil { - return err - } - +func (cmd *rmCmd) doRemove(reposPathList []pathutil.ReposPath, lockJSON *lockjson.LockJSON) error { // Begin transaction - err = transaction.Create() + err := transaction.Create() if err != nil { return err } diff --git a/subcmd/self_upgrade.go b/subcmd/self_upgrade.go index be65a8fd..b35bd3ee 100644 --- a/subcmd/self_upgrade.go +++ b/subcmd/self_upgrade.go @@ -49,8 +49,8 @@ Description return fs } -func (cmd *selfUpgradeCmd) Run(args []string) *Error { - err := cmd.parseArgs(args) +func (cmd *selfUpgradeCmd) Run(runctx *RunContext) *Error { + err := cmd.parseArgs(runctx.Args) if err == ErrShowedHelp { return nil } diff --git a/subcmd/self_upgrade_test.go b/subcmd/self_upgrade_test.go index 858dd344..c53fe31d 100644 --- a/subcmd/self_upgrade_test.go +++ b/subcmd/self_upgrade_test.go @@ -5,6 +5,9 @@ import ( "os" "strings" "testing" + + "github.com/vim-volt/volt/config" + "github.com/vim-volt/volt/lockjson" ) func TestVoltSelfUpgrade(t *testing.T) { @@ -31,7 +34,7 @@ func testVoltSelfUpgradeCheckFromOldVer(t *testing.T) { var err *Error out := captureOutput(t, func() { args := []string{"volt", "self-upgrade", "-check"} - err = Run(args, DefaultRunner) + runSelfUpgrade(t, args) }) if err != nil { @@ -47,7 +50,7 @@ func testVoltSelfUpgradeCheckFromCurrentVer(t *testing.T) { var err *Error out := captureOutput(t, func() { args := []string{"volt", "self-upgrade", "-check"} - err = Run(args, DefaultRunner) + runSelfUpgrade(t, args) }) if err != nil { @@ -58,6 +61,31 @@ func testVoltSelfUpgradeCheckFromCurrentVer(t *testing.T) { } } +func runSelfUpgrade(t *testing.T, args []string) { + t.Helper() + + // Read lock.json + lockJSON, err := lockjson.Read() + if err != nil { + t.Error("failed to read lock.json: " + err.Error()) + return + } + + // Read config.toml + cfg, err := config.Read() + if err != nil { + t.Error("could not read config.toml: " + err.Error()) + return + } + + cmd := &selfUpgradeCmd{} + err = cmd.Run(&RunContext{ + Args: args, + LockJSON: lockJSON, + Config: cfg, + }) +} + func captureOutput(t *testing.T, f func()) string { r, w, err := os.Pipe() if err != nil { diff --git a/subcmd/subcmd.go b/subcmd/subcmd.go new file mode 100644 index 00000000..c703b168 --- /dev/null +++ b/subcmd/subcmd.go @@ -0,0 +1,43 @@ +package subcmd + +import ( + "flag" + + "github.com/vim-volt/volt/config" + "github.com/vim-volt/volt/lockjson" +) + +var cmdMap = make(map[string]Cmd) + +// LookupSubcmd looks up subcommand name +func LookupSubcmd(name string) (cmd Cmd, exists bool) { + cmd, exists = cmdMap[name] + return +} + +// Cmd represents volt's subcommand interface. +// All subcommands must implement this. +type Cmd interface { + ProhibitRootExecution(args []string) bool + Run(runctx *RunContext) *Error + FlagSet() *flag.FlagSet +} + +// RunContext is a struct to have data which are passed between gateway package +// and subcmd package +type RunContext struct { + Args []string + LockJSON *lockjson.LockJSON + Config *config.Config +} + +// Error is a command error. +// It also has a exit code. +type Error struct { + Code int + Msg string +} + +func (e *Error) Error() string { + return e.Msg +} diff --git a/subcmd/version.go b/subcmd/version.go index 5d2b0d6e..4ccf14ea 100644 --- a/subcmd/version.go +++ b/subcmd/version.go @@ -40,9 +40,9 @@ Description return fs } -func (cmd *versionCmd) Run(args []string) *Error { +func (cmd *versionCmd) Run(runctx *RunContext) *Error { fs := cmd.FlagSet() - fs.Parse(args) + fs.Parse(runctx.Args) if cmd.helped { return nil } From 3a307f2dabe222e56781fb8ff981217dd0261c25 Mon Sep 17 00:00:00 2001 From: tyru Date: Sat, 21 Apr 2018 02:07:05 +0900 Subject: [PATCH 24/79] fix: make op.DoOp type pointer type --- dsl/op/do.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dsl/op/do.go b/dsl/op/do.go index 400f716e..f4a6f95d 100644 --- a/dsl/op/do.go +++ b/dsl/op/do.go @@ -7,17 +7,19 @@ import ( ) func init() { - funcMap[string(DoOp)] = &DoOp + opName := doOp("do") + DoOp = &opName + funcMap["do"] = DoOp } type doOp string // DoOp is "do" operation -var DoOp doOp = "do" +var DoOp *doOp // String returns operator name func (*doOp) String() string { - return string(DoOp) + return string(*DoOp) } // Bind binds its arguments, and check if the types of values are correct. @@ -30,7 +32,7 @@ func (*doOp) Bind(args ...types.Value) (*types.Expr, error) { return nil, err } retType := args[len(args)-1].Type() - return types.NewExpr(&DoOp, args, retType), nil + return types.NewExpr(DoOp, args, retType), nil } // InvertExpr returns inverted expression: Call Value.Invert() for each argument, From 332fecba9cc875e316c95cc7c54d0625ba55d58c Mon Sep 17 00:00:00 2001 From: tyru Date: Sat, 21 Apr 2018 19:37:32 +0900 Subject: [PATCH 25/79] fix: now Parse() returns *Expr instead of Value --- dsl/parse.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dsl/parse.go b/dsl/parse.go index 143c967b..b97e7f23 100644 --- a/dsl/parse.go +++ b/dsl/parse.go @@ -13,7 +13,7 @@ import ( // 1. Split to operation and its arguments // 2. Do semantic analysis recursively for its arguments // 3. Convert to *Expr -func Parse(content []byte) (types.Value, error) { +func Parse(content []byte) (*types.Expr, error) { var value interface{} if err := json.Unmarshal(content, value); err != nil { return nil, err @@ -22,13 +22,14 @@ func Parse(content []byte) (types.Value, error) { if !ok { return nil, errors.New("top-level must be an array") } - expr, err := parseArray(array) + arrayValue, err := parseArray(array) if err != nil { return nil, err } // If expression's operator is a macro, return value may not be an array // (e.g. ["macro", 1, 2]) - if _, ok := expr.(*types.Expr); !ok { + expr, ok := arrayValue.(*types.Expr) + if !ok { return nil, errors.New("the result must be an expression") } return expr, nil From dd3f180f6b5700e9bdf696f5d13f0803a1ece020 Mon Sep 17 00:00:00 2001 From: tyru Date: Sat, 21 Apr 2018 19:38:19 +0900 Subject: [PATCH 26/79] fix: rename "@" macro to "$array" --- _docs/json-dsl.md | 62 +++++++++++++++++++++++------------------------ dsl/op/array.go | 14 ++++++----- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/_docs/json-dsl.md b/_docs/json-dsl.md index 29607815..d2e1a638 100644 --- a/_docs/json-dsl.md +++ b/_docs/json-dsl.md @@ -17,7 +17,7 @@ ["parallel", ["lockjson/add", ["repos/get", "github.com/tyru/open-browser.vim"], - ["@", "default"]], + ["$array", "default"]], ["plugconf/install", "github.com/tyru/open-browser.vim"]]], ["label", 3, @@ -25,7 +25,7 @@ ["parallel", ["lockjson/add", ["repos/get", "github.com/tyru/open-browser-github.vim"], - ["@", "default"]], + ["$array", "default"]], ["plugconf/install", "github.com/tyru/open-browser-github.vim"]]]]]] ``` @@ -36,7 +36,7 @@ * e.g. "label" * e.g. "parallel" * macro: like function, but is expanded before execution - * e.g. "@" + * e.g. "$array" * expression: the form of operator application * e.g. `["label", ...]` * e.g. `["parallel", ...]` @@ -98,10 +98,10 @@ JSON DSL is a S-expression like DSL represented as JSON format. ``` This is an application form (called "expression" in this note). -An array literal value is written using `@` operator. +An array literal value is written using `$array` operator. ```json -["@", 1, 2, 3] +["$array", 1, 2, 3] ``` This expression is evaluated to `[1, 2, 3]`. @@ -173,7 +173,7 @@ version for easiness). ["do", ["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], - ["@", "default"]], + ["$array", "default"]], ["plugconf/install", "github.com/tyru/caw.vim"]]] ``` @@ -188,7 +188,7 @@ At first, to invert the expression, `$invert` macro is used: ["do", ["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], - ["@", "default"]], + ["$array", "default"]], ["plugconf/install", "github.com/tyru/caw.vim"]]]] ``` @@ -203,7 +203,7 @@ API" section. ["do", ["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], - ["@", "default"]], + ["$array", "default"]], ["plugconf/install", "github.com/tyru/caw.vim"]]]] ``` @@ -219,7 +219,7 @@ Note that `expr1` and `expr2` becomes reversed order. ["$invert", ["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], - ["@", "default"]]]]] + ["$array", "default"]]]]] ``` And @@ -234,7 +234,7 @@ And ["plugconf/delete", ["$invert", "github.com/tyru/caw.vim"]], ["lockjson/remove", ["$invert", ["repos/get", "github.com/tyru/caw.vim"]], - ["$invert", ["@", "default"]]]]] + ["$invert", ["$array", "default"]]]]] ``` `["$invert", ["repos/get", path]]` becomes @@ -246,7 +246,7 @@ And ["plugconf/delete", ["$invert", "github.com/tyru/caw.vim"]], ["lockjson/remove", ["repos/delete", ["$invert", "github.com/tyru/caw.vim"]], - ["$invert", ["@", "default"]]]]] + ["$invert", ["$array", "default"]]]]] ``` And if `$invert` is applied to literals like string, JSON array, it just remains @@ -258,7 +258,7 @@ as-is. ["plugconf/delete", "github.com/tyru/caw.vim"], ["lockjson/remove", ["repos/delete", "github.com/tyru/caw.vim"], - ["@", "default"]]]] + ["$array", "default"]]]] ``` We can successfully evaluate the inverse expression of the first expression :) @@ -322,7 +322,7 @@ TODO: Move to Godoc. All macros has `$` prefixed name for readability. Macros are not saved in transaction log (expanded before saving). -* `["@", v1 Value, ...] Array` +* `["$array", v1 Value, ...] Array` * Returns inverse expression of given expression. * Internally, this macro calls `InvertExpr()` method of each operator struct. * What value is returned depends on each operator's `InvertExpr()` @@ -334,7 +334,7 @@ Macros are not saved in transaction log (expanded before saving). * What value is returned depends on each operator's `InvertExpr()` implementation. -* `["$eval", expr Expr[* => *]] Expr[* => *]` +* `["$expand-macro", expr Value] Value` * Evaluate `expr` at parsing time. This is useful to save evaluated value to transaction log, instead of its expression. @@ -416,9 +416,9 @@ Macros are not saved in transaction log (expanded before saving). {target_hash}` * It does nothing for bare git repository. * e.g. - * `["repos/git/update", "github.com/tyru/caw.vim", ["$eval", ["repos/git/rev-parse", "HEAD", "github.com/tyru/caw.vim"]], ["$eval", ["repos/git/fetch", "github.com/tyru/caw.vim"]]]` + * `["repos/git/update", "github.com/tyru/caw.vim", ["$expand-macro", ["repos/git/rev-parse", "HEAD", "github.com/tyru/caw.vim"]], ["$expand-macro", ["repos/git/fetch", "github.com/tyru/caw.vim"]]]` * To save evaluated hash string in transaction log instead of its - expression, apply `$eval` to `repos/git/fetch` expression. + expression, apply `$expand-macro` to `repos/git/fetch` expression. * `["$invert", ["repos/git/update", path, target_hash, prev_hash]]` = `["repos/git/update", ["$invert", path], ["$invert", prev_hash], ["$invert", target_hash]]` * `["repos/git/rev-parse", str string, path ReposPath] hash string` @@ -437,7 +437,7 @@ Macros are not saved in transaction log (expanded before saving). * It fails if specified profile name does not exist. * Need to create profile before using `lockjson/profile/add`. * e.g. - * `["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], ["@", "default"]]` + * `["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], ["$array", "default"]]` * `["$invert", ["lockjson/add", repos, profiles]]` = `["lockjson/remove", ["$invert", repos], ["$invert", profiles]]` * `["lockjson/profile/add", name string] Profile` @@ -500,7 +500,7 @@ directory: "path": "github.com/tyru/caw.vim", "version": "deadbeefcafebabe" }, - ["@", "default"] + ["$array", "default"] ], ["repos/delete", "github.com/tyru/caw.vim"], ["plugconf/delete", "github.com/tyru/caw.vim"], @@ -522,7 +522,7 @@ And below is the inverse expression of above. "path": "github.com/tyru/caw.vim", "version": "deadbeefcafebabe" }, - ["@", "default"] + ["$array", "default"] ] ] ``` @@ -550,7 +550,7 @@ This is what we expected. "path": "github.com/tyru/caw.vim", "version": "deadbeefcafebabe" }, - ["@", "default"] + ["$array", "default"] ], ["repos/delete", "github.com/tyru/caw.vim"], ["plugconf/delete", "github.com/tyru/caw.vim"]]] @@ -570,7 +570,7 @@ The inverse expression of the above is: "path": "github.com/tyru/caw.vim", "version": "deadbeefcafebabe" }, - ["@", "default"]]]] + ["$array", "default"]]]] ``` 1. Installs plugconf @@ -595,7 +595,7 @@ But, of course if we placed `vimdir/with-install` at before `repos/delete` or "path": "github.com/tyru/caw.vim", "version": "deadbeefcafebabe" }, - ["@", "default"] + ["$array", "default"] ], ["repos/delete", "github.com/tyru/caw.vim"], ["plugconf/delete", "github.com/tyru/caw.vim"]] @@ -611,7 +611,7 @@ But, of course if we placed `vimdir/with-install` at before `repos/delete` or "path": "github.com/tyru/caw.vim", "version": "deadbeefcafebabe" }, - ["@", "default"]], + ["$array", "default"]], ["vimdir/with-install", ["github.com/tyru/caw.vim"], "dummy"]] @@ -630,7 +630,7 @@ Here is the simple JSON to install ["do", ["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], - ["@", "default"]], + ["$array", "default"]], ["plugconf/install", "github.com/tyru/caw.vim"]]] ``` @@ -642,7 +642,7 @@ Here is the inverse expression of above. ["plugconf/delete", "github.com/tyru/caw.vim"], ["lockjson/remove", ["repos/delete", "github.com/tyru/caw.vim"], - ["@", "default"]]]] + ["$array", "default"]]]] ``` Here is the JSON to install plugins from local directory (static repository). @@ -651,7 +651,7 @@ Here is the JSON to install plugins from local directory (static repository). ["vimdir/with-install", ["lockjson/add", { ... (repository information of local directory) ... }, - ["@", "default"]], + ["$array", "default"]], ["plugconf/install", "localhost/local/myplugin"]] ``` @@ -662,7 +662,7 @@ Here is the inverse expression of above. ["plugconf/delete", "localhost/local/myplugin"], ["lockjson/remove", { ... (repository information of local directory) ... }, - ["@", "default"]]] + ["$array", "default"]]] ``` ### Label examples @@ -678,7 +678,7 @@ Here is the simple example of installing ["do", ["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], - ["@", "default"]], + ["$array", "default"]], ["plugconf/install", "github.com/tyru/caw.vim"]]]] ``` @@ -698,7 +698,7 @@ Note that: ["plugconf/install", "github.com/tyru/caw.vim"], ["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], - ["@", "default"]]]]] + ["$array", "default"]]]]] ``` Here is more complex example to install two plugins "tyru/open-browser.vim", @@ -716,7 +716,7 @@ Here is more complex example to install two plugins "tyru/open-browser.vim", ["parallel", ["lockjson/add", ["repos/get", "github.com/tyru/open-browser.vim"], - ["@", "default"]], + ["$array", "default"]], ["plugconf/install", "github.com/tyru/open-browser.vim"]]], ["label", 3, @@ -724,7 +724,7 @@ Here is more complex example to install two plugins "tyru/open-browser.vim", ["parallel", ["lockjson/add", ["repos/get", "github.com/tyru/open-browser-github.vim"], - ["@", "default"]], + ["$array", "default"]], ["plugconf/install", "github.com/tyru/open-browser-github.vim"]]]]]] ``` diff --git a/dsl/op/array.go b/dsl/op/array.go index e7fdc977..801a737a 100644 --- a/dsl/op/array.go +++ b/dsl/op/array.go @@ -5,20 +5,22 @@ import ( ) func init() { - macroMap[string(ArrayOp)] = &ArrayOp + s := arrayOp("$array") + ArrayOp = &s + macroMap[string(*ArrayOp)] = ArrayOp } type arrayOp string -// ArrayOp is "@" operation -var ArrayOp arrayOp = "@" +// ArrayOp is "$array" operation +var ArrayOp *arrayOp -// String returns "@" +// String returns "$array" func (*arrayOp) String() string { - return string(ArrayOp) + return string(*ArrayOp) } -// Execute executes "@" operation +// Execute executes "$array" operation func (*arrayOp) Expand(args []types.Value) (types.Value, error) { return types.NewArray(args, types.AnyValue), nil } From 9a2cbccafce43f93a830ad8f9d011eb4938870c9 Mon Sep 17 00:00:00 2001 From: tyru Date: Sat, 21 Apr 2018 19:38:44 +0900 Subject: [PATCH 27/79] fix: change InvertOp type --- dsl/op/invert.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dsl/op/invert.go b/dsl/op/invert.go index 4415206d..a4e10c2f 100644 --- a/dsl/op/invert.go +++ b/dsl/op/invert.go @@ -5,17 +5,19 @@ import ( ) func init() { - macroMap[string(InvertOp)] = &InvertOp + s := invertOp("$invert") + InvertOp = &s + macroMap[string(*InvertOp)] = InvertOp } type invertOp string // InvertOp is "$invert" operation -var InvertOp invertOp = "$invert" +var InvertOp *invertOp // String returns "$invert" func (*invertOp) String() string { - return string(InvertOp) + return string(*InvertOp) } // Execute executes "$invert" operation From 6b6d3fb5de0fb800154fe4fdef8d2eed2ecd50c1 Mon Sep 17 00:00:00 2001 From: tyru Date: Sat, 21 Apr 2018 19:57:27 +0900 Subject: [PATCH 28/79] fix: change Macro.Expand() signature --- dsl/op/array.go | 4 ++-- dsl/op/invert.go | 7 ++++--- dsl/op/rollback.go | 3 +++ dsl/parse.go | 3 ++- dsl/types/op.go | 2 +- 5 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 dsl/op/rollback.go diff --git a/dsl/op/array.go b/dsl/op/array.go index 801a737a..de1df87d 100644 --- a/dsl/op/array.go +++ b/dsl/op/array.go @@ -21,6 +21,6 @@ func (*arrayOp) String() string { } // Execute executes "$array" operation -func (*arrayOp) Expand(args []types.Value) (types.Value, error) { - return types.NewArray(args, types.AnyValue), nil +func (*arrayOp) Expand(args []types.Value) (types.Value, func(), error) { + return types.NewArray(args, types.AnyValue), noRollback, nil } diff --git a/dsl/op/invert.go b/dsl/op/invert.go index a4e10c2f..94abb549 100644 --- a/dsl/op/invert.go +++ b/dsl/op/invert.go @@ -21,9 +21,10 @@ func (*invertOp) String() string { } // Execute executes "$invert" operation -func (*invertOp) Expand(args []types.Value) (types.Value, error) { +func (*invertOp) Expand(args []types.Value) (types.Value, func(), error) { if err := signature(types.AnyValue).check(args); err != nil { - return nil, err + return nil, noRollback, err } - return args[0].Invert() + val, err := args[0].Invert() + return val, noRollback, err } diff --git a/dsl/op/rollback.go b/dsl/op/rollback.go new file mode 100644 index 00000000..63f96a76 --- /dev/null +++ b/dsl/op/rollback.go @@ -0,0 +1,3 @@ +package op + +var noRollback = func() {} diff --git a/dsl/parse.go b/dsl/parse.go index b97e7f23..09c911a6 100644 --- a/dsl/parse.go +++ b/dsl/parse.go @@ -52,7 +52,8 @@ func parseArray(array []interface{}) (types.Value, error) { args = append(args, v) } if macro, exists := op.LookupMacro(opName); exists { - return macro.Expand(args) + val, _, err := macro.Expand(args) + return val, err } if fn, exists := op.LookupFunc(opName); exists { return fn.Bind(args...) diff --git a/dsl/types/op.go b/dsl/types/op.go index add10f93..751d76d3 100644 --- a/dsl/types/op.go +++ b/dsl/types/op.go @@ -24,5 +24,5 @@ type Macro interface { // Expand expands this expression (operator + args). // If argument type or arity is different, this returns non-nil error. - Expand(args []Value) (Value, error) + Expand(args []Value) (val Value, rollback func(), err error) } From 4e0ef2fad3df5eae9cde0f75f2c3fe8a3c29b55b Mon Sep 17 00:00:00 2001 From: tyru Date: Sat, 21 Apr 2018 19:58:34 +0900 Subject: [PATCH 29/79] feat: add "$expand-macro" macro --- dsl/op/expand_macro.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 dsl/op/expand_macro.go diff --git a/dsl/op/expand_macro.go b/dsl/op/expand_macro.go new file mode 100644 index 00000000..98494a0b --- /dev/null +++ b/dsl/op/expand_macro.go @@ -0,0 +1,31 @@ +package op + +import ( + "context" + + "github.com/vim-volt/volt/dsl/types" +) + +func init() { + s := expandMacroOp("$expand-macro") + ExpandMacroOp = &s + macroMap[string(*ExpandMacroOp)] = ExpandMacroOp +} + +type expandMacroOp string + +// ExpandMacroOp is "$expand-macro" operation +var ExpandMacroOp *expandMacroOp + +// String returns "$expand-macro" +func (*expandMacroOp) String() string { + return string(*ArrayOp) +} + +// Execute executes "$expand-macro" operation +func (*expandMacroOp) Expand(args []types.Value) (types.Value, func(), error) { + if err := signature(types.AnyValue).check(args); err != nil { + return nil, noRollback, err + } + return args[0].Eval(context.Background()) +} From 15ee38ff88e6b108b4cd7a194797c3b349d7993a Mon Sep 17 00:00:00 2001 From: tyru Date: Sat, 21 Apr 2018 21:43:28 +0900 Subject: [PATCH 30/79] fix: change noRollback to NoRollback --- dsl/op/array.go | 2 +- dsl/op/expand_macro.go | 2 +- dsl/op/invert.go | 4 ++-- dsl/op/rollback.go | 4 +++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/dsl/op/array.go b/dsl/op/array.go index de1df87d..86ad7713 100644 --- a/dsl/op/array.go +++ b/dsl/op/array.go @@ -22,5 +22,5 @@ func (*arrayOp) String() string { // Execute executes "$array" operation func (*arrayOp) Expand(args []types.Value) (types.Value, func(), error) { - return types.NewArray(args, types.AnyValue), noRollback, nil + return types.NewArray(args, types.AnyValue), NoRollback, nil } diff --git a/dsl/op/expand_macro.go b/dsl/op/expand_macro.go index 98494a0b..33b56944 100644 --- a/dsl/op/expand_macro.go +++ b/dsl/op/expand_macro.go @@ -25,7 +25,7 @@ func (*expandMacroOp) String() string { // Execute executes "$expand-macro" operation func (*expandMacroOp) Expand(args []types.Value) (types.Value, func(), error) { if err := signature(types.AnyValue).check(args); err != nil { - return nil, noRollback, err + return nil, NoRollback, err } return args[0].Eval(context.Background()) } diff --git a/dsl/op/invert.go b/dsl/op/invert.go index 94abb549..b1892150 100644 --- a/dsl/op/invert.go +++ b/dsl/op/invert.go @@ -23,8 +23,8 @@ func (*invertOp) String() string { // Execute executes "$invert" operation func (*invertOp) Expand(args []types.Value) (types.Value, func(), error) { if err := signature(types.AnyValue).check(args); err != nil { - return nil, noRollback, err + return nil, NoRollback, err } val, err := args[0].Invert() - return val, noRollback, err + return val, NoRollback, err } diff --git a/dsl/op/rollback.go b/dsl/op/rollback.go index 63f96a76..48f9e223 100644 --- a/dsl/op/rollback.go +++ b/dsl/op/rollback.go @@ -1,3 +1,5 @@ package op -var noRollback = func() {} +// NoRollback is an empty function. +// intepreter can skip this function. +var NoRollback = func() {} From 326647b20cb7ba31d54ab8fb9fd2f6f4227ce8aa Mon Sep 17 00:00:00 2001 From: tyru Date: Sat, 21 Apr 2018 21:44:30 +0900 Subject: [PATCH 31/79] feat: implement new simple transaction algorithm If `os.Mkdir("$VOLTPATH/trx/lock", 0755)` returns nil error, It means it could start transaction successfully. --- dsl/execute.go | 25 ++++--- dsl/types/trxid.go | 6 -- migration/lockjson.go | 10 ++- migration/plugconf-config-func.go | 15 ++-- pathutil/pathutil.go | 6 +- subcmd/build.go | 10 ++- subcmd/get.go | 10 ++- subcmd/profile.go | 50 +++++++++---- subcmd/rm.go | 10 ++- transaction/transaction.go | 120 +++++++++++++++++++----------- 10 files changed, 166 insertions(+), 96 deletions(-) delete mode 100644 dsl/types/trxid.go diff --git a/dsl/execute.go b/dsl/execute.go index d1dcf89a..5f132237 100644 --- a/dsl/execute.go +++ b/dsl/execute.go @@ -2,8 +2,9 @@ package dsl import ( "context" - "errors" + "github.com/pkg/errors" + "github.com/vim-volt/volt/dsl/op" "github.com/vim-volt/volt/dsl/types" ) @@ -21,17 +22,17 @@ const ( // Execute executes given expr with given ctx. func Execute(ctx context.Context, expr *types.Expr) (val types.Value, rollback func(), err error) { - ctx = context.WithValue(ctx, CtxTrxIDKey, genNewTrxID()) - if ctx.Value(CtxLockJSONKey) == nil { - return nil, func() {}, errors.New("no lock.json key in context") - } - if ctx.Value(CtxConfigKey) == nil { - return nil, func() {}, errors.New("no config.toml key in context") + for _, st := range []struct { + key CtxKeyType + name string + }{ + {CtxLockJSONKey, "lock.json"}, + {CtxConfigKey, "config.toml"}, + {CtxTrxIDKey, "transaction ID"}, + } { + if ctx.Value(st.key) == nil { + return nil, op.NoRollback, errors.Errorf("no %s key in context", st.name) + } } return expr.Eval(ctx) } - -func genNewTrxID() types.TrxID { - // TODO: Get unallocated transaction ID looking $VOLTPATH/trx/ directory - return types.TrxID(0) -} diff --git a/dsl/types/trxid.go b/dsl/types/trxid.go deleted file mode 100644 index 78200099..00000000 --- a/dsl/types/trxid.go +++ /dev/null @@ -1,6 +0,0 @@ -package types - -// TrxID is a transaction ID, which is a serial number and directory name of -// transaction log file. -// XXX: this should be in transaction package? -type TrxID int64 diff --git a/migration/lockjson.go b/migration/lockjson.go index d08952e8..485e1200 100644 --- a/migration/lockjson.go +++ b/migration/lockjson.go @@ -32,13 +32,17 @@ Description To suppress this, running this command simply reads and writes migrated structure to lock.json.` } -func (*lockjsonMigrater) Migrate(lockJSON *lockjson.LockJSON, cfg *config.Config) error { +func (*lockjsonMigrater) Migrate(lockJSON *lockjson.LockJSON, cfg *config.Config) (result error) { // Begin transaction - err := transaction.Create() + trx, err := transaction.Start() if err != nil { return err } - defer transaction.Remove() + defer func() { + if err := trx.Done(); err != nil { + result = err + } + }() // Write to lock.json err = lockJSON.Write() diff --git a/migration/plugconf-config-func.go b/migration/plugconf-config-func.go index 30b5fc34..fa856f84 100644 --- a/migration/plugconf-config-func.go +++ b/migration/plugconf-config-func.go @@ -1,12 +1,12 @@ package migration import ( - "errors" "io/ioutil" "os" "path/filepath" "strings" + "github.com/pkg/errors" "github.com/vim-volt/volt/config" "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/logger" @@ -40,7 +40,7 @@ Description All plugconf files are replaced with new contents.` } -func (*plugconfConfigMigrater) Migrate(lockJSON *lockjson.LockJSON, cfg *config.Config) error { +func (*plugconfConfigMigrater) Migrate(lockJSON *lockjson.LockJSON, cfg *config.Config) (result error) { results, parseErr := plugconf.ParseMultiPlugconf(lockJSON.Repos) if parseErr.HasErrs() { logger.Error("Please fix the following errors before migration:") @@ -84,17 +84,20 @@ func (*plugconfConfigMigrater) Migrate(lockJSON *lockjson.LockJSON, cfg *config. } // Begin transaction - err := transaction.Create() + trx, err := transaction.Start() if err != nil { return err } - defer transaction.Remove() + defer func() { + if err := trx.Done(); err != nil { + result = err + } + }() // Build ~/.vim/pack/volt dir err = builder.Build(false, lockJSON, cfg) if err != nil { - return errors.New("could not build " + pathutil.VimVoltDir() + ": " + err.Error()) + return errors.Wrap(err, "could not build "+pathutil.VimVoltDir()) } - return nil } diff --git a/pathutil/pathutil.go b/pathutil/pathutil.go index a48890cb..8108b050 100644 --- a/pathutil/pathutil.go +++ b/pathutil/pathutil.go @@ -169,9 +169,9 @@ func ConfigTOML() string { return filepath.Join(VoltPath(), "config.toml") } -// TrxLock returns fullpath of "$HOME/volt/trx.lock". -func TrxLock() string { - return filepath.Join(VoltPath(), "trx.lock") +// TrxDir returns fullpath of "$HOME/volt/trx". +func TrxDir() string { + return filepath.Join(VoltPath(), "trx") } // TempDir returns fullpath of "$HOME/tmp". diff --git a/subcmd/build.go b/subcmd/build.go index 16ba8f67..5b677faa 100644 --- a/subcmd/build.go +++ b/subcmd/build.go @@ -53,7 +53,7 @@ Description return fs } -func (cmd *buildCmd) Run(runctx *RunContext) *Error { +func (cmd *buildCmd) Run(runctx *RunContext) (cmdErr *Error) { // Parse args fs := cmd.FlagSet() fs.Parse(runctx.Args) @@ -62,12 +62,16 @@ func (cmd *buildCmd) Run(runctx *RunContext) *Error { } // Begin transaction - err := transaction.Create() + trx, err := transaction.Start() if err != nil { logger.Error() return &Error{Code: 11, Msg: "Failed to begin transaction: " + err.Error()} } - defer transaction.Remove() + defer func() { + if err := trx.Done(); err != nil { + cmdErr = &Error{Code: 13, Msg: "Failed to end transaction: " + err.Error()} + } + }() err = builder.Build(cmd.full, runctx.LockJSON, runctx.Config) if err != nil { diff --git a/subcmd/get.go b/subcmd/get.go index 017c8681..597b221f 100644 --- a/subcmd/get.go +++ b/subcmd/get.go @@ -180,7 +180,7 @@ func (cmd *getCmd) getReposPathList(args []string, lockJSON *lockjson.LockJSON) return reposPathList, nil } -func (cmd *getCmd) doGet(reposPathList []pathutil.ReposPath, lockJSON *lockjson.LockJSON, cfg *config.Config) error { +func (cmd *getCmd) doGet(reposPathList []pathutil.ReposPath, lockJSON *lockjson.LockJSON, cfg *config.Config) (result error) { // Find matching profile profile, err := lockJSON.Profiles.FindByName(lockJSON.CurrentProfileName) if err != nil { @@ -190,11 +190,15 @@ func (cmd *getCmd) doGet(reposPathList []pathutil.ReposPath, lockJSON *lockjson. } // Begin transaction - err = transaction.Create() + trx, err := transaction.Start() if err != nil { return err } - defer transaction.Remove() + defer func() { + if err := trx.Done(); err != nil { + result = err + } + }() done := make(chan getParallelResult, len(reposPathList)) getCount := 0 diff --git a/subcmd/profile.go b/subcmd/profile.go index db9b0f4d..7e4b1809 100644 --- a/subcmd/profile.go +++ b/subcmd/profile.go @@ -153,7 +153,7 @@ func (cmd *profileCmd) parseArgs(args []string) ([]string, error) { return fs.Args(), nil } -func (cmd *profileCmd) doSet(runctx *RunContext) error { +func (cmd *profileCmd) doSet(runctx *RunContext) (result error) { args := runctx.Args lockJSON := runctx.LockJSON @@ -190,11 +190,15 @@ func (cmd *profileCmd) doSet(runctx *RunContext) error { } // Begin transaction - err := transaction.Create() + trx, err := transaction.Start() if err != nil { return err } - defer transaction.Remove() + defer func() { + if err := trx.Done(); err != nil { + result = err + } + }() // Set profile name lockJSON.CurrentProfileName = profileName @@ -254,7 +258,7 @@ func (cmd *profileCmd) doList(runctx *RunContext) error { `, runctx.LockJSON) } -func (cmd *profileCmd) doNew(runctx *RunContext) error { +func (cmd *profileCmd) doNew(runctx *RunContext) (result error) { args := runctx.Args lockJSON := runctx.LockJSON @@ -272,11 +276,15 @@ func (cmd *profileCmd) doNew(runctx *RunContext) error { } // Begin transaction - err = transaction.Create() + trx, err := transaction.Start() if err != nil { return err } - defer transaction.Remove() + defer func() { + if err := trx.Done(); err != nil { + result = err + } + }() // Add profile lockJSON.Profiles = append(lockJSON.Profiles, lockjson.Profile{ @@ -295,7 +303,7 @@ func (cmd *profileCmd) doNew(runctx *RunContext) error { return nil } -func (cmd *profileCmd) doDestroy(runctx *RunContext) error { +func (cmd *profileCmd) doDestroy(runctx *RunContext) (result error) { args := runctx.Args lockJSON := runctx.LockJSON @@ -306,11 +314,15 @@ func (cmd *profileCmd) doDestroy(runctx *RunContext) error { } // Begin transaction - err := transaction.Create() + trx, err := transaction.Start() if err != nil { return err } - defer transaction.Remove() + defer func() { + if err := trx.Done(); err != nil { + result = err + } + }() var merr *multierror.Error for i := range args { @@ -350,7 +362,7 @@ func (cmd *profileCmd) doDestroy(runctx *RunContext) error { return merr.ErrorOrNil() } -func (cmd *profileCmd) doRename(runctx *RunContext) error { +func (cmd *profileCmd) doRename(runctx *RunContext) (result error) { args := runctx.Args lockJSON := runctx.LockJSON @@ -374,11 +386,15 @@ func (cmd *profileCmd) doRename(runctx *RunContext) error { } // Begin transaction - err := transaction.Create() + trx, err := transaction.Start() if err != nil { return err } - defer transaction.Remove() + defer func() { + if err := trx.Done(); err != nil { + result = err + } + }() // Rename profile names lockJSON.Profiles[index].Name = newName @@ -515,7 +531,7 @@ func (cmd *profileCmd) parseAddArgs(lockJSON *lockjson.LockJSON, subCmd string, } // Run modifyProfile and write modified structure to lock.json -func (*profileCmd) transactProfile(lockJSON *lockjson.LockJSON, profileName string, modifyProfile func(*lockjson.Profile)) (*lockjson.LockJSON, error) { +func (*profileCmd) transactProfile(lockJSON *lockjson.LockJSON, profileName string, modifyProfile func(*lockjson.Profile)) (resultLockJSON *lockjson.LockJSON, result error) { // Return error if profiles[]/name does not match profileName profile, err := lockJSON.Profiles.FindByName(profileName) if err != nil { @@ -523,11 +539,15 @@ func (*profileCmd) transactProfile(lockJSON *lockjson.LockJSON, profileName stri } // Begin transaction - err = transaction.Create() + trx, err := transaction.Start() if err != nil { return nil, err } - defer transaction.Remove() + defer func() { + if err := trx.Done(); err != nil { + result = err + } + }() modifyProfile(profile) diff --git a/subcmd/rm.go b/subcmd/rm.go index a09566b3..9b262b59 100644 --- a/subcmd/rm.go +++ b/subcmd/rm.go @@ -108,13 +108,17 @@ func (cmd *rmCmd) parseArgs(args []string) ([]pathutil.ReposPath, error) { return reposPathList, nil } -func (cmd *rmCmd) doRemove(reposPathList []pathutil.ReposPath, lockJSON *lockjson.LockJSON) error { +func (cmd *rmCmd) doRemove(reposPathList []pathutil.ReposPath, lockJSON *lockjson.LockJSON) (result error) { // Begin transaction - err := transaction.Create() + trx, err := transaction.Start() if err != nil { return err } - defer transaction.Remove() + defer func() { + if err := trx.Done(); err != nil { + result = err + } + }() // Check if specified plugins are depended by some plugins for _, reposPath := range reposPathList { diff --git a/transaction/transaction.go b/transaction/transaction.go index 97c9bc23..eab90907 100644 --- a/transaction/transaction.go +++ b/transaction/transaction.go @@ -1,68 +1,104 @@ package transaction import ( - "errors" - "io/ioutil" "os" "path/filepath" "strconv" + "strings" + "unicode" - "github.com/vim-volt/volt/logger" + "github.com/pkg/errors" "github.com/vim-volt/volt/pathutil" ) -// Create creates $VOLTPATH/trx.lock file -func Create() error { - ownPid := []byte(strconv.Itoa(os.Getpid())) - trxLockFile := pathutil.TrxLock() - - // Create trx.lock parent directories - err := os.MkdirAll(filepath.Dir(trxLockFile), 0755) - if err != nil { - return errors.New("failed to begin transaction: " + err.Error()) +// Start creates $VOLTPATH/trx/lock directory +func Start() (*Transaction, error) { + os.MkdirAll(pathutil.TrxDir(), 0755) + lockDir := filepath.Join(pathutil.TrxDir(), "lock") + if err := os.Mkdir(lockDir, 0755); err != nil { + return nil, errors.Wrap(err, "failed to begin transaction: "+lockDir+" exists: if no other volt process is currently running, this probably means a volt process crashed earlier. Make sure no other volt process is running and remove the file manually to continue") } - - // Return error if the file exists - if pathutil.Exists(trxLockFile) { - return errors.New("failed to begin transaction: " + pathutil.TrxLock() + " exists: if no other volt process is currently running, this probably means a volt process crashed earlier. Make sure no other volt process is running and remove the file manually to continue") + trxID, err := genNewTrxID() + if err != nil { + return nil, errors.Wrap(err, "could not allocate a new transaction ID") } + return &Transaction{trxID: trxID}, nil +} - // Write pid to trx.lock file - err = ioutil.WriteFile(trxLockFile, ownPid, 0644) +// genNewTrxID gets unallocated transaction ID looking $VOLTPATH/trx/ directory +func genNewTrxID() (_ TrxID, result error) { + trxDir, err := os.Open(pathutil.TrxDir()) if err != nil { - return errors.New("failed to begin transaction: " + err.Error()) + return nil, errors.Wrap(err, "could not open $VOLTPATH/trx directory") } - - // Read pid from trx.lock file - pid, err := ioutil.ReadFile(trxLockFile) + defer func() { result = trxDir.Close() }() + names, err := trxDir.Readdirnames(0) if err != nil { - return errors.New("failed to begin transaction: " + err.Error()) + return nil, errors.Wrap(err, "could not readdir of $VOLTPATH/trx directory") } - - if string(pid) != string(ownPid) { - return errors.New("transaction lock was taken by PID " + string(pid)) + var maxID TrxID + for i := range names { + if !isTrxDirName(names[i]) { + continue + } + if maxID == nil { + maxID = TrxID(names[i]) + continue + } + if greaterThan(names[i], string(maxID)) { + maxID = TrxID(names[i]) + } } - return nil + if maxID == nil { + return TrxID("1"), nil // no transaction ID directory + } + return maxID.Add(1) } -// Remove removes $VOLTPATH/trx.lock file -func Remove() { - // Read pid from trx.lock file - trxLockFile := pathutil.TrxLock() - pid, err := ioutil.ReadFile(trxLockFile) - if err != nil { - logger.Error("trx.lock was already removed") - return +func greaterThan(a, b string) bool { + d := len(a) - len(b) + if d > 0 { + b = strings.Repeat("0", d) + b + } else if d < 0 { + a = strings.Repeat("0", -d) + a } + return strings.Compare(a, b) > 0 +} - // Remove trx.lock if pid is same - if string(pid) != strconv.Itoa(os.Getpid()) { - logger.Error("Cannot remove another process's trx.lock") - return +func isTrxDirName(name string) bool { + for _, r := range name { + if !unicode.IsDigit(r) { + return false + } } - err = os.Remove(trxLockFile) + return true +} + +// TrxID is a transaction ID, which is a serial number and directory name of +// transaction log file. +type TrxID []byte + +// Add adds n to transaction ID +func (tid *TrxID) Add(n int) (TrxID, error) { + newID, err := strconv.ParseUint(string(*tid), 10, 32) if err != nil { - logger.Error("Cannot remove trx.lock: " + err.Error()) - return + return nil, err + } + if newID+uint64(n) < newID { + // TODO: compute in string? + return nil, errors.Errorf("%d + %d causes overflow", newID, n) } + return TrxID(strconv.FormatUint(newID+uint64(n), 10)), nil +} + +// Transaction provides transaction methods +type Transaction struct { + trxID TrxID +} + +// Done rename "lock" directory to "{trxid}" directory +func (trx *Transaction) Done() error { + lockDir := filepath.Join(pathutil.TrxDir(), "lock") + trxIDDir := filepath.Join(pathutil.TrxDir(), string(trx.trxID)) + return os.Rename(lockDir, trxIDDir) } From 7c1bc0e9501165b994fc24a87d6f2d70745f8e18 Mon Sep 17 00:00:00 2001 From: tyru Date: Sat, 21 Apr 2018 23:52:43 +0900 Subject: [PATCH 32/79] fix: dsl.Execute(): validate types of props in context --- dsl/execute.go | 49 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/dsl/execute.go b/dsl/execute.go index 5f132237..065c27b2 100644 --- a/dsl/execute.go +++ b/dsl/execute.go @@ -4,8 +4,11 @@ import ( "context" "github.com/pkg/errors" + "github.com/vim-volt/volt/config" "github.com/vim-volt/volt/dsl/op" "github.com/vim-volt/volt/dsl/types" + "github.com/vim-volt/volt/lockjson" + "github.com/vim-volt/volt/transaction" ) // CtxKeyType is the type of the key of context specified for Execute() @@ -22,17 +25,47 @@ const ( // Execute executes given expr with given ctx. func Execute(ctx context.Context, expr *types.Expr) (val types.Value, rollback func(), err error) { - for _, st := range []struct { - key CtxKeyType - name string + for _, required := range []struct { + key CtxKeyType + validate func(interface{}) error }{ - {CtxLockJSONKey, "lock.json"}, - {CtxConfigKey, "config.toml"}, - {CtxTrxIDKey, "transaction ID"}, + {CtxLockJSONKey, validateLockJSON}, + {CtxConfigKey, validateConfig}, + {CtxTrxIDKey, validateTrxID}, } { - if ctx.Value(st.key) == nil { - return nil, op.NoRollback, errors.Errorf("no %s key in context", st.name) + if err := required.validate(ctx.Value(required.key)); err != nil { + return nil, op.NoRollback, err } } return expr.Eval(ctx) } + +func validateLockJSON(v interface{}) error { + if v == nil { + return errors.New("no lock.json key in context") + } + if _, ok := v.(*lockjson.LockJSON); !ok { + return errors.New("invalid lock.json data in context") + } + return nil +} + +func validateConfig(v interface{}) error { + if v == nil { + return errors.New("no config.toml key in context") + } + if _, ok := v.(*config.Config); !ok { + return errors.New("invalid config.toml data in context") + } + return nil +} + +func validateTrxID(v interface{}) error { + if v == nil { + return errors.New("no transaction ID key in context") + } + if _, ok := v.(transaction.TrxID); !ok { + return errors.New("invalid transaction ID data in context") + } + return nil +} From f1ce8d8467a84336b7504f3c790d334c055f0cde Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 01:39:41 +0900 Subject: [PATCH 33/79] refactor: remove unnecessary logger.Error() --- subcmd/build.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/subcmd/build.go b/subcmd/build.go index 5b677faa..30d9df17 100644 --- a/subcmd/build.go +++ b/subcmd/build.go @@ -5,7 +5,6 @@ import ( "fmt" "os" - "github.com/vim-volt/volt/logger" "github.com/vim-volt/volt/subcmd/builder" "github.com/vim-volt/volt/transaction" ) @@ -64,7 +63,6 @@ func (cmd *buildCmd) Run(runctx *RunContext) (cmdErr *Error) { // Begin transaction trx, err := transaction.Start() if err != nil { - logger.Error() return &Error{Code: 11, Msg: "Failed to begin transaction: " + err.Error()} } defer func() { @@ -75,7 +73,6 @@ func (cmd *buildCmd) Run(runctx *RunContext) (cmdErr *Error) { err = builder.Build(cmd.full, runctx.LockJSON, runctx.Config) if err != nil { - logger.Error() return &Error{Code: 12, Msg: "Failed to build: " + err.Error()} } From cd226a5641fa0b2b0ef53e8d748917a90fd9e60a Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 02:05:04 +0900 Subject: [PATCH 34/79] refactor: move Value interface to value.go --- dsl/types/json.go | 16 ---------------- dsl/types/value.go | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 16 deletions(-) create mode 100644 dsl/types/value.go diff --git a/dsl/types/json.go b/dsl/types/json.go index 0c4798bb..c1566392 100644 --- a/dsl/types/json.go +++ b/dsl/types/json.go @@ -2,22 +2,6 @@ package types import "context" -// Value is JSON value -type Value interface { - // Invert returns inverted value/operation. - // All type values are invertible. - // Literals like string,number,... return itself as-is. - // If argument type or arity is different, this returns non-nil error. - Invert() (Value, error) - - // Eval returns a evaluated value. - // Literals like string,number,... return itself as-is. - Eval(ctx context.Context) (val Value, rollback func(), err error) - - // Type returns the type of this value. - Type() Type -} - // ================ Null ================ // Null is JSON null struct diff --git a/dsl/types/value.go b/dsl/types/value.go new file mode 100644 index 00000000..db4041a4 --- /dev/null +++ b/dsl/types/value.go @@ -0,0 +1,19 @@ +package types + +import "context" + +// Value is JSON value +type Value interface { + // Invert returns inverted value/operation. + // All type values are invertible. + // Literals like string,number,... return itself as-is. + // If argument type or arity is different, this returns non-nil error. + Invert() (Value, error) + + // Eval returns a evaluated value. + // Literals like string,number,... return itself as-is. + Eval(ctx context.Context) (val Value, rollback func(), err error) + + // Type returns the type of this value. + Type() Type +} From e57962f5fd56cd9f4fee106b7160c0eacc0f37a9 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 15:19:36 +0900 Subject: [PATCH 35/79] fix: rename $expand-macro to $eval --- _docs/json-dsl.md | 6 +++--- dsl/op/eval.go | 31 +++++++++++++++++++++++++++++++ dsl/op/expand_macro.go | 31 ------------------------------- 3 files changed, 34 insertions(+), 34 deletions(-) create mode 100644 dsl/op/eval.go delete mode 100644 dsl/op/expand_macro.go diff --git a/_docs/json-dsl.md b/_docs/json-dsl.md index d2e1a638..b8143dcb 100644 --- a/_docs/json-dsl.md +++ b/_docs/json-dsl.md @@ -334,7 +334,7 @@ Macros are not saved in transaction log (expanded before saving). * What value is returned depends on each operator's `InvertExpr()` implementation. -* `["$expand-macro", expr Value] Value` +* `["$eval", expr Value] Value` * Evaluate `expr` at parsing time. This is useful to save evaluated value to transaction log, instead of its expression. @@ -416,9 +416,9 @@ Macros are not saved in transaction log (expanded before saving). {target_hash}` * It does nothing for bare git repository. * e.g. - * `["repos/git/update", "github.com/tyru/caw.vim", ["$expand-macro", ["repos/git/rev-parse", "HEAD", "github.com/tyru/caw.vim"]], ["$expand-macro", ["repos/git/fetch", "github.com/tyru/caw.vim"]]]` + * `["repos/git/update", "github.com/tyru/caw.vim", ["$eval", ["repos/git/rev-parse", "HEAD", "github.com/tyru/caw.vim"]], ["$eval", ["repos/git/fetch", "github.com/tyru/caw.vim"]]]` * To save evaluated hash string in transaction log instead of its - expression, apply `$expand-macro` to `repos/git/fetch` expression. + expression, apply `$eval` to `repos/git/fetch` expression. * `["$invert", ["repos/git/update", path, target_hash, prev_hash]]` = `["repos/git/update", ["$invert", path], ["$invert", prev_hash], ["$invert", target_hash]]` * `["repos/git/rev-parse", str string, path ReposPath] hash string` diff --git a/dsl/op/eval.go b/dsl/op/eval.go new file mode 100644 index 00000000..246fdcd7 --- /dev/null +++ b/dsl/op/eval.go @@ -0,0 +1,31 @@ +package op + +import ( + "context" + + "github.com/vim-volt/volt/dsl/types" +) + +func init() { + s := evalOp("$eval") + EvalOp = &s + macroMap[string(*EvalOp)] = EvalOp +} + +type evalOp string + +// EvalOp is "$eval" operation +var EvalOp *evalOp + +// String returns "$eval" +func (*evalOp) String() string { + return string(*EvalOp) +} + +// Execute executes "$eval" operation +func (*evalOp) Expand(args []types.Value) (types.Value, func(), error) { + if err := signature(types.AnyValue).check(args); err != nil { + return nil, NoRollback, err + } + return args[0].Eval(context.Background()) +} diff --git a/dsl/op/expand_macro.go b/dsl/op/expand_macro.go deleted file mode 100644 index 33b56944..00000000 --- a/dsl/op/expand_macro.go +++ /dev/null @@ -1,31 +0,0 @@ -package op - -import ( - "context" - - "github.com/vim-volt/volt/dsl/types" -) - -func init() { - s := expandMacroOp("$expand-macro") - ExpandMacroOp = &s - macroMap[string(*ExpandMacroOp)] = ExpandMacroOp -} - -type expandMacroOp string - -// ExpandMacroOp is "$expand-macro" operation -var ExpandMacroOp *expandMacroOp - -// String returns "$expand-macro" -func (*expandMacroOp) String() string { - return string(*ArrayOp) -} - -// Execute executes "$expand-macro" operation -func (*expandMacroOp) Expand(args []types.Value) (types.Value, func(), error) { - if err := signature(types.AnyValue).check(args); err != nil { - return nil, NoRollback, err - } - return args[0].Eval(context.Background()) -} From 5bfb377145a9d7063d1f63ab7666330513e06480 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 15:25:29 +0900 Subject: [PATCH 36/79] doc: remove "Go API" section The note should be written in GoDoc. --- _docs/json-dsl.md | 62 +---------------------------------------------- 1 file changed, 1 insertion(+), 61 deletions(-) diff --git a/_docs/json-dsl.md b/_docs/json-dsl.md index b8143dcb..0ad32514 100644 --- a/_docs/json-dsl.md +++ b/_docs/json-dsl.md @@ -126,7 +126,7 @@ or `volt history undo` command is executed. The transaction log file does not have ID but the saved directory `{id}` does: ``` -$VOLTPATH/trx/{id}/{logfile} +$VOLTPATH/trx/{id}/log.json ``` `{id}` is called transaction ID, a simple serial number assigned `max + 1` like @@ -727,63 +727,3 @@ Here is more complex example to install two plugins "tyru/open-browser.vim", ["$array", "default"]], ["plugconf/install", "github.com/tyru/open-browser-github.vim"]]]]]] ``` - -### Go API - -* dsl package - * `Execute(Context, Expr) (val Value, rollback func(), err error)` - * Executes in new transacton. - * In the given context, if the following keys are missing, returns an error. - * lock.json information `*lockjson.LockJSON` - * config.toml information `*config.Config` - * And this function sets the below key before execution. - * Transaction ID: assigns `max + 1` - -* dsl/types package - * Value interface - * `Eval(Context) (val Value, rollback func(), err error)` - * Evaluate this value. the value of JSON literal just returns itself as-is. - * `Invert() (Value, error)` - * Returns inverse expression. - * Null struct - * implements Value - * NullType Type = 1 - * var NullValue Null - * Bool struct - * implements Value - * BoolType Type = 2 - * var TrueValue Bool - * var FalseValue Bool - * Number struct - * implements Value - * NumberType Type = 3 - * String struct - * implements Value - * StringType Type = 4 - * Array struct - * implements Value - * ArrayType Type = 5 - * Object struct - * implements Value - * ObjectType Type = 6 - * Expr struct - * implements Value - * Op Op - * Args []Value - * Type Type - -* dsl/op package - * `signature(types Type...) *sigChecker` - * `sigChecker.check(args []Value) error` - * Returns nil if the types of args match. - * Returns non-nil error otherwise. - * Op interface - * `Bind(args []Value...) (*Expr, error)` - * Binds arguments to this operator, and returns an expression. - * Checks semantics (type) of each argument. - * Sets the result type of Expr. - * `InvertExpr(args []Value) (*Expr, error)` - * Returns inverse expression. This normally inverts operator and arguments - and call Bind() (not all operators do it). - * `Execute(ctx Context, args []Value) (val Value, rollback func(), err error)` - * Executes expression (operator + args). From 59633e95caa62f73780fe0e1fa4d964bfff71c88 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 16:14:07 +0900 Subject: [PATCH 37/79] refactor: unify Func,Macro interfaces to Op interface --- dsl/deparse.go | 4 ++-- dsl/op/array.go | 27 ++++++++++++++++----------- dsl/op/do.go | 13 ++++++------- dsl/op/eval.go | 25 ++++++++++++++----------- dsl/op/invert.go | 27 ++++++++++++++++----------- dsl/op/lookup.go | 20 +++++--------------- dsl/op/macro.go | 24 ++++++++++++++++++++++++ dsl/parse.go | 15 +++++++++------ dsl/types/expr.go | 18 +++++++++--------- dsl/types/op.go | 19 ++++++------------- 10 files changed, 107 insertions(+), 85 deletions(-) create mode 100644 dsl/op/macro.go diff --git a/dsl/deparse.go b/dsl/deparse.go index b0a08ce3..36b7090c 100644 --- a/dsl/deparse.go +++ b/dsl/deparse.go @@ -41,8 +41,8 @@ func deparse(value types.Value) (interface{}, error) { case *types.Expr: a := make([]interface{}, 0, len(val.Args())+1) // Do not include "@" in array literal - if val.Func().String() != op.ArrayOp.String() { - a = append(a, types.NewString(val.Func().String())) + if val.Op().String() != op.ArrayOp.String() { + a = append(a, types.NewString(val.Op().String())) } for i := range a { v, err := deparse(val.Args()[i]) diff --git a/dsl/op/array.go b/dsl/op/array.go index 86ad7713..a90b0ddd 100644 --- a/dsl/op/array.go +++ b/dsl/op/array.go @@ -1,26 +1,31 @@ package op import ( + "context" + "github.com/vim-volt/volt/dsl/types" ) func init() { - s := arrayOp("$array") - ArrayOp = &s - macroMap[string(*ArrayOp)] = ArrayOp + opsMap[ArrayOp.String()] = ArrayOp +} + +type arrayOp struct { + macroBase } -type arrayOp string +// ArrayOp is "$array" operator +var ArrayOp = &arrayOp{macroBase("$array")} -// ArrayOp is "$array" operation -var ArrayOp *arrayOp +func (op *arrayOp) InvertExpr(args []types.Value) (types.Value, error) { + return op.macroInvertExpr(op.Execute(context.Background(), args)) +} -// String returns "$array" -func (*arrayOp) String() string { - return string(*ArrayOp) +func (*arrayOp) Bind(args ...types.Value) (*types.Expr, error) { + expr := types.NewExpr(ArrayOp, args, types.NewArrayType(types.AnyValue)) + return expr, nil } -// Execute executes "$array" operation -func (*arrayOp) Expand(args []types.Value) (types.Value, func(), error) { +func (*arrayOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { return types.NewArray(args, types.AnyValue), NoRollback, nil } diff --git a/dsl/op/do.go b/dsl/op/do.go index f4a6f95d..302657d6 100644 --- a/dsl/op/do.go +++ b/dsl/op/do.go @@ -9,7 +9,7 @@ import ( func init() { opName := doOp("do") DoOp = &opName - funcMap["do"] = DoOp + opsMap["do"] = DoOp } type doOp string @@ -17,12 +17,14 @@ type doOp string // DoOp is "do" operation var DoOp *doOp -// String returns operator name func (*doOp) String() string { return string(*DoOp) } -// Bind binds its arguments, and check if the types of values are correct. +func (*doOp) IsMacro() bool { + return false +} + func (*doOp) Bind(args ...types.Value) (*types.Expr, error) { sig := make([]types.Type, 0, len(args)) for i := 0; i < len(args); i++ { @@ -35,9 +37,7 @@ func (*doOp) Bind(args ...types.Value) (*types.Expr, error) { return types.NewExpr(DoOp, args, retType), nil } -// InvertExpr returns inverted expression: Call Value.Invert() for each argument, -// and reverse arguments order. -func (*doOp) InvertExpr(args []types.Value) (*types.Expr, error) { +func (*doOp) InvertExpr(args []types.Value) (types.Value, error) { newargs := make([]types.Value, len(args)) for i := range args { a, err := args[i].Invert() @@ -49,7 +49,6 @@ func (*doOp) InvertExpr(args []types.Value) (*types.Expr, error) { return DoOp.Bind(newargs...) } -// Execute executes "do" operation func (*doOp) Execute(ctx context.Context, args []types.Value) (val types.Value, rollback func(), err error) { g := funcGuard(DoOp.String()) defer func() { err = g.rollback(recover()) }() diff --git a/dsl/op/eval.go b/dsl/op/eval.go index 246fdcd7..2f8e9bed 100644 --- a/dsl/op/eval.go +++ b/dsl/op/eval.go @@ -7,23 +7,26 @@ import ( ) func init() { - s := evalOp("$eval") - EvalOp = &s - macroMap[string(*EvalOp)] = EvalOp + opsMap[EvalOp.String()] = EvalOp } -type evalOp string +type evalOp struct { + macroBase +} + +// EvalOp is "$eval" operator +var EvalOp = &evalOp{macroBase("$eval")} -// EvalOp is "$eval" operation -var EvalOp *evalOp +func (op *evalOp) InvertExpr(args []types.Value) (types.Value, error) { + return op.macroInvertExpr(op.Execute(context.Background(), args)) +} -// String returns "$eval" -func (*evalOp) String() string { - return string(*EvalOp) +func (*evalOp) Bind(args ...types.Value) (*types.Expr, error) { + expr := types.NewExpr(ArrayOp, args, types.NewArrayType(types.AnyValue)) + return expr, nil } -// Execute executes "$eval" operation -func (*evalOp) Expand(args []types.Value) (types.Value, func(), error) { +func (*evalOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { if err := signature(types.AnyValue).check(args); err != nil { return nil, NoRollback, err } diff --git a/dsl/op/invert.go b/dsl/op/invert.go index b1892150..956abaf9 100644 --- a/dsl/op/invert.go +++ b/dsl/op/invert.go @@ -1,27 +1,32 @@ package op import ( + "context" + "github.com/vim-volt/volt/dsl/types" ) func init() { - s := invertOp("$invert") - InvertOp = &s - macroMap[string(*InvertOp)] = InvertOp + opsMap[InvertOp.String()] = InvertOp +} + +type invertOp struct { + macroBase } -type invertOp string +// InvertOp is "$invert" operator +var InvertOp = &invertOp{macroBase("$invert")} -// InvertOp is "$invert" operation -var InvertOp *invertOp +func (op *invertOp) InvertExpr(args []types.Value) (types.Value, error) { + return op.macroInvertExpr(op.Execute(context.Background(), args)) +} -// String returns "$invert" -func (*invertOp) String() string { - return string(*InvertOp) +func (*invertOp) Bind(args ...types.Value) (*types.Expr, error) { + expr := types.NewExpr(ArrayOp, args, types.NewArrayType(types.AnyValue)) + return expr, nil } -// Execute executes "$invert" operation -func (*invertOp) Expand(args []types.Value) (types.Value, func(), error) { +func (*invertOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { if err := signature(types.AnyValue).check(args); err != nil { return nil, NoRollback, err } diff --git a/dsl/op/lookup.go b/dsl/op/lookup.go index 5d15a5d3..ce63e008 100644 --- a/dsl/op/lookup.go +++ b/dsl/op/lookup.go @@ -2,22 +2,12 @@ package op import "github.com/vim-volt/volt/dsl/types" -// funcMap holds all operation structs. +// opsMap holds all operation structs. // All operations in dsl/op/*.go sets its struct to this in init() -var funcMap map[string]types.Func +var opsMap map[string]types.Op -// LookupFunc looks up function name -func LookupFunc(name string) (types.Func, bool) { - op, exists := funcMap[name] - return op, exists -} - -// macroMap holds all operation structs. -// All operations in dsl/op/*.go sets its struct to this in init() -var macroMap map[string]types.Macro - -// LookupMacro looks up macro name -func LookupMacro(name string) (types.Macro, bool) { - op, exists := macroMap[name] +// Lookup looks up operator name +func Lookup(name string) (types.Op, bool) { + op, exists := opsMap[name] return op, exists } diff --git a/dsl/op/macro.go b/dsl/op/macro.go new file mode 100644 index 00000000..cf620404 --- /dev/null +++ b/dsl/op/macro.go @@ -0,0 +1,24 @@ +package op + +import ( + "github.com/vim-volt/volt/dsl/types" +) + +type macroBase string + +// String returns "$array" +func (m *macroBase) String() string { + return string(*m) +} + +func (*macroBase) IsMacro() bool { + return true +} + +// Invert the result of op.Execute() which expands an expression +func (*macroBase) macroInvertExpr(val types.Value, _ func(), err error) (types.Value, error) { + if err != nil { + return nil, err + } + return val.Invert() +} diff --git a/dsl/parse.go b/dsl/parse.go index 09c911a6..a96fca09 100644 --- a/dsl/parse.go +++ b/dsl/parse.go @@ -1,6 +1,7 @@ package dsl import ( + "context" "encoding/json" "errors" "fmt" @@ -51,14 +52,16 @@ func parseArray(array []interface{}) (types.Value, error) { } args = append(args, v) } - if macro, exists := op.LookupMacro(opName); exists { - val, _, err := macro.Expand(args) - return val, err + op, exists := op.Lookup(opName) + if !exists { + return nil, fmt.Errorf("no such operation '%s'", opName) } - if fn, exists := op.LookupFunc(opName); exists { - return fn.Bind(args...) + // Expand macro's expression at parsing time + if op.IsMacro() { + val, _, err := op.Execute(context.Background(), args) + return val, err } - return nil, fmt.Errorf("no such operation '%s'", opName) + return op.Bind(args...) } func parse(value interface{}) (types.Value, error) { diff --git a/dsl/types/expr.go b/dsl/types/expr.go index 612fb615..cf959782 100644 --- a/dsl/types/expr.go +++ b/dsl/types/expr.go @@ -4,14 +4,14 @@ import "context" // Expr has an operation and its arguments type Expr struct { - fun Func + op Op args []Value retType Type } -// Func returns function of Expr -func (expr *Expr) Func() Func { - return expr.fun +// Op returns operator of Expr +func (expr *Expr) Op() Op { + return expr.op } // Args returns arguments of Expr @@ -25,19 +25,19 @@ func (expr *Expr) RetType() Type { } // NewExpr creates Expr instance -func NewExpr(fun Func, args []Value, retType Type) *Expr { - return &Expr{fun: fun, args: args, retType: retType} +func NewExpr(op Op, args []Value, retType Type) *Expr { + return &Expr{op: op, args: args, retType: retType} } // Eval evaluates given expression expr with given transaction ID trxID. func (expr *Expr) Eval(ctx context.Context) (val Value, rollback func(), err error) { - return expr.fun.Execute(ctx, expr.args) + return expr.op.Execute(ctx, expr.args) } // Invert inverts this expression. -// This just calls Func.InvertExpr() with arguments. +// This just calls Op().InvertExpr() with saved arguments. func (expr *Expr) Invert() (Value, error) { - return expr.fun.InvertExpr(expr.args) + return expr.op.InvertExpr(expr.args) } // Type returns the type. diff --git a/dsl/types/op.go b/dsl/types/op.go index 751d76d3..fafeec38 100644 --- a/dsl/types/op.go +++ b/dsl/types/op.go @@ -2,27 +2,20 @@ package types import "context" -// Func is an operation of JSON DSL -type Func interface { +// Op is an operation of JSON DSL +type Op interface { // String returns function name String() string // InvertExpr returns inverted expression - InvertExpr(args []Value) (*Expr, error) + InvertExpr(args []Value) (Value, error) - // Bind binds its arguments, and check if the types of values are correct. + // Bind binds its arguments, and check if the types of values are correct Bind(args ...Value) (*Expr, error) // Execute executes this operation and returns its result value and error Execute(ctx context.Context, args []Value) (ret Value, rollback func(), err error) -} - -// Macro is an operation of JSON DSL -type Macro interface { - // String returns macro name - String() string - // Expand expands this expression (operator + args). - // If argument type or arity is different, this returns non-nil error. - Expand(args []Value) (val Value, rollback func(), err error) + // IsMacro returns true if this operator is a macro + IsMacro() bool } From cf5138b0dc2145a8ef80073461a9e62d041fd704 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 16:24:05 +0900 Subject: [PATCH 38/79] refactor: rename package dsl/op -> dsl/ops --- dsl/deparse.go | 4 ++-- dsl/execute.go | 4 ++-- dsl/{op => ops}/array.go | 2 +- dsl/{op => ops}/do.go | 2 +- dsl/{op => ops}/eval.go | 2 +- dsl/{op => ops}/guard.go | 2 +- dsl/{op => ops}/invert.go | 2 +- dsl/{op => ops}/lookup.go | 2 +- dsl/{op => ops}/macro.go | 2 +- dsl/{op => ops}/rollback.go | 2 +- dsl/{op => ops}/sigcheck.go | 2 +- dsl/parse.go | 4 ++-- 12 files changed, 15 insertions(+), 15 deletions(-) rename dsl/{op => ops}/array.go (98%) rename dsl/{op => ops}/do.go (99%) rename dsl/{op => ops}/eval.go (98%) rename dsl/{op => ops}/guard.go (98%) rename dsl/{op => ops}/invert.go (98%) rename dsl/{op => ops}/lookup.go (96%) rename dsl/{op => ops}/macro.go (97%) rename dsl/{op => ops}/rollback.go (89%) rename dsl/{op => ops}/sigcheck.go (98%) diff --git a/dsl/deparse.go b/dsl/deparse.go index 36b7090c..48f4340c 100644 --- a/dsl/deparse.go +++ b/dsl/deparse.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/vim-volt/volt/dsl/op" + "github.com/vim-volt/volt/dsl/ops" "github.com/vim-volt/volt/dsl/types" ) @@ -41,7 +41,7 @@ func deparse(value types.Value) (interface{}, error) { case *types.Expr: a := make([]interface{}, 0, len(val.Args())+1) // Do not include "@" in array literal - if val.Op().String() != op.ArrayOp.String() { + if val.Op().String() != ops.ArrayOp.String() { a = append(a, types.NewString(val.Op().String())) } for i := range a { diff --git a/dsl/execute.go b/dsl/execute.go index 065c27b2..4b3cee39 100644 --- a/dsl/execute.go +++ b/dsl/execute.go @@ -5,7 +5,7 @@ import ( "github.com/pkg/errors" "github.com/vim-volt/volt/config" - "github.com/vim-volt/volt/dsl/op" + "github.com/vim-volt/volt/dsl/ops" "github.com/vim-volt/volt/dsl/types" "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/transaction" @@ -34,7 +34,7 @@ func Execute(ctx context.Context, expr *types.Expr) (val types.Value, rollback f {CtxTrxIDKey, validateTrxID}, } { if err := required.validate(ctx.Value(required.key)); err != nil { - return nil, op.NoRollback, err + return nil, ops.NoRollback, err } } return expr.Eval(ctx) diff --git a/dsl/op/array.go b/dsl/ops/array.go similarity index 98% rename from dsl/op/array.go rename to dsl/ops/array.go index a90b0ddd..cc6cc30d 100644 --- a/dsl/op/array.go +++ b/dsl/ops/array.go @@ -1,4 +1,4 @@ -package op +package ops import ( "context" diff --git a/dsl/op/do.go b/dsl/ops/do.go similarity index 99% rename from dsl/op/do.go rename to dsl/ops/do.go index 302657d6..75cf44e4 100644 --- a/dsl/op/do.go +++ b/dsl/ops/do.go @@ -1,4 +1,4 @@ -package op +package ops import ( "context" diff --git a/dsl/op/eval.go b/dsl/ops/eval.go similarity index 98% rename from dsl/op/eval.go rename to dsl/ops/eval.go index 2f8e9bed..17cff76e 100644 --- a/dsl/op/eval.go +++ b/dsl/ops/eval.go @@ -1,4 +1,4 @@ -package op +package ops import ( "context" diff --git a/dsl/op/guard.go b/dsl/ops/guard.go similarity index 98% rename from dsl/op/guard.go rename to dsl/ops/guard.go index 877b0dbb..d7f96586 100644 --- a/dsl/op/guard.go +++ b/dsl/ops/guard.go @@ -1,4 +1,4 @@ -package op +package ops import ( "fmt" diff --git a/dsl/op/invert.go b/dsl/ops/invert.go similarity index 98% rename from dsl/op/invert.go rename to dsl/ops/invert.go index 956abaf9..e739a088 100644 --- a/dsl/op/invert.go +++ b/dsl/ops/invert.go @@ -1,4 +1,4 @@ -package op +package ops import ( "context" diff --git a/dsl/op/lookup.go b/dsl/ops/lookup.go similarity index 96% rename from dsl/op/lookup.go rename to dsl/ops/lookup.go index ce63e008..5b8176b2 100644 --- a/dsl/op/lookup.go +++ b/dsl/ops/lookup.go @@ -1,4 +1,4 @@ -package op +package ops import "github.com/vim-volt/volt/dsl/types" diff --git a/dsl/op/macro.go b/dsl/ops/macro.go similarity index 97% rename from dsl/op/macro.go rename to dsl/ops/macro.go index cf620404..07ae13e0 100644 --- a/dsl/op/macro.go +++ b/dsl/ops/macro.go @@ -1,4 +1,4 @@ -package op +package ops import ( "github.com/vim-volt/volt/dsl/types" diff --git a/dsl/op/rollback.go b/dsl/ops/rollback.go similarity index 89% rename from dsl/op/rollback.go rename to dsl/ops/rollback.go index 48f9e223..c2554dc7 100644 --- a/dsl/op/rollback.go +++ b/dsl/ops/rollback.go @@ -1,4 +1,4 @@ -package op +package ops // NoRollback is an empty function. // intepreter can skip this function. diff --git a/dsl/op/sigcheck.go b/dsl/ops/sigcheck.go similarity index 98% rename from dsl/op/sigcheck.go rename to dsl/ops/sigcheck.go index 2fe44085..fecaf61d 100644 --- a/dsl/op/sigcheck.go +++ b/dsl/ops/sigcheck.go @@ -1,4 +1,4 @@ -package op +package ops import ( "fmt" diff --git a/dsl/parse.go b/dsl/parse.go index a96fca09..0a0990a6 100644 --- a/dsl/parse.go +++ b/dsl/parse.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" - "github.com/vim-volt/volt/dsl/op" + "github.com/vim-volt/volt/dsl/ops" "github.com/vim-volt/volt/dsl/types" ) @@ -52,7 +52,7 @@ func parseArray(array []interface{}) (types.Value, error) { } args = append(args, v) } - op, exists := op.Lookup(opName) + op, exists := ops.Lookup(opName) if !exists { return nil, fmt.Errorf("no such operation '%s'", opName) } From 538dc4269f7766c033f7f650e36337d57a1b9efb Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 16:25:40 +0900 Subject: [PATCH 39/79] refactor: add prefix to filename 'macro_' or 'func_' --- dsl/ops/{do.go => func_do.go} | 0 dsl/ops/{array.go => macro_array.go} | 0 dsl/ops/{eval.go => macro_eval.go} | 0 dsl/ops/{invert.go => macro_invert.go} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename dsl/ops/{do.go => func_do.go} (100%) rename dsl/ops/{array.go => macro_array.go} (100%) rename dsl/ops/{eval.go => macro_eval.go} (100%) rename dsl/ops/{invert.go => macro_invert.go} (100%) diff --git a/dsl/ops/do.go b/dsl/ops/func_do.go similarity index 100% rename from dsl/ops/do.go rename to dsl/ops/func_do.go diff --git a/dsl/ops/array.go b/dsl/ops/macro_array.go similarity index 100% rename from dsl/ops/array.go rename to dsl/ops/macro_array.go diff --git a/dsl/ops/eval.go b/dsl/ops/macro_eval.go similarity index 100% rename from dsl/ops/eval.go rename to dsl/ops/macro_eval.go diff --git a/dsl/ops/invert.go b/dsl/ops/macro_invert.go similarity index 100% rename from dsl/ops/invert.go rename to dsl/ops/macro_invert.go From a773d5d4cc0dc463f05f5309611ad7a1a981da28 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 16:45:23 +0900 Subject: [PATCH 40/79] refactor: move SigChecker,Guard to dsl/ops/util package --- dsl/ops/func_do.go | 13 +++---- dsl/ops/guard.go | 54 ----------------------------- dsl/ops/macro_eval.go | 3 +- dsl/ops/macro_invert.go | 3 +- dsl/ops/util/guard.go | 63 ++++++++++++++++++++++++++++++++++ dsl/ops/{ => util}/sigcheck.go | 12 +++++-- 6 files changed, 83 insertions(+), 65 deletions(-) delete mode 100644 dsl/ops/guard.go create mode 100644 dsl/ops/util/guard.go rename dsl/ops/{ => util}/sigcheck.go (61%) diff --git a/dsl/ops/func_do.go b/dsl/ops/func_do.go index 75cf44e4..fee8fc92 100644 --- a/dsl/ops/func_do.go +++ b/dsl/ops/func_do.go @@ -3,6 +3,7 @@ package ops import ( "context" + "github.com/vim-volt/volt/dsl/ops/util" "github.com/vim-volt/volt/dsl/types" ) @@ -30,7 +31,7 @@ func (*doOp) Bind(args ...types.Value) (*types.Expr, error) { for i := 0; i < len(args); i++ { sig = append(sig, types.AnyValue) } - if err := signature(sig...).check(args); err != nil { + if err := util.Signature(sig...).Check(args); err != nil { return nil, err } retType := args[len(args)-1].Type() @@ -50,15 +51,15 @@ func (*doOp) InvertExpr(args []types.Value) (types.Value, error) { } func (*doOp) Execute(ctx context.Context, args []types.Value) (val types.Value, rollback func(), err error) { - g := funcGuard(DoOp.String()) - defer func() { err = g.rollback(recover()) }() - rollback = g.rollbackForcefully + g := util.FuncGuard(DoOp.String()) + defer func() { err = g.Rollback(recover()) }() + rollback = g.RollbackForcefully for i := range args { v, rbFunc, e := args[i].Eval(ctx) - g.add(rbFunc) + g.Add(rbFunc) if e != nil { - err = g.rollback(e) + err = g.Rollback(e) return } val = v diff --git a/dsl/ops/guard.go b/dsl/ops/guard.go deleted file mode 100644 index d7f96586..00000000 --- a/dsl/ops/guard.go +++ /dev/null @@ -1,54 +0,0 @@ -package ops - -import ( - "fmt" - "github.com/pkg/errors" -) - -// guard invokes "rollback functions" if rollback method received non-nil value -// (e.g. recover(), non-nil error). -type guard struct { - errMsg string - rbFuncs []func() -} - -// rollback rolls back if v is non-nil. -// -// defer func() { err = g.Rollback(recover()) }() -// -// // or -// -// if e != nil { -// err = g.Rollback(e) -// err = g.Rollback(e) // this won't call rollback functions twice! -// return -// } -func (g *guard) rollback(v interface{}) error { - var err error - if e, ok := v.(error); ok { - err = e - } else if v != nil { - err = fmt.Errorf("%s", v) - } - if err != nil { - g.rollbackForcefully() - } - return errors.Wrap(err, g.errMsg) -} - -// rollbackForcefully calls rollback functions in reversed order -func (g *guard) rollbackForcefully() { - for i := len(g.rbFuncs) - 1; i >= 0; i-- { - g.rbFuncs[i]() - } - g.rbFuncs = nil // do not rollback twice -} - -// add adds given rollback functions -func (g *guard) add(f func()) { - g.rbFuncs = append(g.rbFuncs, f) -} - -func funcGuard(name string) *guard { - return &guard{errMsg: fmt.Sprintf("function \"%s\" has an error", name)} -} diff --git a/dsl/ops/macro_eval.go b/dsl/ops/macro_eval.go index 17cff76e..6420a0b0 100644 --- a/dsl/ops/macro_eval.go +++ b/dsl/ops/macro_eval.go @@ -3,6 +3,7 @@ package ops import ( "context" + "github.com/vim-volt/volt/dsl/ops/util" "github.com/vim-volt/volt/dsl/types" ) @@ -27,7 +28,7 @@ func (*evalOp) Bind(args ...types.Value) (*types.Expr, error) { } func (*evalOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { - if err := signature(types.AnyValue).check(args); err != nil { + if err := util.Signature(types.AnyValue).Check(args); err != nil { return nil, NoRollback, err } return args[0].Eval(context.Background()) diff --git a/dsl/ops/macro_invert.go b/dsl/ops/macro_invert.go index e739a088..bd4a2e13 100644 --- a/dsl/ops/macro_invert.go +++ b/dsl/ops/macro_invert.go @@ -3,6 +3,7 @@ package ops import ( "context" + "github.com/vim-volt/volt/dsl/ops/util" "github.com/vim-volt/volt/dsl/types" ) @@ -27,7 +28,7 @@ func (*invertOp) Bind(args ...types.Value) (*types.Expr, error) { } func (*invertOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { - if err := signature(types.AnyValue).check(args); err != nil { + if err := util.Signature(types.AnyValue).Check(args); err != nil { return nil, NoRollback, err } val, err := args[0].Invert() diff --git a/dsl/ops/util/guard.go b/dsl/ops/util/guard.go new file mode 100644 index 00000000..f08d649c --- /dev/null +++ b/dsl/ops/util/guard.go @@ -0,0 +1,63 @@ +package util + +import ( + "fmt" + "github.com/pkg/errors" +) + +// Guard invokes "rollback functions" if Rollback method received non-nil value +// (e.g. recover(), non-nil error). +type Guard interface { + // Rollback rolls back if v is non-nil. + // + // defer func() { err = g.Rollback(recover()) }() + // + // // or + // + // if e != nil { + // err = g.Rollback(e) + // err = g.Rollback(e) // this won't call rollback functions twice! + // return + // } + Rollback(v interface{}) error + + // RollbackForcefully calls rollback functions in reversed order + RollbackForcefully() + + // Add adds given rollback functions + Add(f func()) +} + +// FuncGuard returns Guard instance for function +func FuncGuard(name string) Guard { + return &guard{errMsg: fmt.Sprintf("function \"%s\" has an error", name)} +} + +type guard struct { + errMsg string + rbFuncs []func() +} + +func (g *guard) Rollback(v interface{}) error { + var err error + if e, ok := v.(error); ok { + err = e + } else if v != nil { + err = fmt.Errorf("%s", v) + } + if err != nil { + g.RollbackForcefully() + } + return errors.Wrap(err, g.errMsg) +} + +func (g *guard) RollbackForcefully() { + for i := len(g.rbFuncs) - 1; i >= 0; i-- { + g.rbFuncs[i]() + } + g.rbFuncs = nil // do not rollback twice +} + +func (g *guard) Add(f func()) { + g.rbFuncs = append(g.rbFuncs, f) +} diff --git a/dsl/ops/sigcheck.go b/dsl/ops/util/sigcheck.go similarity index 61% rename from dsl/ops/sigcheck.go rename to dsl/ops/util/sigcheck.go index fecaf61d..b180b67f 100644 --- a/dsl/ops/sigcheck.go +++ b/dsl/ops/util/sigcheck.go @@ -1,4 +1,4 @@ -package ops +package util import ( "fmt" @@ -6,7 +6,13 @@ import ( "github.com/vim-volt/volt/dsl/types" ) -func signature(argTypes ...types.Type) *sigChecker { +// SigChecker checks if the type of args met given types to Signature() +type SigChecker interface { + Check(args []types.Value) error +} + +// Signature returns SigChecker for given types +func Signature(argTypes ...types.Type) SigChecker { return &sigChecker{argTypes: argTypes} } @@ -14,7 +20,7 @@ type sigChecker struct { argTypes []types.Type } -func (sc *sigChecker) check(args []types.Value) error { +func (sc *sigChecker) Check(args []types.Value) error { if len(args) != len(sc.argTypes) { return fmt.Errorf("expected %d arity but got %d", len(sc.argTypes), len(args)) } From 1ea27814ede602ef55cd9c3328303aecb30e1e05 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 16:51:06 +0900 Subject: [PATCH 41/79] refactor: rename Op.Execute() to Op.EvalExpr() --- dsl/ops/func_do.go | 2 +- dsl/ops/macro_array.go | 4 ++-- dsl/ops/macro_eval.go | 4 ++-- dsl/ops/macro_invert.go | 4 ++-- dsl/parse.go | 2 +- dsl/types/expr.go | 2 +- dsl/types/op.go | 7 +++++-- 7 files changed, 14 insertions(+), 11 deletions(-) diff --git a/dsl/ops/func_do.go b/dsl/ops/func_do.go index fee8fc92..a435f6c7 100644 --- a/dsl/ops/func_do.go +++ b/dsl/ops/func_do.go @@ -50,7 +50,7 @@ func (*doOp) InvertExpr(args []types.Value) (types.Value, error) { return DoOp.Bind(newargs...) } -func (*doOp) Execute(ctx context.Context, args []types.Value) (val types.Value, rollback func(), err error) { +func (*doOp) EvalExpr(ctx context.Context, args []types.Value) (val types.Value, rollback func(), err error) { g := util.FuncGuard(DoOp.String()) defer func() { err = g.Rollback(recover()) }() rollback = g.RollbackForcefully diff --git a/dsl/ops/macro_array.go b/dsl/ops/macro_array.go index cc6cc30d..a6dec441 100644 --- a/dsl/ops/macro_array.go +++ b/dsl/ops/macro_array.go @@ -18,7 +18,7 @@ type arrayOp struct { var ArrayOp = &arrayOp{macroBase("$array")} func (op *arrayOp) InvertExpr(args []types.Value) (types.Value, error) { - return op.macroInvertExpr(op.Execute(context.Background(), args)) + return op.macroInvertExpr(op.EvalExpr(context.Background(), args)) } func (*arrayOp) Bind(args ...types.Value) (*types.Expr, error) { @@ -26,6 +26,6 @@ func (*arrayOp) Bind(args ...types.Value) (*types.Expr, error) { return expr, nil } -func (*arrayOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { +func (*arrayOp) EvalExpr(ctx context.Context, args []types.Value) (types.Value, func(), error) { return types.NewArray(args, types.AnyValue), NoRollback, nil } diff --git a/dsl/ops/macro_eval.go b/dsl/ops/macro_eval.go index 6420a0b0..5f3c4d20 100644 --- a/dsl/ops/macro_eval.go +++ b/dsl/ops/macro_eval.go @@ -19,7 +19,7 @@ type evalOp struct { var EvalOp = &evalOp{macroBase("$eval")} func (op *evalOp) InvertExpr(args []types.Value) (types.Value, error) { - return op.macroInvertExpr(op.Execute(context.Background(), args)) + return op.macroInvertExpr(op.EvalExpr(context.Background(), args)) } func (*evalOp) Bind(args ...types.Value) (*types.Expr, error) { @@ -27,7 +27,7 @@ func (*evalOp) Bind(args ...types.Value) (*types.Expr, error) { return expr, nil } -func (*evalOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { +func (*evalOp) EvalExpr(ctx context.Context, args []types.Value) (types.Value, func(), error) { if err := util.Signature(types.AnyValue).Check(args); err != nil { return nil, NoRollback, err } diff --git a/dsl/ops/macro_invert.go b/dsl/ops/macro_invert.go index bd4a2e13..e3375bba 100644 --- a/dsl/ops/macro_invert.go +++ b/dsl/ops/macro_invert.go @@ -19,7 +19,7 @@ type invertOp struct { var InvertOp = &invertOp{macroBase("$invert")} func (op *invertOp) InvertExpr(args []types.Value) (types.Value, error) { - return op.macroInvertExpr(op.Execute(context.Background(), args)) + return op.macroInvertExpr(op.EvalExpr(context.Background(), args)) } func (*invertOp) Bind(args ...types.Value) (*types.Expr, error) { @@ -27,7 +27,7 @@ func (*invertOp) Bind(args ...types.Value) (*types.Expr, error) { return expr, nil } -func (*invertOp) Execute(ctx context.Context, args []types.Value) (types.Value, func(), error) { +func (*invertOp) EvalExpr(ctx context.Context, args []types.Value) (types.Value, func(), error) { if err := util.Signature(types.AnyValue).Check(args); err != nil { return nil, NoRollback, err } diff --git a/dsl/parse.go b/dsl/parse.go index 0a0990a6..63302c6b 100644 --- a/dsl/parse.go +++ b/dsl/parse.go @@ -58,7 +58,7 @@ func parseArray(array []interface{}) (types.Value, error) { } // Expand macro's expression at parsing time if op.IsMacro() { - val, _, err := op.Execute(context.Background(), args) + val, _, err := op.EvalExpr(context.Background(), args) return val, err } return op.Bind(args...) diff --git a/dsl/types/expr.go b/dsl/types/expr.go index cf959782..65d9fedf 100644 --- a/dsl/types/expr.go +++ b/dsl/types/expr.go @@ -31,7 +31,7 @@ func NewExpr(op Op, args []Value, retType Type) *Expr { // Eval evaluates given expression expr with given transaction ID trxID. func (expr *Expr) Eval(ctx context.Context) (val Value, rollback func(), err error) { - return expr.op.Execute(ctx, expr.args) + return expr.op.EvalExpr(ctx, expr.args) } // Invert inverts this expression. diff --git a/dsl/types/op.go b/dsl/types/op.go index fafeec38..387ee598 100644 --- a/dsl/types/op.go +++ b/dsl/types/op.go @@ -13,8 +13,11 @@ type Op interface { // Bind binds its arguments, and check if the types of values are correct Bind(args ...Value) (*Expr, error) - // Execute executes this operation and returns its result value and error - Execute(ctx context.Context, args []Value) (ret Value, rollback func(), err error) + // EvalExpr evaluates expression (this operator + given arguments). + // If this operator is a function, it executes the operation and returns its + // result and error. + // If this operator is a macro, this expands expression. + EvalExpr(ctx context.Context, args []Value) (ret Value, rollback func(), err error) // IsMacro returns true if this operator is a macro IsMacro() bool From fc726fd4ede3706191500df4c2636667621ca5ea Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 17:00:00 +0900 Subject: [PATCH 42/79] refactor: define Expr interface Instead of exporting Expr struct directly, define Expr interface and hide impl. Pros. * Does not allow `Expr{ ... }` construction * No need to write a lots of useless GoDoc comments on exported struct methods Cons. * Little bit bothersome, but it's better than Java :p --- dsl/deparse.go | 6 ++--- dsl/execute.go | 2 +- dsl/ops/func_do.go | 2 +- dsl/ops/macro_array.go | 2 +- dsl/ops/macro_eval.go | 2 +- dsl/ops/macro_invert.go | 2 +- dsl/parse.go | 4 ++-- dsl/types/expr.go | 52 ++++++++++++++++++++++++++--------------- dsl/types/op.go | 2 +- 9 files changed, 44 insertions(+), 30 deletions(-) diff --git a/dsl/deparse.go b/dsl/deparse.go index 48f4340c..994d4063 100644 --- a/dsl/deparse.go +++ b/dsl/deparse.go @@ -8,9 +8,9 @@ import ( "github.com/vim-volt/volt/dsl/types" ) -// Deparse deparses *types.Expr. +// Deparse deparses types.Expr. // ["@", 1, 2, 3] becomes [1, 2, 3] -func Deparse(expr *types.Expr) (interface{}, error) { +func Deparse(expr types.Expr) (interface{}, error) { value, err := deparse(expr) if err != nil { return nil, err @@ -38,7 +38,7 @@ func deparse(value types.Value) (interface{}, error) { m[k] = v } return m, nil - case *types.Expr: + case types.Expr: a := make([]interface{}, 0, len(val.Args())+1) // Do not include "@" in array literal if val.Op().String() != ops.ArrayOp.String() { diff --git a/dsl/execute.go b/dsl/execute.go index 4b3cee39..a6410839 100644 --- a/dsl/execute.go +++ b/dsl/execute.go @@ -24,7 +24,7 @@ const ( ) // Execute executes given expr with given ctx. -func Execute(ctx context.Context, expr *types.Expr) (val types.Value, rollback func(), err error) { +func Execute(ctx context.Context, expr types.Expr) (val types.Value, rollback func(), err error) { for _, required := range []struct { key CtxKeyType validate func(interface{}) error diff --git a/dsl/ops/func_do.go b/dsl/ops/func_do.go index a435f6c7..9902aaf3 100644 --- a/dsl/ops/func_do.go +++ b/dsl/ops/func_do.go @@ -26,7 +26,7 @@ func (*doOp) IsMacro() bool { return false } -func (*doOp) Bind(args ...types.Value) (*types.Expr, error) { +func (*doOp) Bind(args ...types.Value) (types.Expr, error) { sig := make([]types.Type, 0, len(args)) for i := 0; i < len(args); i++ { sig = append(sig, types.AnyValue) diff --git a/dsl/ops/macro_array.go b/dsl/ops/macro_array.go index a6dec441..bb1751ac 100644 --- a/dsl/ops/macro_array.go +++ b/dsl/ops/macro_array.go @@ -21,7 +21,7 @@ func (op *arrayOp) InvertExpr(args []types.Value) (types.Value, error) { return op.macroInvertExpr(op.EvalExpr(context.Background(), args)) } -func (*arrayOp) Bind(args ...types.Value) (*types.Expr, error) { +func (*arrayOp) Bind(args ...types.Value) (types.Expr, error) { expr := types.NewExpr(ArrayOp, args, types.NewArrayType(types.AnyValue)) return expr, nil } diff --git a/dsl/ops/macro_eval.go b/dsl/ops/macro_eval.go index 5f3c4d20..b95b314e 100644 --- a/dsl/ops/macro_eval.go +++ b/dsl/ops/macro_eval.go @@ -22,7 +22,7 @@ func (op *evalOp) InvertExpr(args []types.Value) (types.Value, error) { return op.macroInvertExpr(op.EvalExpr(context.Background(), args)) } -func (*evalOp) Bind(args ...types.Value) (*types.Expr, error) { +func (*evalOp) Bind(args ...types.Value) (types.Expr, error) { expr := types.NewExpr(ArrayOp, args, types.NewArrayType(types.AnyValue)) return expr, nil } diff --git a/dsl/ops/macro_invert.go b/dsl/ops/macro_invert.go index e3375bba..58743027 100644 --- a/dsl/ops/macro_invert.go +++ b/dsl/ops/macro_invert.go @@ -22,7 +22,7 @@ func (op *invertOp) InvertExpr(args []types.Value) (types.Value, error) { return op.macroInvertExpr(op.EvalExpr(context.Background(), args)) } -func (*invertOp) Bind(args ...types.Value) (*types.Expr, error) { +func (*invertOp) Bind(args ...types.Value) (types.Expr, error) { expr := types.NewExpr(ArrayOp, args, types.NewArrayType(types.AnyValue)) return expr, nil } diff --git a/dsl/parse.go b/dsl/parse.go index 63302c6b..d8cd7e93 100644 --- a/dsl/parse.go +++ b/dsl/parse.go @@ -14,7 +14,7 @@ import ( // 1. Split to operation and its arguments // 2. Do semantic analysis recursively for its arguments // 3. Convert to *Expr -func Parse(content []byte) (*types.Expr, error) { +func Parse(content []byte) (types.Expr, error) { var value interface{} if err := json.Unmarshal(content, value); err != nil { return nil, err @@ -29,7 +29,7 @@ func Parse(content []byte) (*types.Expr, error) { } // If expression's operator is a macro, return value may not be an array // (e.g. ["macro", 1, 2]) - expr, ok := arrayValue.(*types.Expr) + expr, ok := arrayValue.(types.Expr) if !ok { return nil, errors.New("the result must be an expression") } diff --git a/dsl/types/expr.go b/dsl/types/expr.go index 65d9fedf..785951df 100644 --- a/dsl/types/expr.go +++ b/dsl/types/expr.go @@ -3,44 +3,58 @@ package types import "context" // Expr has an operation and its arguments -type Expr struct { +type Expr interface { + // Op returns operator of Expr + Op() Op + + // Args returns arguments of Expr + Args() []Value + + // RetType returns return type of Expr + RetType() Type + + // Eval evaluates given expression expr with given transaction ID trxID. + Eval(ctx context.Context) (val Value, rollback func(), err error) + + // Invert inverts this expression. + // This just calls Op().InvertExpr() with saved arguments. + Invert() (Value, error) + + // Type returns the type. + Type() Type +} + +// NewExpr creates Expr instance +func NewExpr(op Op, args []Value, retType Type) Expr { + return &expr{op: op, args: args, retType: retType} +} + +type expr struct { op Op args []Value retType Type } -// Op returns operator of Expr -func (expr *Expr) Op() Op { +func (expr *expr) Op() Op { return expr.op } -// Args returns arguments of Expr -func (expr *Expr) Args() []Value { +func (expr *expr) Args() []Value { return expr.args } -// RetType returns return type of Expr -func (expr *Expr) RetType() Type { +func (expr *expr) RetType() Type { return expr.retType } -// NewExpr creates Expr instance -func NewExpr(op Op, args []Value, retType Type) *Expr { - return &Expr{op: op, args: args, retType: retType} -} - -// Eval evaluates given expression expr with given transaction ID trxID. -func (expr *Expr) Eval(ctx context.Context) (val Value, rollback func(), err error) { +func (expr *expr) Eval(ctx context.Context) (val Value, rollback func(), err error) { return expr.op.EvalExpr(ctx, expr.args) } -// Invert inverts this expression. -// This just calls Op().InvertExpr() with saved arguments. -func (expr *Expr) Invert() (Value, error) { +func (expr *expr) Invert() (Value, error) { return expr.op.InvertExpr(expr.args) } -// Type returns the type. -func (expr *Expr) Type() Type { +func (expr *expr) Type() Type { return expr.retType } diff --git a/dsl/types/op.go b/dsl/types/op.go index 387ee598..7dbaa69f 100644 --- a/dsl/types/op.go +++ b/dsl/types/op.go @@ -11,7 +11,7 @@ type Op interface { InvertExpr(args []Value) (Value, error) // Bind binds its arguments, and check if the types of values are correct - Bind(args ...Value) (*Expr, error) + Bind(args ...Value) (Expr, error) // EvalExpr evaluates expression (this operator + given arguments). // If this operator is a function, it executes the operation and returns its From 4443069214781d5f3ba6ed72d70f7ddc73ccfbcb Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 17:29:07 +0900 Subject: [PATCH 43/79] refactor: define interfaces for JSON values --- dsl/deparse.go | 13 ++-- dsl/types/json.go | 187 ++++++++++++++++++++++++---------------------- 2 files changed, 106 insertions(+), 94 deletions(-) diff --git a/dsl/deparse.go b/dsl/deparse.go index 994d4063..99512b44 100644 --- a/dsl/deparse.go +++ b/dsl/deparse.go @@ -19,16 +19,17 @@ func Deparse(expr types.Expr) (interface{}, error) { } func deparse(value types.Value) (interface{}, error) { - switch val := value.(type) { - case *types.Null: + if _, ok := value.Type().(*types.NullType); ok { return nil, nil - case *types.Bool: + } + switch val := value.(type) { + case types.Bool: return val.Value(), nil - case *types.String: + case types.String: return val.Value(), nil - case *types.Number: + case types.Number: return val.Value(), nil - case *types.Object: + case types.Object: m := make(map[string]interface{}, len(val.Value())) for k, o := range val.Value() { v, err := deparse(o) diff --git a/dsl/types/json.go b/dsl/types/json.go index c1566392..48a4a7cb 100644 --- a/dsl/types/json.go +++ b/dsl/types/json.go @@ -4,198 +4,209 @@ import "context" // ================ Null ================ -// Null is JSON null struct -type Null struct{} - // NullValue is the JSON null value -var NullValue = &Null{} +var NullValue = &nullT{} -// Invert returns itself as-is. -func (v *Null) Invert() (Value, error) { - return v, nil +type nullT struct{} + +func (*nullT) Invert() (Value, error) { + return NullValue, nil } -// Eval returns itself as-is. -func (v *Null) Eval(context.Context) (val Value, rollback func(), err error) { +func (v *nullT) Eval(context.Context) (val Value, rollback func(), err error) { return v, func() {}, nil } -// Type returns the type. -func (v *Null) Type() Type { +func (*nullT) Type() Type { return &NullType{} } // ================ Bool ================ +// TrueValue is the JSON true value +var TrueValue = &boolT{true} + +// FalseValue is the JSON false value +var FalseValue = &boolT{false} + // Bool is JSON boolean struct -type Bool struct { - value bool +type Bool interface { + Value + + // Value returns the holding internal value + Value() bool } // NewBool creates Bool instance -func NewBool(value bool) *Bool { +func NewBool(value bool) Bool { if value { return TrueValue } return FalseValue } -// Value returns the holding internal value -func (v *Bool) Value() bool { +type boolT struct { + value bool +} + +func (v *boolT) Value() bool { return v.value } -// Invert returns itself as-is. All literal types of JSON values are the same. -func (v *Bool) Invert() (Value, error) { +func (v *boolT) Invert() (Value, error) { return v, nil } -// Eval returns itself as-is. -func (v *Bool) Eval(context.Context) (val Value, rollback func(), err error) { +func (v *boolT) Eval(context.Context) (val Value, rollback func(), err error) { return v, func() {}, nil } -// Type returns the type. -func (v *Bool) Type() Type { +func (*boolT) Type() Type { return &BoolType{} } -// TrueValue is the JSON true value -var TrueValue = &Bool{true} - -// FalseValue is the JSON false value -var FalseValue = &Bool{false} - // ================ Number ================ // Number is JSON number struct -type Number struct { - value float64 -} +type Number interface { + Value -// Value returns the holding internal value -func (v *Number) Value() float64 { - return v.value + // Value returns the holding internal value + Value() float64 } // NewNumber creates Number instance -func NewNumber(value float64) *Number { - return &Number{value: value} +func NewNumber(value float64) Number { + return &numberT{value: value} } -// Invert returns itself as-is. All literal types of JSON values are the same. -func (v *Number) Invert() (Value, error) { +type numberT struct { + value float64 +} + +func (v *numberT) Value() float64 { + return v.value +} + +func (v *numberT) Invert() (Value, error) { return v, nil } -// Eval returns itself as-is. -func (v *Number) Eval(context.Context) (val Value, rollback func(), err error) { +func (v *numberT) Eval(context.Context) (val Value, rollback func(), err error) { return v, func() {}, nil } -// Type returns the type. -func (v *Number) Type() Type { +func (*numberT) Type() Type { return &NumberType{} } // ================ String ================ // String is JSON string struct -type String struct { - value string -} +type String interface { + Value -// Value returns the holding internal value -func (v *String) Value() string { - return v.value + // Value returns the holding internal value + Value() string } // NewString creates String instance -func NewString(value string) *String { - return &String{value: value} +func NewString(value string) String { + return &stringT{value: value} } -// Invert returns itself as-is. All literal types of JSON values are the same. -func (v *String) Invert() (Value, error) { +type stringT struct { + value string +} + +func (v *stringT) Value() string { + return v.value +} + +func (v *stringT) Invert() (Value, error) { return v, nil } -// Eval returns itself as-is. -func (v *String) Eval(context.Context) (val Value, rollback func(), err error) { +func (v *stringT) Eval(context.Context) (val Value, rollback func(), err error) { return v, func() {}, nil } -// Type returns the type. -func (v *String) Type() Type { +func (*stringT) Type() Type { return &StringType{} } // ================ Array ================ // Array is JSON array struct -type Array struct { +type Array interface { + Value + + // Value returns the holding internal value. + // DO NOT CHANGE THE RETURN VALUE DIRECTLY! + // Copy the slice before changing the value. + Value() []Value +} + +// NewArray creates Array instance +func NewArray(value []Value, argType Type) Array { + return &arrayT{value: value, argType: argType} +} + +type arrayT struct { value []Value argType Type } -// Value returns the holding internal value. -// DO NOT CHANGE THE RETURN VALUE DIRECTLY! -// Copy the slice before changing the value. -func (v *Array) Value() []Value { +func (v *arrayT) Value() []Value { return v.value } -// NewArray creates Array instance -func NewArray(value []Value, argType Type) *Array { - return &Array{value: value, argType: argType} -} - -// Invert returns itself as-is. All literal types of JSON values are the same. -func (v *Array) Invert() (Value, error) { +func (v *arrayT) Invert() (Value, error) { return v, nil } -// Eval returns itself as-is. -func (v *Array) Eval(context.Context) (val Value, rollback func(), err error) { +func (v *arrayT) Eval(context.Context) (val Value, rollback func(), err error) { return v, func() {}, nil } -// Type returns the type. -func (v *Array) Type() Type { +func (v *arrayT) Type() Type { return NewArrayType(v.argType) } // ================ Object ================ // Object is JSON object struct -type Object struct { +type Object interface { + Value + + // Value returns the holding internal value. + // DO NOT CHANGE THE RETURN VALUE DIRECTLY! + // Copy the map instance before changing the value. + Value() map[string]Value +} + +// NewObject creates Object instance +func NewObject(value map[string]Value, argType Type) Object { + return &objectT{value: value, argType: argType} +} + +type objectT struct { value map[string]Value argType Type } -// Value returns the holding internal value. -// DO NOT CHANGE THE RETURN VALUE DIRECTLY! -// Copy the map instance before changing the value. -func (v *Object) Value() map[string]Value { +func (v *objectT) Value() map[string]Value { return v.value } -// NewObject creates Object instance -func NewObject(value map[string]Value, argType Type) *Object { - return &Object{value: value, argType: argType} -} - -// Invert returns itself as-is. All literal types of JSON values are the same. -func (v *Object) Invert() (Value, error) { +func (v *objectT) Invert() (Value, error) { return v, nil } -// Eval returns itself as-is. -func (v *Object) Eval(context.Context) (val Value, rollback func(), err error) { +func (v *objectT) Eval(context.Context) (val Value, rollback func(), err error) { return v, func() {}, nil } -// Type returns the type. -func (v *Object) Type() Type { +func (v *objectT) Type() Type { return NewObjectType(v.argType) } From 6aa55c91d8b7753ea02279f09395b50c39125bc4 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 17:43:15 +0900 Subject: [PATCH 44/79] refactor: define interfaces for JSON types --- dsl/deparse.go | 2 +- dsl/types/json.go | 8 +-- dsl/types/types.go | 118 ++++++++++++++++++++++----------------------- 3 files changed, 64 insertions(+), 64 deletions(-) diff --git a/dsl/deparse.go b/dsl/deparse.go index 99512b44..1519ef98 100644 --- a/dsl/deparse.go +++ b/dsl/deparse.go @@ -19,7 +19,7 @@ func Deparse(expr types.Expr) (interface{}, error) { } func deparse(value types.Value) (interface{}, error) { - if _, ok := value.Type().(*types.NullType); ok { + if value.Type() == types.NullType { return nil, nil } switch val := value.(type) { diff --git a/dsl/types/json.go b/dsl/types/json.go index 48a4a7cb..8c14e432 100644 --- a/dsl/types/json.go +++ b/dsl/types/json.go @@ -18,7 +18,7 @@ func (v *nullT) Eval(context.Context) (val Value, rollback func(), err error) { } func (*nullT) Type() Type { - return &NullType{} + return NullType } // ================ Bool ================ @@ -62,7 +62,7 @@ func (v *boolT) Eval(context.Context) (val Value, rollback func(), err error) { } func (*boolT) Type() Type { - return &BoolType{} + return BoolType } // ================ Number ================ @@ -97,7 +97,7 @@ func (v *numberT) Eval(context.Context) (val Value, rollback func(), err error) } func (*numberT) Type() Type { - return &NumberType{} + return NumberType } // ================ String ================ @@ -132,7 +132,7 @@ func (v *stringT) Eval(context.Context) (val Value, rollback func(), err error) } func (*stringT) Type() Type { - return &StringType{} + return StringType } // ================ Array ================ diff --git a/dsl/types/types.go b/dsl/types/types.go index 02a5d9c6..d6a63ebc 100644 --- a/dsl/types/types.go +++ b/dsl/types/types.go @@ -9,140 +9,141 @@ type Type interface { InstanceOf(Type) bool } -// ===================== VoidType ===================== // +// ===================== Void type ===================== // -// VoidType is void type -type VoidType struct{} +// VoidType is a void type +var VoidType = &voidType{} -func (*VoidType) String() string { +type voidType struct{} + +func (*voidType) String() string { return "Void" } -// InstanceOf returns true if t is VoidType -func (*VoidType) InstanceOf(t Type) bool { - if _, ok := t.(*VoidType); ok { +func (*voidType) InstanceOf(t Type) bool { + if _, ok := t.(*voidType); ok { return true } return false } -// ===================== NullType ===================== // +// ===================== Null type ===================== // + +// NullType is a null type +var NullType = &nullType{} -// NullType is JSON null type -type NullType struct{} +type nullType struct{} -func (*NullType) String() string { +func (*nullType) String() string { return "Null" } -// InstanceOf returns true if t is NullType -func (*NullType) InstanceOf(t Type) bool { - if _, ok := t.(*NullType); ok { +func (*nullType) InstanceOf(t Type) bool { + if _, ok := t.(*nullType); ok { return true } return false } -// ===================== BoolType ===================== // +// ===================== Bool type ===================== // + +// BoolType is a null type +var BoolType = &boolType{} -// BoolType is JSON boolean type -type BoolType struct{} +type boolType struct{} -func (*BoolType) String() string { +func (*boolType) String() string { return "Bool" } -// InstanceOf returns true if t is BoolType -func (*BoolType) InstanceOf(t Type) bool { - if _, ok := t.(*BoolType); ok { +func (*boolType) InstanceOf(t Type) bool { + if _, ok := t.(*boolType); ok { return true } return false } -// ===================== NumberType ===================== // +// ===================== Number type ===================== // -// NumberType is JSON number type -type NumberType struct{} +// NumberType is a null type +var NumberType = &numberType{} -func (*NumberType) String() string { +type numberType struct{} + +func (*numberType) String() string { return "Number" } -// InstanceOf returns true if t is NumberType -func (*NumberType) InstanceOf(t Type) bool { - if _, ok := t.(*NumberType); ok { +func (*numberType) InstanceOf(t Type) bool { + if _, ok := t.(*numberType); ok { return true } return false } -// ===================== StringType ===================== // +// ===================== String type ===================== // + +// StringType is a null type +var StringType = &stringType{} -// StringType is JSON string type -type StringType struct{} +type stringType struct{} -func (*StringType) String() string { +func (*stringType) String() string { return "String" } -// InstanceOf returns true if t is StringType -func (*StringType) InstanceOf(t Type) bool { - if _, ok := t.(*StringType); ok { +func (*stringType) InstanceOf(t Type) bool { + if _, ok := t.(*stringType); ok { return true } return false } -// ===================== ArrayType ===================== // +// ===================== Array type ===================== // -// ArrayType is JSON array type -type ArrayType struct { - arg Type +// NewArrayType creates array type instance +func NewArrayType(arg Type) Type { + return &arrayType{arg: arg} } -// NewArrayType creates ArrayType instance -func NewArrayType(arg Type) *ArrayType { - return &ArrayType{arg: arg} +type arrayType struct { + arg Type } -func (t *ArrayType) String() string { +func (t *arrayType) String() string { return "Array[" + t.arg.String() + "]" } -// InstanceOf returns true if t is instance of t2 -func (t *ArrayType) InstanceOf(t2 Type) bool { - if array, ok := t2.(*ArrayType); ok { +func (t *arrayType) InstanceOf(t2 Type) bool { + if array, ok := t2.(*arrayType); ok { return t.arg.InstanceOf(array.arg) } return false } -// ===================== ObjectType ===================== // +// ===================== Object type ===================== // -// ObjectType is JSON object type -type ObjectType struct { - arg Type +// NewObjectType creates object type instance +func NewObjectType(arg Type) Type { + return &objectType{arg: arg} } -// NewObjectType creates ObjectType instance -func NewObjectType(arg Type) *ObjectType { - return &ObjectType{arg: arg} +type objectType struct { + arg Type } -func (t *ObjectType) String() string { +func (t *objectType) String() string { return "Object[" + t.arg.String() + "]" } -// InstanceOf returns true if t is instance of t2 -func (t *ObjectType) InstanceOf(t2 Type) bool { - if array, ok := t2.(*ObjectType); ok { +func (t *objectType) InstanceOf(t2 Type) bool { + if array, ok := t2.(*objectType); ok { return t.arg.InstanceOf(array.arg) } return false } -// ===================== AnyType ===================== // +// ===================== Any type ===================== // // AnyValue allows any type var AnyValue = &anyType{} @@ -153,7 +154,6 @@ func (*anyType) String() string { return "Any" } -// InstanceOf always returns true func (*anyType) InstanceOf(_ Type) bool { return true } From aa9511755fe2e2acf95f3264d005f8771b99711d Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 18:08:14 +0900 Subject: [PATCH 45/79] refactor: move common func methods to dsl/ops/func.go --- dsl/ops/func.go | 11 +++++++++++ dsl/ops/func_do.go | 16 ++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 dsl/ops/func.go diff --git a/dsl/ops/func.go b/dsl/ops/func.go new file mode 100644 index 00000000..30e17d65 --- /dev/null +++ b/dsl/ops/func.go @@ -0,0 +1,11 @@ +package ops + +type funcBase string + +func (f *funcBase) String() string { + return string(*f) +} + +func (*funcBase) IsMacro() bool { + return false +} diff --git a/dsl/ops/func_do.go b/dsl/ops/func_do.go index 9902aaf3..10e6d2eb 100644 --- a/dsl/ops/func_do.go +++ b/dsl/ops/func_do.go @@ -8,23 +8,15 @@ import ( ) func init() { - opName := doOp("do") - DoOp = &opName opsMap["do"] = DoOp } -type doOp string - -// DoOp is "do" operation -var DoOp *doOp - -func (*doOp) String() string { - return string(*DoOp) +type doOp struct { + funcBase } -func (*doOp) IsMacro() bool { - return false -} +// DoOp is "do" operation +var DoOp = &doOp{funcBase("do")} func (*doOp) Bind(args ...types.Value) (types.Expr, error) { sig := make([]types.Type, 0, len(args)) From 08bc03d8c09c3675178357f85ec3d33e9442f78e Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 18:10:01 +0900 Subject: [PATCH 46/79] refactor: fix comments --- dsl/ops/macro.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dsl/ops/macro.go b/dsl/ops/macro.go index 07ae13e0..4391cb5b 100644 --- a/dsl/ops/macro.go +++ b/dsl/ops/macro.go @@ -6,7 +6,6 @@ import ( type macroBase string -// String returns "$array" func (m *macroBase) String() string { return string(*m) } @@ -15,7 +14,7 @@ func (*macroBase) IsMacro() bool { return true } -// Invert the result of op.Execute() which expands an expression +// macroInvertExpr inverts the result of op.Execute() which expands an expression func (*macroBase) macroInvertExpr(val types.Value, _ func(), err error) (types.Value, error) { if err != nil { return nil, err From 6493bab85d5147688feb2a0273a31c54addb7c39 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 18:42:48 +0900 Subject: [PATCH 47/79] refactor: define Transaction interface --- transaction/transaction.go | 51 +++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/transaction/transaction.go b/transaction/transaction.go index eab90907..5b4a37dc 100644 --- a/transaction/transaction.go +++ b/transaction/transaction.go @@ -12,7 +12,7 @@ import ( ) // Start creates $VOLTPATH/trx/lock directory -func Start() (*Transaction, error) { +func Start() (Transaction, error) { os.MkdirAll(pathutil.TrxDir(), 0755) lockDir := filepath.Join(pathutil.TrxDir(), "lock") if err := os.Mkdir(lockDir, 0755); err != nil { @@ -22,7 +22,30 @@ func Start() (*Transaction, error) { if err != nil { return nil, errors.Wrap(err, "could not allocate a new transaction ID") } - return &Transaction{trxID: trxID}, nil + return &transaction{id: trxID}, nil +} + +// Transaction provides transaction methods +type Transaction interface { + // Done renames "lock" directory to "{trxid}" directory + Done() error + + // ID returns transaction ID + ID() TrxID +} + +type transaction struct { + id TrxID +} + +func (trx *transaction) ID() TrxID { + return trx.id +} + +func (trx *transaction) Done() error { + lockDir := filepath.Join(pathutil.TrxDir(), "lock") + trxIDDir := filepath.Join(pathutil.TrxDir(), string(trx.id)) + return os.Rename(lockDir, trxIDDir) } // genNewTrxID gets unallocated transaction ID looking $VOLTPATH/trx/ directory @@ -52,7 +75,7 @@ func genNewTrxID() (_ TrxID, result error) { if maxID == nil { return TrxID("1"), nil // no transaction ID directory } - return maxID.Add(1) + return maxID.Inc() } func greaterThan(a, b string) bool { @@ -78,27 +101,15 @@ func isTrxDirName(name string) bool { // transaction log file. type TrxID []byte -// Add adds n to transaction ID -func (tid *TrxID) Add(n int) (TrxID, error) { +// Inc increments transaction ID +func (tid *TrxID) Inc() (TrxID, error) { newID, err := strconv.ParseUint(string(*tid), 10, 32) if err != nil { return nil, err } - if newID+uint64(n) < newID { + if newID+uint64(1) < newID { // TODO: compute in string? - return nil, errors.Errorf("%d + %d causes overflow", newID, n) + return nil, errors.Errorf("%d + %d causes overflow", newID, 1) } - return TrxID(strconv.FormatUint(newID+uint64(n), 10)), nil -} - -// Transaction provides transaction methods -type Transaction struct { - trxID TrxID -} - -// Done rename "lock" directory to "{trxid}" directory -func (trx *Transaction) Done() error { - lockDir := filepath.Join(pathutil.TrxDir(), "lock") - trxIDDir := filepath.Join(pathutil.TrxDir(), string(trx.trxID)) - return os.Rename(lockDir, trxIDDir) + return TrxID(strconv.FormatUint(newID+uint64(1), 10)), nil } From fbd6893fa1d0353858611a2a558b5bcd321b471a Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 19:00:41 +0900 Subject: [PATCH 48/79] refactor: rename variable --- dsl/ops/func_do.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dsl/ops/func_do.go b/dsl/ops/func_do.go index 10e6d2eb..d3f79f0f 100644 --- a/dsl/ops/func_do.go +++ b/dsl/ops/func_do.go @@ -42,16 +42,16 @@ func (*doOp) InvertExpr(args []types.Value) (types.Value, error) { return DoOp.Bind(newargs...) } -func (*doOp) EvalExpr(ctx context.Context, args []types.Value) (val types.Value, rollback func(), err error) { +func (*doOp) EvalExpr(ctx context.Context, args []types.Value) (val types.Value, rollback func(), result error) { g := util.FuncGuard(DoOp.String()) - defer func() { err = g.Rollback(recover()) }() + defer func() { result = g.Rollback(recover()) }() rollback = g.RollbackForcefully for i := range args { v, rbFunc, e := args[i].Eval(ctx) g.Add(rbFunc) if e != nil { - err = g.Rollback(e) + result = g.Rollback(e) return } val = v From 77a99b48c05ee4b9e494d585eace481946c42788 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 19:12:36 +0900 Subject: [PATCH 49/79] fix: opsMap must not be nil --- dsl/ops/lookup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsl/ops/lookup.go b/dsl/ops/lookup.go index 5b8176b2..fbc6803f 100644 --- a/dsl/ops/lookup.go +++ b/dsl/ops/lookup.go @@ -4,7 +4,7 @@ import "github.com/vim-volt/volt/dsl/types" // opsMap holds all operation structs. // All operations in dsl/op/*.go sets its struct to this in init() -var opsMap map[string]types.Op +var opsMap = make(map[string]types.Op) // Lookup looks up operator name func Lookup(name string) (types.Op, bool) { From cf188d43d36b9362621b25170c86d65f7999bda3 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 19:13:20 +0900 Subject: [PATCH 50/79] fix: now dsl.Execute() rolls back when error occurred And now TrxIDKey is not included in context. --- dsl/dslctx/dslctx.go | 64 +++++++++++++++++++++++++++++++++++++++ dsl/execute.go | 72 +++++++++++--------------------------------- 2 files changed, 82 insertions(+), 54 deletions(-) create mode 100644 dsl/dslctx/dslctx.go diff --git a/dsl/dslctx/dslctx.go b/dsl/dslctx/dslctx.go new file mode 100644 index 00000000..df4d9499 --- /dev/null +++ b/dsl/dslctx/dslctx.go @@ -0,0 +1,64 @@ +package dslctx + +import ( + "context" + "errors" + + "github.com/vim-volt/volt/config" + "github.com/vim-volt/volt/lockjson" +) + +// KeyType is the type of the key of context specified for Execute() +type KeyType uint + +const ( + // TrxIDKey is the key to get transaction ID + TrxIDKey KeyType = iota + // LockJSONKey is the key to get *lockjson.LockJSON value + LockJSONKey + // ConfigKey is the key to get *config.Config value + ConfigKey +) + +// WithDSLValues adds given values +func WithDSLValues(ctx context.Context, lockJSON *lockjson.LockJSON, cfg *config.Config) context.Context { + ctx = context.WithValue(ctx, LockJSONKey, lockJSON) + ctx = context.WithValue(ctx, ConfigKey, cfg) + return ctx +} + +// Validate validates if required keys exist in ctx +func Validate(ctx context.Context) error { + for _, required := range []struct { + key KeyType + validate func(interface{}) error + }{ + {LockJSONKey, validateLockJSON}, + {ConfigKey, validateConfig}, + } { + if err := required.validate(ctx.Value(required.key)); err != nil { + return err + } + } + return nil +} + +func validateLockJSON(v interface{}) error { + if v == nil { + return errors.New("no lock.json key in context") + } + if _, ok := v.(*lockjson.LockJSON); !ok { + return errors.New("invalid lock.json data in context") + } + return nil +} + +func validateConfig(v interface{}) error { + if v == nil { + return errors.New("no config.toml key in context") + } + if _, ok := v.(*config.Config); !ok { + return errors.New("invalid config.toml data in context") + } + return nil +} diff --git a/dsl/execute.go b/dsl/execute.go index a6410839..0d13bf59 100644 --- a/dsl/execute.go +++ b/dsl/execute.go @@ -4,68 +4,32 @@ import ( "context" "github.com/pkg/errors" - "github.com/vim-volt/volt/config" - "github.com/vim-volt/volt/dsl/ops" + "github.com/vim-volt/volt/dsl/dslctx" "github.com/vim-volt/volt/dsl/types" - "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/transaction" ) -// CtxKeyType is the type of the key of context specified for Execute() -type CtxKeyType uint - -const ( - // CtxTrxIDKey is the key to get transaction ID - CtxTrxIDKey CtxKeyType = iota - // CtxLockJSONKey is the key to get *lockjson.LockJSON value - CtxLockJSONKey - // CtxConfigKey is the key to get *config.Config value - CtxConfigKey -) - // Execute executes given expr with given ctx. -func Execute(ctx context.Context, expr types.Expr) (val types.Value, rollback func(), err error) { - for _, required := range []struct { - key CtxKeyType - validate func(interface{}) error - }{ - {CtxLockJSONKey, validateLockJSON}, - {CtxConfigKey, validateConfig}, - {CtxTrxIDKey, validateTrxID}, - } { - if err := required.validate(ctx.Value(required.key)); err != nil { - return nil, ops.NoRollback, err - } +func Execute(ctx context.Context, expr types.Expr) (_ types.Value, result error) { + if err := dslctx.Validate(ctx); err != nil { + return nil, err } - return expr.Eval(ctx) -} -func validateLockJSON(v interface{}) error { - if v == nil { - return errors.New("no lock.json key in context") - } - if _, ok := v.(*lockjson.LockJSON); !ok { - return errors.New("invalid lock.json data in context") + // Begin transaction + trx, err := transaction.Start() + if err != nil { + return nil, err } - return nil -} - -func validateConfig(v interface{}) error { - if v == nil { - return errors.New("no config.toml key in context") - } - if _, ok := v.(*config.Config); !ok { - return errors.New("invalid config.toml data in context") - } - return nil -} + defer func() { + if err := trx.Done(); err != nil { + result = err + } + }() -func validateTrxID(v interface{}) error { - if v == nil { - return errors.New("no transaction ID key in context") - } - if _, ok := v.(transaction.TrxID); !ok { - return errors.New("invalid transaction ID data in context") + val, rollback, err := expr.Eval(ctx) + if err != nil { + rollback() + return nil, errors.Wrap(err, "expression returned an error") } - return nil + return val, nil } From e08953453330fe9375b68a7b1c3fddb5d16686b7 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 19:17:00 +0900 Subject: [PATCH 51/79] doc: add "lockjson/write" func, and so on --- _docs/json-dsl.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/_docs/json-dsl.md b/_docs/json-dsl.md index 0ad32514..629f38a9 100644 --- a/_docs/json-dsl.md +++ b/_docs/json-dsl.md @@ -430,6 +430,11 @@ Macros are not saved in transaction log (expanded before saving). ### lock.json operators +* `["lockjson/write"] void` + * Writes lock.json to a file + * e.g. + * `["$invert", ["lockjson/write"]]` = `["lockjson/write"]` + * `["lockjson/add", repos Repos, profiles []string]` * Add `repos` information to `repos[]` array in lock.json. If `profiles` is not empty, the repository name is added to @@ -472,6 +477,14 @@ Macros are not saved in transaction log (expanded before saving). * `["plugconf/delete", "github.com/tyru/caw.vim"]` * `["$invert", ["plugconf/delete", path]]` = `["plugconf/install", ["$invert", path]]` +### Migration operators + +* `["migrate/plugconf/config-func"] void` + * Converts `s:config()` function name to `s:on_load_pre()` in all plugconf files. + * See `volt migrate -help plugconf/config-func` for the details. + * e.g. + * `["$invert", ["migrate/plugconf/config-func"]]` = `["migrate/plugconf/config-func"]` + ### Vim directory operators * `["vimdir/with-install", paths "all" | []ReposPath, expr Expr[* => R]] R` @@ -482,7 +495,7 @@ Macros are not saved in transaction log (expanded before saving). * See "Why `vimdir/install` and `vimdir/uninstall` operators do not exist?" section -### Why `vimdir/install` and `vimdir/uninstall` operators do not exist? +#### Why `vimdir/install` and `vimdir/uninstall` operators do not exist? We'll describe why `vimdir/install` and `vimdir/uninstall` operators do not exist, and `vimdir/with-install` exists instead. From 7bd0d14762470511c9222c30c1061a9dcb3ea243 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 19:18:29 +0900 Subject: [PATCH 52/79] feat: add "lockjson/write" function --- dsl/ops/func_lockjson_write.go | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 dsl/ops/func_lockjson_write.go diff --git a/dsl/ops/func_lockjson_write.go b/dsl/ops/func_lockjson_write.go new file mode 100644 index 00000000..239cb78d --- /dev/null +++ b/dsl/ops/func_lockjson_write.go @@ -0,0 +1,46 @@ +package ops + +import ( + "context" + + "github.com/pkg/errors" + "github.com/vim-volt/volt/dsl/dslctx" + "github.com/vim-volt/volt/dsl/ops/util" + "github.com/vim-volt/volt/dsl/types" + "github.com/vim-volt/volt/lockjson" +) + +func init() { + opsMap["lockjson/write"] = LockJSONWriteOp +} + +type lockJSONWriteOp struct { + funcBase +} + +// LockJSONWriteOp is "lockjson/write" operator +var LockJSONWriteOp = &lockJSONWriteOp{funcBase("lockjson/write")} + +func (*lockJSONWriteOp) Bind(args ...types.Value) (types.Expr, error) { + if err := util.Signature().Check(args); err != nil { + return nil, err + } + retType := types.VoidType + return types.NewExpr(LockJSONWriteOp, args, retType), nil +} + +func (*lockJSONWriteOp) InvertExpr(args []types.Value) (types.Value, error) { + return LockJSONWriteOp.Bind(args...) +} + +func (*lockJSONWriteOp) EvalExpr(ctx context.Context, args []types.Value) (_ types.Value, rollback func(), result error) { + rollback = NoRollback + + lockJSON := ctx.Value(dslctx.LockJSONKey).(*lockjson.LockJSON) + result = lockJSON.Write() + if result != nil { + result = errors.Wrap(result, "could not write to lock.json") + } + + return +} From 9728651d59f2db708adbbb068d358bf28b4ee1d5 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 19:18:46 +0900 Subject: [PATCH 53/79] refactor: let 'volt migrate lockjson' use JSON DSL --- migration/lockjson.go | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/migration/lockjson.go b/migration/lockjson.go index 485e1200..89c7e773 100644 --- a/migration/lockjson.go +++ b/migration/lockjson.go @@ -1,11 +1,14 @@ package migration import ( - "errors" + "context" + "github.com/pkg/errors" "github.com/vim-volt/volt/config" + "github.com/vim-volt/volt/dsl" + "github.com/vim-volt/volt/dsl/dslctx" + "github.com/vim-volt/volt/dsl/ops" "github.com/vim-volt/volt/lockjson" - "github.com/vim-volt/volt/transaction" ) func init() { @@ -32,22 +35,12 @@ Description To suppress this, running this command simply reads and writes migrated structure to lock.json.` } -func (*lockjsonMigrater) Migrate(lockJSON *lockjson.LockJSON, cfg *config.Config) (result error) { - // Begin transaction - trx, err := transaction.Start() +func (*lockjsonMigrater) Migrate(lockJSON *lockjson.LockJSON, cfg *config.Config) error { + ctx := dslctx.WithDSLValues(context.Background(), lockJSON, cfg) + expr, err := ops.LockJSONWriteOp.Bind() if err != nil { - return err + return errors.Wrapf(err, "cannot bind %s operator", ops.LockJSONWriteOp.String()) } - defer func() { - if err := trx.Done(); err != nil { - result = err - } - }() - - // Write to lock.json - err = lockJSON.Write() - if err != nil { - return errors.New("could not write to lock.json: " + err.Error()) - } - return nil + _, err = dsl.Execute(ctx, expr) + return err } From 6fbfc1f5ebb2f886e73e991144abcb892ee06dbe Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 19:30:01 +0900 Subject: [PATCH 54/79] fix: pass context outside dsl/ops package Don't use context.Background() in dsl/ops package. --- dsl/ops/func_do.go | 4 ++-- dsl/ops/func_lockjson_write.go | 2 +- dsl/ops/macro.go | 6 ++++-- dsl/ops/macro_array.go | 5 +++-- dsl/ops/macro_eval.go | 7 ++++--- dsl/ops/macro_invert.go | 7 ++++--- dsl/types/expr.go | 16 ++++------------ dsl/types/json.go | 12 ++++++------ dsl/types/op.go | 2 +- dsl/types/value.go | 2 +- 10 files changed, 30 insertions(+), 33 deletions(-) diff --git a/dsl/ops/func_do.go b/dsl/ops/func_do.go index d3f79f0f..b93955cb 100644 --- a/dsl/ops/func_do.go +++ b/dsl/ops/func_do.go @@ -30,10 +30,10 @@ func (*doOp) Bind(args ...types.Value) (types.Expr, error) { return types.NewExpr(DoOp, args, retType), nil } -func (*doOp) InvertExpr(args []types.Value) (types.Value, error) { +func (*doOp) InvertExpr(ctx context.Context, args []types.Value) (types.Value, error) { newargs := make([]types.Value, len(args)) for i := range args { - a, err := args[i].Invert() + a, err := args[i].Invert(ctx) if err != nil { return nil, err } diff --git a/dsl/ops/func_lockjson_write.go b/dsl/ops/func_lockjson_write.go index 239cb78d..6caaa81c 100644 --- a/dsl/ops/func_lockjson_write.go +++ b/dsl/ops/func_lockjson_write.go @@ -29,7 +29,7 @@ func (*lockJSONWriteOp) Bind(args ...types.Value) (types.Expr, error) { return types.NewExpr(LockJSONWriteOp, args, retType), nil } -func (*lockJSONWriteOp) InvertExpr(args []types.Value) (types.Value, error) { +func (*lockJSONWriteOp) InvertExpr(_ context.Context, args []types.Value) (types.Value, error) { return LockJSONWriteOp.Bind(args...) } diff --git a/dsl/ops/macro.go b/dsl/ops/macro.go index 4391cb5b..c27ec44d 100644 --- a/dsl/ops/macro.go +++ b/dsl/ops/macro.go @@ -1,6 +1,8 @@ package ops import ( + "context" + "github.com/vim-volt/volt/dsl/types" ) @@ -15,9 +17,9 @@ func (*macroBase) IsMacro() bool { } // macroInvertExpr inverts the result of op.Execute() which expands an expression -func (*macroBase) macroInvertExpr(val types.Value, _ func(), err error) (types.Value, error) { +func (*macroBase) macroInvertExpr(ctx context.Context, val types.Value, _ func(), err error) (types.Value, error) { if err != nil { return nil, err } - return val.Invert() + return val.Invert(ctx) } diff --git a/dsl/ops/macro_array.go b/dsl/ops/macro_array.go index bb1751ac..dcd13ae4 100644 --- a/dsl/ops/macro_array.go +++ b/dsl/ops/macro_array.go @@ -17,8 +17,9 @@ type arrayOp struct { // ArrayOp is "$array" operator var ArrayOp = &arrayOp{macroBase("$array")} -func (op *arrayOp) InvertExpr(args []types.Value) (types.Value, error) { - return op.macroInvertExpr(op.EvalExpr(context.Background(), args)) +func (op *arrayOp) InvertExpr(ctx context.Context, args []types.Value) (types.Value, error) { + val, rollback, err := op.EvalExpr(ctx, args) + return op.macroInvertExpr(ctx, val, rollback, err) } func (*arrayOp) Bind(args ...types.Value) (types.Expr, error) { diff --git a/dsl/ops/macro_eval.go b/dsl/ops/macro_eval.go index b95b314e..a3e09f3e 100644 --- a/dsl/ops/macro_eval.go +++ b/dsl/ops/macro_eval.go @@ -18,8 +18,9 @@ type evalOp struct { // EvalOp is "$eval" operator var EvalOp = &evalOp{macroBase("$eval")} -func (op *evalOp) InvertExpr(args []types.Value) (types.Value, error) { - return op.macroInvertExpr(op.EvalExpr(context.Background(), args)) +func (op *evalOp) InvertExpr(ctx context.Context, args []types.Value) (types.Value, error) { + val, rollback, err := op.EvalExpr(ctx, args) + return op.macroInvertExpr(ctx, val, rollback, err) } func (*evalOp) Bind(args ...types.Value) (types.Expr, error) { @@ -31,5 +32,5 @@ func (*evalOp) EvalExpr(ctx context.Context, args []types.Value) (types.Value, f if err := util.Signature(types.AnyValue).Check(args); err != nil { return nil, NoRollback, err } - return args[0].Eval(context.Background()) + return args[0].Eval(ctx) } diff --git a/dsl/ops/macro_invert.go b/dsl/ops/macro_invert.go index 58743027..5c9ca5f1 100644 --- a/dsl/ops/macro_invert.go +++ b/dsl/ops/macro_invert.go @@ -18,8 +18,9 @@ type invertOp struct { // InvertOp is "$invert" operator var InvertOp = &invertOp{macroBase("$invert")} -func (op *invertOp) InvertExpr(args []types.Value) (types.Value, error) { - return op.macroInvertExpr(op.EvalExpr(context.Background(), args)) +func (op *invertOp) InvertExpr(ctx context.Context, args []types.Value) (types.Value, error) { + val, rollback, err := op.EvalExpr(ctx, args) + return op.macroInvertExpr(ctx, val, rollback, err) } func (*invertOp) Bind(args ...types.Value) (types.Expr, error) { @@ -31,6 +32,6 @@ func (*invertOp) EvalExpr(ctx context.Context, args []types.Value) (types.Value, if err := util.Signature(types.AnyValue).Check(args); err != nil { return nil, NoRollback, err } - val, err := args[0].Invert() + val, err := args[0].Invert(ctx) return val, NoRollback, err } diff --git a/dsl/types/expr.go b/dsl/types/expr.go index 785951df..7cf89182 100644 --- a/dsl/types/expr.go +++ b/dsl/types/expr.go @@ -4,6 +4,8 @@ import "context" // Expr has an operation and its arguments type Expr interface { + Value + // Op returns operator of Expr Op() Op @@ -12,16 +14,6 @@ type Expr interface { // RetType returns return type of Expr RetType() Type - - // Eval evaluates given expression expr with given transaction ID trxID. - Eval(ctx context.Context) (val Value, rollback func(), err error) - - // Invert inverts this expression. - // This just calls Op().InvertExpr() with saved arguments. - Invert() (Value, error) - - // Type returns the type. - Type() Type } // NewExpr creates Expr instance @@ -51,8 +43,8 @@ func (expr *expr) Eval(ctx context.Context) (val Value, rollback func(), err err return expr.op.EvalExpr(ctx, expr.args) } -func (expr *expr) Invert() (Value, error) { - return expr.op.InvertExpr(expr.args) +func (expr *expr) Invert(ctx context.Context) (Value, error) { + return expr.op.InvertExpr(ctx, expr.args) } func (expr *expr) Type() Type { diff --git a/dsl/types/json.go b/dsl/types/json.go index 8c14e432..9774960f 100644 --- a/dsl/types/json.go +++ b/dsl/types/json.go @@ -9,7 +9,7 @@ var NullValue = &nullT{} type nullT struct{} -func (*nullT) Invert() (Value, error) { +func (*nullT) Invert(context.Context) (Value, error) { return NullValue, nil } @@ -53,7 +53,7 @@ func (v *boolT) Value() bool { return v.value } -func (v *boolT) Invert() (Value, error) { +func (v *boolT) Invert(context.Context) (Value, error) { return v, nil } @@ -88,7 +88,7 @@ func (v *numberT) Value() float64 { return v.value } -func (v *numberT) Invert() (Value, error) { +func (v *numberT) Invert(context.Context) (Value, error) { return v, nil } @@ -123,7 +123,7 @@ func (v *stringT) Value() string { return v.value } -func (v *stringT) Invert() (Value, error) { +func (v *stringT) Invert(context.Context) (Value, error) { return v, nil } @@ -161,7 +161,7 @@ func (v *arrayT) Value() []Value { return v.value } -func (v *arrayT) Invert() (Value, error) { +func (v *arrayT) Invert(context.Context) (Value, error) { return v, nil } @@ -199,7 +199,7 @@ func (v *objectT) Value() map[string]Value { return v.value } -func (v *objectT) Invert() (Value, error) { +func (v *objectT) Invert(context.Context) (Value, error) { return v, nil } diff --git a/dsl/types/op.go b/dsl/types/op.go index 7dbaa69f..4d4629d7 100644 --- a/dsl/types/op.go +++ b/dsl/types/op.go @@ -8,7 +8,7 @@ type Op interface { String() string // InvertExpr returns inverted expression - InvertExpr(args []Value) (Value, error) + InvertExpr(ctx context.Context, args []Value) (Value, error) // Bind binds its arguments, and check if the types of values are correct Bind(args ...Value) (Expr, error) diff --git a/dsl/types/value.go b/dsl/types/value.go index db4041a4..0439b09a 100644 --- a/dsl/types/value.go +++ b/dsl/types/value.go @@ -8,7 +8,7 @@ type Value interface { // All type values are invertible. // Literals like string,number,... return itself as-is. // If argument type or arity is different, this returns non-nil error. - Invert() (Value, error) + Invert(ctx context.Context) (Value, error) // Eval returns a evaluated value. // Literals like string,number,... return itself as-is. From 2b195092b9038427cbd497cc0cb3fd081506dc7a Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 19:36:29 +0900 Subject: [PATCH 55/79] refactor: use String() instead of literal --- dsl/ops/func_do.go | 2 +- dsl/ops/func_lockjson_write.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dsl/ops/func_do.go b/dsl/ops/func_do.go index b93955cb..66a7ab22 100644 --- a/dsl/ops/func_do.go +++ b/dsl/ops/func_do.go @@ -8,7 +8,7 @@ import ( ) func init() { - opsMap["do"] = DoOp + opsMap[DoOp.String()] = DoOp } type doOp struct { diff --git a/dsl/ops/func_lockjson_write.go b/dsl/ops/func_lockjson_write.go index 6caaa81c..58354c34 100644 --- a/dsl/ops/func_lockjson_write.go +++ b/dsl/ops/func_lockjson_write.go @@ -11,7 +11,7 @@ import ( ) func init() { - opsMap["lockjson/write"] = LockJSONWriteOp + opsMap[LockJSONWriteOp.String()] = LockJSONWriteOp } type lockJSONWriteOp struct { From d2452f1b37aee79f6142c6ec0085769e11a04f9f Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 19:50:31 +0900 Subject: [PATCH 56/79] refactor: let 'volt migrate plugconf/config-func' use JSON DSL --- dsl/ops/func_migrate_plugconf_config_func.go | 107 +++++++++++++++++++ migration/plugconf-config-func.go | 75 ++----------- 2 files changed, 116 insertions(+), 66 deletions(-) create mode 100644 dsl/ops/func_migrate_plugconf_config_func.go diff --git a/dsl/ops/func_migrate_plugconf_config_func.go b/dsl/ops/func_migrate_plugconf_config_func.go new file mode 100644 index 00000000..cd879c09 --- /dev/null +++ b/dsl/ops/func_migrate_plugconf_config_func.go @@ -0,0 +1,107 @@ +package ops + +import ( + "bytes" + "context" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/pkg/errors" + "github.com/vim-volt/volt/config" + "github.com/vim-volt/volt/dsl/dslctx" + "github.com/vim-volt/volt/dsl/ops/util" + "github.com/vim-volt/volt/dsl/types" + "github.com/vim-volt/volt/lockjson" + "github.com/vim-volt/volt/pathutil" + "github.com/vim-volt/volt/plugconf" + "github.com/vim-volt/volt/subcmd/builder" +) + +func init() { + opsMap[MigratePlugconfConfigFuncOp.String()] = MigratePlugconfConfigFuncOp +} + +type migratePlugconfConfigFuncOp struct { + funcBase +} + +// MigratePlugconfConfigFuncOp is "migrate/plugconf/config-func" operator +var MigratePlugconfConfigFuncOp = &migratePlugconfConfigFuncOp{ + funcBase("migrate/plugconf/config-func"), +} + +func (*migratePlugconfConfigFuncOp) Bind(args ...types.Value) (types.Expr, error) { + if err := util.Signature().Check(args); err != nil { + return nil, err + } + retType := types.VoidType + return types.NewExpr(MigratePlugconfConfigFuncOp, args, retType), nil +} + +func (*migratePlugconfConfigFuncOp) InvertExpr(_ context.Context, args []types.Value) (types.Value, error) { + return MigratePlugconfConfigFuncOp.Bind(args...) +} + +func (*migratePlugconfConfigFuncOp) EvalExpr(ctx context.Context, args []types.Value) (_ types.Value, rollback func(), result error) { + rollback = NoRollback + lockJSON := ctx.Value(dslctx.LockJSONKey).(*lockjson.LockJSON) + cfg := ctx.Value(dslctx.ConfigKey).(*config.Config) + + parseResults, parseErr := plugconf.ParseMultiPlugconf(lockJSON.Repos) + if parseErr.HasErrs() { + var errMsg bytes.Buffer + errMsg.WriteString("Please fix the following errors before migration:") + for _, err := range parseErr.Errors().Errors { + for _, line := range strings.Split(err.Error(), "\n") { + errMsg.WriteString(" ") + errMsg.WriteString(line) + } + } + result = errors.New(errMsg.String()) + return + } + + type plugInfo struct { + path string + content []byte + } + infoList := make([]plugInfo, 0, len(lockJSON.Repos)) + + // Collects plugconf infomations and check errors + parseResults.Each(func(reposPath pathutil.ReposPath, info *plugconf.ParsedInfo) { + if !info.ConvertConfigToOnLoadPreFunc() { + return // no s:config() function + } + content, err := info.GeneratePlugconf() + if err != nil { + result = errors.Wrap(err, "could not generate converted plugconf") + return + } + infoList = append(infoList, plugInfo{ + path: reposPath.Plugconf(), + content: content, + }) + }) + if result != nil { + return + } + + // After checking errors, write the content to files + for _, info := range infoList { + os.MkdirAll(filepath.Dir(info.path), 0755) + err := ioutil.WriteFile(info.path, info.content, 0644) + if err != nil { + result = errors.Wrapf(err, "could not write to file %s", info.path) + return + } + } + + // Build ~/.vim/pack/volt dir + result = builder.Build(false, lockJSON, cfg) + if result != nil { + result = errors.Wrap(result, "could not build "+pathutil.VimVoltDir()) + } + return +} diff --git a/migration/plugconf-config-func.go b/migration/plugconf-config-func.go index fa856f84..8c318da1 100644 --- a/migration/plugconf-config-func.go +++ b/migration/plugconf-config-func.go @@ -1,19 +1,14 @@ package migration import ( - "io/ioutil" - "os" - "path/filepath" - "strings" + "context" "github.com/pkg/errors" "github.com/vim-volt/volt/config" + "github.com/vim-volt/volt/dsl" + "github.com/vim-volt/volt/dsl/dslctx" + "github.com/vim-volt/volt/dsl/ops" "github.com/vim-volt/volt/lockjson" - "github.com/vim-volt/volt/logger" - "github.com/vim-volt/volt/pathutil" - "github.com/vim-volt/volt/plugconf" - "github.com/vim-volt/volt/subcmd/builder" - "github.com/vim-volt/volt/transaction" ) func init() { @@ -41,63 +36,11 @@ Description } func (*plugconfConfigMigrater) Migrate(lockJSON *lockjson.LockJSON, cfg *config.Config) (result error) { - results, parseErr := plugconf.ParseMultiPlugconf(lockJSON.Repos) - if parseErr.HasErrs() { - logger.Error("Please fix the following errors before migration:") - for _, err := range parseErr.Errors().Errors { - for _, line := range strings.Split(err.Error(), "\n") { - logger.Errorf(" %s", line) - } - } - return nil - } - - type plugInfo struct { - path string - content []byte - } - infoList := make([]plugInfo, 0, len(lockJSON.Repos)) - - // Collects plugconf infomations and check errors - results.Each(func(reposPath pathutil.ReposPath, info *plugconf.ParsedInfo) { - if !info.ConvertConfigToOnLoadPreFunc() { - return // no s:config() function - } - content, err := info.GeneratePlugconf() - if err != nil { - logger.Errorf("Could not generate converted plugconf: %s", err) - return - } - infoList = append(infoList, plugInfo{ - path: reposPath.Plugconf(), - content: content, - }) - }) - - // After checking errors, write the content to files - for _, info := range infoList { - os.MkdirAll(filepath.Dir(info.path), 0755) - err := ioutil.WriteFile(info.path, info.content, 0644) - if err != nil { - return err - } - } - - // Begin transaction - trx, err := transaction.Start() - if err != nil { - return err - } - defer func() { - if err := trx.Done(); err != nil { - result = err - } - }() - - // Build ~/.vim/pack/volt dir - err = builder.Build(false, lockJSON, cfg) + ctx := dslctx.WithDSLValues(context.Background(), lockJSON, cfg) + expr, err := ops.MigratePlugconfConfigFuncOp.Bind() if err != nil { - return errors.Wrap(err, "could not build "+pathutil.VimVoltDir()) + return errors.Wrapf(err, "cannot bind %s operator", ops.LockJSONWriteOp.String()) } - return nil + _, err = dsl.Execute(ctx, expr) + return err } From 11007bb90fa2336b74f62198db0a693e6cd0b97b Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 19:53:04 +0900 Subject: [PATCH 57/79] refactor: move migration to subcmd/migration --- subcmd/migrate.go | 2 +- {migration => subcmd/migration}/lockjson.go | 0 {migration => subcmd/migration}/migrater.go | 0 {migration => subcmd/migration}/plugconf-config-func.go | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename {migration => subcmd/migration}/lockjson.go (100%) rename {migration => subcmd/migration}/migrater.go (100%) rename {migration => subcmd/migration}/plugconf-config-func.go (100%) diff --git a/subcmd/migrate.go b/subcmd/migrate.go index 92baae60..a47f0dfb 100644 --- a/subcmd/migrate.go +++ b/subcmd/migrate.go @@ -7,7 +7,7 @@ import ( "os" "github.com/vim-volt/volt/logger" - "github.com/vim-volt/volt/migration" + "github.com/vim-volt/volt/subcmd/migration" ) func init() { diff --git a/migration/lockjson.go b/subcmd/migration/lockjson.go similarity index 100% rename from migration/lockjson.go rename to subcmd/migration/lockjson.go diff --git a/migration/migrater.go b/subcmd/migration/migrater.go similarity index 100% rename from migration/migrater.go rename to subcmd/migration/migrater.go diff --git a/migration/plugconf-config-func.go b/subcmd/migration/plugconf-config-func.go similarity index 100% rename from migration/plugconf-config-func.go rename to subcmd/migration/plugconf-config-func.go From 9ae163f82e4340362e17d3b6899fc1da9fbde385 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 20:25:11 +0900 Subject: [PATCH 58/79] feat: write transaction log before evaluation --- dsl/deparse.go | 2 +- dsl/execute.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/dsl/deparse.go b/dsl/deparse.go index 1519ef98..0163a4cf 100644 --- a/dsl/deparse.go +++ b/dsl/deparse.go @@ -10,7 +10,7 @@ import ( // Deparse deparses types.Expr. // ["@", 1, 2, 3] becomes [1, 2, 3] -func Deparse(expr types.Expr) (interface{}, error) { +func Deparse(expr types.Expr) ([]byte, error) { value, err := deparse(expr) if err != nil { return nil, err diff --git a/dsl/execute.go b/dsl/execute.go index 0d13bf59..fda03d60 100644 --- a/dsl/execute.go +++ b/dsl/execute.go @@ -1,11 +1,16 @@ package dsl import ( + "bytes" "context" + "io" + "os" + "path/filepath" "github.com/pkg/errors" "github.com/vim-volt/volt/dsl/dslctx" "github.com/vim-volt/volt/dsl/types" + "github.com/vim-volt/volt/pathutil" "github.com/vim-volt/volt/transaction" ) @@ -26,6 +31,18 @@ func Execute(ctx context.Context, expr types.Expr) (_ types.Value, result error) } }() + // Expand all macros before write + expr, err = expandMacro(expr) + if err != nil { + return nil, errors.Wrap(err, "failed to expand macros") + } + + // Write given expression to $VOLTPATH/trx/lock/log.json + err = writeExpr(expr) + if err != nil { + return nil, err + } + val, rollback, err := expr.Eval(ctx) if err != nil { rollback() @@ -33,3 +50,58 @@ func Execute(ctx context.Context, expr types.Expr) (_ types.Value, result error) } return val, nil } + +func expandMacro(expr types.Expr) (types.Expr, error) { + val, err := doExpandMacro(expr) + if err != nil { + return nil, err + } + result, ok := val.(types.Expr) + if !ok { + return nil, errors.New("the result of expansion of macros must be an expression") + } + return result, nil +} + +func writeExpr(expr types.Expr) error { + deparsed, err := Deparse(expr) + if err != nil { + return errors.Wrap(err, "failed to deparse expression") + } + + filename := filepath.Join(pathutil.TrxDir(), "lock", "log.json") + logFile, err := os.Create(filename) + if err != nil { + return errors.Wrapf(err, "could not create %s", filename) + } + _, err = io.Copy(logFile, bytes.NewReader(deparsed)) + if err != nil { + return errors.Wrapf(err, "failed to write transaction log %s", filename) + } + err = logFile.Close() + if err != nil { + return errors.Wrapf(err, "failed to close transaction log %s", filename) + } + return nil +} + +// doExpandMacro expands macro's expression recursively +func doExpandMacro(expr types.Expr) (types.Value, error) { + op := expr.Op() + if !op.IsMacro() { + return expr, nil + } + args := expr.Args() + for i := range args { + if inner, ok := args[i].(types.Expr); ok { + v, err := doExpandMacro(inner) + if err != nil { + return nil, err + } + args[i] = v + } + } + // XXX: should we care rollback function? + val, _, err := op.EvalExpr(context.Background(), args) + return val, err +} From 4d77f86d00287dd9589ab5f9bb1e9e5712fdabf6 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 20:39:59 +0900 Subject: [PATCH 59/79] fix: fix bug that operator name is not deparsed --- dsl/deparse.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/dsl/deparse.go b/dsl/deparse.go index 0163a4cf..8e64ecc3 100644 --- a/dsl/deparse.go +++ b/dsl/deparse.go @@ -2,8 +2,8 @@ package dsl import ( "encoding/json" - "fmt" + "github.com/pkg/errors" "github.com/vim-volt/volt/dsl/ops" "github.com/vim-volt/volt/dsl/types" ) @@ -30,30 +30,31 @@ func deparse(value types.Value) (interface{}, error) { case types.Number: return val.Value(), nil case types.Object: - m := make(map[string]interface{}, len(val.Value())) + result := make(map[string]interface{}, len(val.Value())) for k, o := range val.Value() { v, err := deparse(o) if err != nil { return nil, err } - m[k] = v + result[k] = v } - return m, nil + return result, nil case types.Expr: - a := make([]interface{}, 0, len(val.Args())+1) + args := val.Args() + result := make([]interface{}, 0, len(args)+1) // Do not include "@" in array literal if val.Op().String() != ops.ArrayOp.String() { - a = append(a, types.NewString(val.Op().String())) + result = append(result, val.Op().String()) } - for i := range a { - v, err := deparse(val.Args()[i]) + for i := range args { + v, err := deparse(args[i]) if err != nil { return nil, err } - a = append(a, v) + result = append(result, v) } - return a, nil + return result, nil default: - return nil, fmt.Errorf("unknown value was given '%+v'", val) + return nil, errors.Errorf("unknown value was given '%+v'", val) } } From 0224d51961d0fa50524c67fd9a78ff64cd09c1e4 Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 20:54:19 +0900 Subject: [PATCH 60/79] feat: add json tags to Config struct --- config/config.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config/config.go b/config/config.go index fc8feedf..94019f83 100644 --- a/config/config.go +++ b/config/config.go @@ -9,20 +9,20 @@ import ( // Config is marshallable content of config.toml type Config struct { - Alias map[string][]string `toml:"alias"` - Build configBuild `toml:"build"` - Get configGet `toml:"get"` + Alias map[string][]string `toml:"alias" json:"alias"` + Build configBuild `toml:"build" json:"build"` + Get configGet `toml:"get" json:"get"` } // configBuild is a config for 'volt build'. type configBuild struct { - Strategy string `toml:"strategy"` + Strategy string `toml:"strategy" json:"strategy"` } // configGet is a config for 'volt get'. type configGet struct { - CreateSkeletonPlugconf *bool `toml:"create_skeleton_plugconf"` - FallbackGitCmd *bool `toml:"fallback_git_cmd"` + CreateSkeletonPlugconf *bool `toml:"create_skeleton_plugconf" json:"create_skeleton_plugconf"` + FallbackGitCmd *bool `toml:"fallback_git_cmd" json:"fallback_git_cmd"` } const ( From 89360a8fab3a3f8642f442131d9ad3c991c3e22e Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 20:54:51 +0900 Subject: [PATCH 61/79] feat: save also lock.json and config info to transacion log --- dsl/deparse.go | 16 +++------------- dsl/execute.go | 23 ++++++++++++++++++++--- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/dsl/deparse.go b/dsl/deparse.go index 8e64ecc3..03b2fb55 100644 --- a/dsl/deparse.go +++ b/dsl/deparse.go @@ -1,8 +1,6 @@ package dsl import ( - "encoding/json" - "github.com/pkg/errors" "github.com/vim-volt/volt/dsl/ops" "github.com/vim-volt/volt/dsl/types" @@ -10,15 +8,7 @@ import ( // Deparse deparses types.Expr. // ["@", 1, 2, 3] becomes [1, 2, 3] -func Deparse(expr types.Expr) ([]byte, error) { - value, err := deparse(expr) - if err != nil { - return nil, err - } - return json.Marshal(value) -} - -func deparse(value types.Value) (interface{}, error) { +func Deparse(value types.Value) (interface{}, error) { if value.Type() == types.NullType { return nil, nil } @@ -32,7 +22,7 @@ func deparse(value types.Value) (interface{}, error) { case types.Object: result := make(map[string]interface{}, len(val.Value())) for k, o := range val.Value() { - v, err := deparse(o) + v, err := Deparse(o) if err != nil { return nil, err } @@ -47,7 +37,7 @@ func deparse(value types.Value) (interface{}, error) { result = append(result, val.Op().String()) } for i := range args { - v, err := deparse(args[i]) + v, err := Deparse(args[i]) if err != nil { return nil, err } diff --git a/dsl/execute.go b/dsl/execute.go index fda03d60..b39d24e4 100644 --- a/dsl/execute.go +++ b/dsl/execute.go @@ -3,13 +3,16 @@ package dsl import ( "bytes" "context" + "encoding/json" "io" "os" "path/filepath" "github.com/pkg/errors" + "github.com/vim-volt/volt/config" "github.com/vim-volt/volt/dsl/dslctx" "github.com/vim-volt/volt/dsl/types" + "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/pathutil" "github.com/vim-volt/volt/transaction" ) @@ -38,7 +41,7 @@ func Execute(ctx context.Context, expr types.Expr) (_ types.Value, result error) } // Write given expression to $VOLTPATH/trx/lock/log.json - err = writeExpr(expr) + err = writeTrxLog(ctx, expr) if err != nil { return nil, err } @@ -63,18 +66,32 @@ func expandMacro(expr types.Expr) (types.Expr, error) { return result, nil } -func writeExpr(expr types.Expr) error { +func writeTrxLog(ctx context.Context, expr types.Expr) error { deparsed, err := Deparse(expr) if err != nil { return errors.Wrap(err, "failed to deparse expression") } + type contentT struct { + Expr interface{} `json:"expr"` + Config *config.Config `json:"config"` + LockJSON *lockjson.LockJSON `json:"lockjson"` + } + content, err := json.Marshal(&contentT{ + Expr: deparsed, + Config: ctx.Value(dslctx.ConfigKey).(*config.Config), + LockJSON: ctx.Value(dslctx.LockJSONKey).(*lockjson.LockJSON), + }) + if err != nil { + return errors.Wrap(err, "failed to marshal as JSON") + } + filename := filepath.Join(pathutil.TrxDir(), "lock", "log.json") logFile, err := os.Create(filename) if err != nil { return errors.Wrapf(err, "could not create %s", filename) } - _, err = io.Copy(logFile, bytes.NewReader(deparsed)) + _, err = io.Copy(logFile, bytes.NewReader(content)) if err != nil { return errors.Wrapf(err, "failed to write transaction log %s", filename) } From b9ec437ef2732cde05cb41ec7434bce30ee06e9f Mon Sep 17 00:00:00 2001 From: tyru Date: Sun, 22 Apr 2018 21:03:43 +0900 Subject: [PATCH 62/79] refactor: move writeTrxLog function def to bottom --- dsl/execute.go | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/dsl/execute.go b/dsl/execute.go index b39d24e4..3e5bdf33 100644 --- a/dsl/execute.go +++ b/dsl/execute.go @@ -66,6 +66,27 @@ func expandMacro(expr types.Expr) (types.Expr, error) { return result, nil } +// doExpandMacro expands macro's expression recursively +func doExpandMacro(expr types.Expr) (types.Value, error) { + op := expr.Op() + if !op.IsMacro() { + return expr, nil + } + args := expr.Args() + for i := range args { + if inner, ok := args[i].(types.Expr); ok { + v, err := doExpandMacro(inner) + if err != nil { + return nil, err + } + args[i] = v + } + } + // XXX: should we care rollback function? + val, _, err := op.EvalExpr(context.Background(), args) + return val, err +} + func writeTrxLog(ctx context.Context, expr types.Expr) error { deparsed, err := Deparse(expr) if err != nil { @@ -101,24 +122,3 @@ func writeTrxLog(ctx context.Context, expr types.Expr) error { } return nil } - -// doExpandMacro expands macro's expression recursively -func doExpandMacro(expr types.Expr) (types.Value, error) { - op := expr.Op() - if !op.IsMacro() { - return expr, nil - } - args := expr.Args() - for i := range args { - if inner, ok := args[i].(types.Expr); ok { - v, err := doExpandMacro(inner) - if err != nil { - return nil, err - } - args[i] = v - } - } - // XXX: should we care rollback function? - val, _, err := op.EvalExpr(context.Background(), args) - return val, err -} From 73767a81590189d1eb9412f34af91cc6064b1ff4 Mon Sep 17 00:00:00 2001 From: Takuya Fujiwara Date: Sun, 22 Apr 2018 22:37:14 +0900 Subject: [PATCH 63/79] doc: update README.md --- README.md | 48 ++++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 0e95e094..a6955431 100644 --- a/README.md +++ b/README.md @@ -306,14 +306,7 @@ See [plugconf directory](https://github.com/tyru/dotfiles/tree/75a37b4a640a5cffe You can think this is similar feature of **branch** of `git`. The default profile name is "default". -You can see profile list by `volt profile list`. - -``` -$ volt profile list -* default -``` - -You can create a new profile by `volt profile new`. +You can create an *empty* profile by `volt profile new`. ``` $ volt profile new foo # will create profile "foo" @@ -322,7 +315,8 @@ $ volt profile list foo ``` -You can switch current profile by `volt profile set`. +Then you can switch current profile by `volt profile set`. +This removes all plugins from `~/.vim/pack/volt/opt/*`, because the new created profile is empty; no plugins are included. ``` $ volt profile set foo # will switch profile to "foo" @@ -331,38 +325,40 @@ $ volt profile list * foo ``` -You can delete profile by `volt profile destroy` (but you cannot delete current profile which you are switching on). +You can install new plugins or enable installed plugins **only in the current profile.** +`volt enable` is a shortcut of `volt profile add -current`. ``` -$ volt profile destroy foo # will delete profile "foo" +$ volt enable foo/bar bar/baz # enable installed plugins (foo/bar, bar/baz) also in new profile +$ volt profile add -current foo/bar bar/baz # same as above +$ volt get foo/bar bar/baz # or you can just use 'volt get', this installs missing plugins (it just includes plugins if already installed) ``` -You can enable/disable plugin by `volt enable` (`volt profile add`), `volt disable` (`volt profile rm`). +You can disable plugins by `volt disable`. +This is a shortcut of `volt profile rm -current`. ``` -$ volt enable tyru/caw.vim # enable loading tyru/caw.vim on current profile -$ volt profile add foo tyru/caw.vim # enable loading tyru/caw.vim on "foo" profile +$ volt disable foo/bar # disable loading foo/bar on current profile +$ volt profile rm -current foo/bar # same as above +$ volt profile rm foo foo/bar # or disable plugins outside current profile (of course 'volt profile add' can do it too) ``` +You can delete profile by `volt profile destroy` (but you cannot delete current profile which you are switching on). + ``` -$ volt disable tyru/caw.vim # disable loading tyru/caw.vim on current profile -$ volt profile rm foo tyru/caw.vim # disable loading tyru/caw.vim on "foo" profile +$ volt profile destroy foo # will delete profile "foo" ``` -You can create a vimrc & gvimrc file for each profile: +--- + +And you can create local vimrc & gvimrc files for each profile: * vimrc: `$VOLTPATH/rc//vimrc.vim` * gvimrc: `$VOLTPATH/rc//gvimrc.vim` -NOTE: If the path(s) exists, `$MYVIMRC` and `$MYGVIMRC` are set. So `:edit $MYVIMRC` does not open generated vimrc (`~/.vim/vimrc`), but above vimrc/gvimrc. +NOTE: If the path(s) exists, `$MYVIMRC` and `$MYGVIMRC` are set. So `:edit $MYVIMRC` opens above vimrc/gvimrc, not generated vimrc (`~/.vim/vimrc`). -This file is copied to `~/.vim/vimrc` and `~/.vim/gvimrc` with magic comment (shows error if existing vimrc/gvimrc files exist with no magic comment). - -And you can enable/disable vimrc by `volt profile use` (or you can simply remove `$VOLTPATH/rc//vimrc.vim` file if you don't want vimrc for the profile). - -``` -$ volt profile use -current vimrc false # Disable installing vimrc on current profile -$ volt profile use default gvimrc true # Enable installing gvimrc on profile default -``` +The files are copied to `~/.vim/vimrc` and `~/.vim/gvimrc` with magic comment. +Because volt shows an error if existing vimrc/gvimrc files exist with no magic comment which is not created by volt. See `volt help profile` for more detailed information. From 8c51e1f739f9b40679861d1f1ca0dc9581864235 Mon Sep 17 00:00:00 2001 From: tyru Date: Mon, 23 Apr 2018 00:04:46 +0900 Subject: [PATCH 64/79] refactor: fix comments --- dsl/types/json.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dsl/types/json.go b/dsl/types/json.go index 9774960f..bf2c9cdf 100644 --- a/dsl/types/json.go +++ b/dsl/types/json.go @@ -29,7 +29,7 @@ var TrueValue = &boolT{true} // FalseValue is the JSON false value var FalseValue = &boolT{false} -// Bool is JSON boolean struct +// Bool is JSON boolean value type Bool interface { Value @@ -67,7 +67,7 @@ func (*boolT) Type() Type { // ================ Number ================ -// Number is JSON number struct +// Number is JSON number value type Number interface { Value @@ -102,7 +102,7 @@ func (*numberT) Type() Type { // ================ String ================ -// String is JSON string struct +// String is JSON string value type String interface { Value @@ -137,7 +137,7 @@ func (*stringT) Type() Type { // ================ Array ================ -// Array is JSON array struct +// Array is JSON array value type Array interface { Value @@ -175,7 +175,7 @@ func (v *arrayT) Type() Type { // ================ Object ================ -// Object is JSON object struct +// Object is JSON object value type Object interface { Value From df969949ab4b66bfcb5e3fc5afcb8691247b85d2 Mon Sep 17 00:00:00 2001 From: Takuya Fujiwara Date: Mon, 23 Apr 2018 01:03:29 +0900 Subject: [PATCH 65/79] doc: update README.md --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a6955431..e1eaa951 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ See [the command reference](https://github.com/vim-volt/volt/blob/master/CMDREF. * Or `go get github.com/vim-volt/volt` * You need Go 1.9 or higher * And if you are using Windows Subsystem Linux, you need to apply **[the patch for os.RemoveAll()](https://go-review.googlesource.com/c/go/+/62970) ! ([#1](https://github.com/vim-volt/go-volt/issues/1))** - * But it's a hassle, you can just download linux-386/amd64 binaries from [GitHub releases](https://github.com/vim-volt/volt/releases) :) + * But it's a hassle, you can just download linux-(386/amd64) binaries from [GitHub releases](https://github.com/vim-volt/volt/releases) :) And there is bash completion script in [\_contrib](https://github.com/vim-volt/volt/blob/master/_contrib/completion/bash) directory (thanks @AvianY). @@ -119,12 +119,10 @@ You can update all plugins as follows: $ volt get -l -u ``` -`-l` works like all plugins in current profile are specified (the repositories list is read from `$VOLTPATH/lock.json`). -If you do not use profile feature, or `enable` and `disable` commands, you can -think that `-l` specifies all plugins what you have installed. `-u` updates specified plugins. +`-l` works like all plugins in current profile are specified (the repositories list is read from `$VOLTPATH/lock.json`). -Or, update only specified plugin(s) as follows: +Or you can update only specified plugin(s) as follows: ``` $ volt get -u tyru/caw.vim From e2ae2abdf843baa0f34e01ad365512e3142a4c18 Mon Sep 17 00:00:00 2001 From: tyru Date: Mon, 23 Apr 2018 14:49:38 +0900 Subject: [PATCH 66/79] fix: use defer --- dsl/execute.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/dsl/execute.go b/dsl/execute.go index 3e5bdf33..f26113ea 100644 --- a/dsl/execute.go +++ b/dsl/execute.go @@ -87,7 +87,7 @@ func doExpandMacro(expr types.Expr) (types.Value, error) { return val, err } -func writeTrxLog(ctx context.Context, expr types.Expr) error { +func writeTrxLog(ctx context.Context, expr types.Expr) (result error) { deparsed, err := Deparse(expr) if err != nil { return errors.Wrap(err, "failed to deparse expression") @@ -112,13 +112,14 @@ func writeTrxLog(ctx context.Context, expr types.Expr) error { if err != nil { return errors.Wrapf(err, "could not create %s", filename) } + defer func() { + if err := logFile.Close(); err != nil { + result = errors.Wrapf(err, "failed to close transaction log %s", filename) + } + }() _, err = io.Copy(logFile, bytes.NewReader(content)) if err != nil { return errors.Wrapf(err, "failed to write transaction log %s", filename) } - err = logFile.Close() - if err != nil { - return errors.Wrapf(err, "failed to close transaction log %s", filename) - } return nil } From edf18c38b6dc688adfc58071b0c56049612a302d Mon Sep 17 00:00:00 2001 From: tyru Date: Mon, 23 Apr 2018 17:50:08 +0900 Subject: [PATCH 67/79] doc: change to eager evaluation, and add lambda --- _docs/json-dsl.md | 190 ++++++++++++++++++++++++++++++---------------- 1 file changed, 126 insertions(+), 64 deletions(-) diff --git a/_docs/json-dsl.md b/_docs/json-dsl.md index 629f38a9..a47f8d30 100644 --- a/_docs/json-dsl.md +++ b/_docs/json-dsl.md @@ -6,27 +6,28 @@ ## Example of JSON DSL ```json -["label", +["$label", 1, "installing plugins:", - ["vimdir/with-install", - ["parallel", - ["label", + ["$vimdir/with-install", + ["$parallel", + ["$label", 2, " github.com/tyru/open-browser.vim ... {{if .Done}}done!{{end}}", - ["parallel", + ["$parallel", ["lockjson/add", ["repos/get", "github.com/tyru/open-browser.vim"], ["$array", "default"]], ["plugconf/install", "github.com/tyru/open-browser.vim"]]], - ["label", + ["$label", 3, " github.com/tyru/open-browser-github.vim ... {{if .Done}}done!{{end}}", - ["parallel", + ["$parallel", ["lockjson/add", ["repos/get", "github.com/tyru/open-browser-github.vim"], ["$array", "default"]], - ["plugconf/install", "github.com/tyru/open-browser-github.vim"]]]]]] + ["plugconf/install", + "github.com/tyru/open-browser-github.vim"]]]]]] ``` ## Wordings @@ -107,7 +108,7 @@ An array literal value is written using `$array` operator. This expression is evaluated to `[1, 2, 3]`. Each expression has 0 or more parameters. And evaluation -strategy is a non-strict evaluation. +strategy is an eager evaluation. Parameter types are @@ -117,7 +118,8 @@ Parameter types are * number * array * object -* expression +* lambda +* expression (only macro can treat this) But all values must be able to be serialized to JSON. Because AST of whole process is serialized and saved as a "transaction log file". The process can be @@ -169,14 +171,29 @@ different with latest volt's JSON DSL when installing. this is a simplified version for easiness). ```json -["vimdir/with-install", - ["do", +["$vimdir/with-install", + ["$do", ["lockjson/add", ["repos/get", "github.com/tyru/caw.vim"], ["$array", "default"]], ["plugconf/install", "github.com/tyru/caw.vim"]]] ``` +At first, expands all macros (`["$array", "default"]` cannot be written in JSON +notation, it is expanded to array but array literal is `["$array", "default"]`). + +```json +["vimdir/with-install", + ["fn", [], + ["do", + ["fn", [], + ["lockjson/add", + ["repos/get", "github.com/tyru/caw.vim"], + ["$array", "default"]]], + ["fn", [], + ["plugconf/install", "github.com/tyru/caw.vim"]]]]] +``` + I show you what happens in several steps when you "invert" the expression like `volt history undo`. @@ -185,41 +202,65 @@ At first, to invert the expression, `$invert` macro is used: ```json ["$invert", ["vimdir/with-install", - ["do", - ["lockjson/add", - ["repos/get", "github.com/tyru/caw.vim"], - ["$array", "default"]], - ["plugconf/install", "github.com/tyru/caw.vim"]]]] + ["fn", [], + ["do", + ["fn", [], + ["lockjson/add", + ["repos/get", "github.com/tyru/caw.vim"], + ["$array", "default"]]], + ["fn", [], + ["plugconf/install", "github.com/tyru/caw.vim"]]]]]] ``` -`["$invert", ["vimdir/with-install", expr]]` is expanded to -`["vimdir/with-install", ["$invert", expr]]`. Internally, it is implemented as -calling `Invert()` method of `vimdir/with-install` operator struct. See "Go -API" section. +`["$invert", ["vimdir/with-install", thunk]]` is expanded to +`["vimdir/with-install", ["$invert", thunk]]`. Internally, it is implemented as +calling `InvertExpr()` method of `vimdir/with-install` operator struct. ```json ["vimdir/with-install", ["$invert", - ["do", - ["lockjson/add", - ["repos/get", "github.com/tyru/caw.vim"], - ["$array", "default"]], - ["plugconf/install", "github.com/tyru/caw.vim"]]]] + ["fn", [], + ["do", + ["fn", [], + ["lockjson/add", + ["repos/get", "github.com/tyru/caw.vim"], + ["$array", "default"]]], + ["fn", [], + ["plugconf/install", "github.com/tyru/caw.vim"]]]]]] ``` -And `["$invert", ["do", expr1, expr2]]` becomes -`["do", ["$invert", expr2], ["$invert", expr1]]`. -Note that `expr1` and `expr2` becomes reversed order. +And `["$invert", ["fn", args, body]]` becomes +`["fn", args, ["$invert", body]]`. ```json ["vimdir/with-install", - ["do", - ["$invert", - ["plugconf/install", "github.com/tyru/caw.vim"]], + ["fn", [], ["$invert", - ["lockjson/add", - ["repos/get", "github.com/tyru/caw.vim"], - ["$array", "default"]]]]] + ["do", + ["fn", [], + ["lockjson/add", + ["repos/get", "github.com/tyru/caw.vim"], + ["$array", "default"]]], + ["fn", [], + ["plugconf/install", "github.com/tyru/caw.vim"]]]]]] +``` + +And `["$invert", ["do", thunk1, thunk2]]` becomes +`["do", ["$invert", thunk2], ["$invert", thunk1]]`. +Note that `thunk1` and `thunk2` become reversed order. + +```json +["vimdir/with-install", + ["fn", [], + ["do", + ["fn", [], + ["$invert", + ["lockjson/add", + ["repos/get", "github.com/tyru/caw.vim"], + ["$array", "default"]]]], + ["fn", [], + ["$invert", + ["plugconf/install", "github.com/tyru/caw.vim"]]]]]] ``` And @@ -230,11 +271,14 @@ And ```json ["vimdir/with-install", - ["do", - ["plugconf/delete", ["$invert", "github.com/tyru/caw.vim"]], - ["lockjson/remove", - ["$invert", ["repos/get", "github.com/tyru/caw.vim"]], - ["$invert", ["$array", "default"]]]]] + ["fn", [], + ["do", + ["fn", [], + ["lockjson/add", + ["$invert", ["repos/get", "github.com/tyru/caw.vim"]], + ["$invert", ["$array", "default"]]]], + ["fn", [], + ["plugconf/install", ["$invert", "github.com/tyru/caw.vim"]]]]]] ``` `["$invert", ["repos/get", path]]` becomes @@ -242,11 +286,14 @@ And ```json ["vimdir/with-install", - ["do", - ["plugconf/delete", ["$invert", "github.com/tyru/caw.vim"]], - ["lockjson/remove", - ["repos/delete", ["$invert", "github.com/tyru/caw.vim"]], - ["$invert", ["$array", "default"]]]]] + ["fn", [], + ["do", + ["fn", [], + ["lockjson/add", + ["repos/delete", ["$invert", "github.com/tyru/caw.vim"]], + ["$invert", ["$array", "default"]]]], + ["fn", [], + ["plugconf/install", ["$invert", "github.com/tyru/caw.vim"]]]]]] ``` And if `$invert` is applied to literals like string, JSON array, it just remains @@ -254,14 +301,17 @@ as-is. ```json ["vimdir/with-install", - ["do", - ["plugconf/delete", "github.com/tyru/caw.vim"], - ["lockjson/remove", - ["repos/delete", "github.com/tyru/caw.vim"], - ["$array", "default"]]]] + ["fn", [], + ["do", + ["fn", [], + ["lockjson/add", + ["repos/delete", "github.com/tyru/caw.vim"], + ["$array", "default"]]], + ["fn", [], + ["plugconf/install", "github.com/tyru/caw.vim"]]]]] ``` -We can successfully evaluate the inverse expression of the first expression :) +We can successfully evaluate the inverse expression of the first expression! :) ### Uninstall operation should be "Recovable"? @@ -317,7 +367,7 @@ As the above signature shows, operators must take care the following points: TODO: Move to Godoc. -### Macro +### Basic macros All macros has `$` prefixed name for readability. Macros are not saved in transaction log (expanded before saving). @@ -342,27 +392,39 @@ Macros are not saved in transaction log (expanded before saving). ### Basic operators -* `["label", linenum: number, tmpl string, expr Expr[* => R]] R` +* `["$label", linenum: number, tmpl string, expr Expr[* => R]] R` + * `["$label", linenum, tmpl, expr]` expands to + `["label", linenum, tmpl, ["fn", [] expr]]` + +* `["label", linenum: number, tmpl string, thunk Func[* => R]] R` * Render `tmpl` by text/template to `linenum` line (1-origin). - Returns the evaluated value of `expr`. + Returns the evaluated value of `thunk`. * e.g. - * `["$invert", ["label", linenum, "msg", expr]]` = `["label", ["$invert", linenum], "revert: \"msg\"", ["$invert", expr]]` + * `["$invert", ["label", linenum, "msg", thunk]]` = `["label", ["$invert", linenum], "revert: \"msg\"", ["$invert", thunk]]` * See `Label examples` section for more details -* `["do", expr1 R1, ..., expr_last R2] R2` - * Executes multiple expressions in series. - * Returns the evaluated value of the last expression. +* `["$do", expr1 Expr[* => R1], ..., expr_last Expr[* => R2]] R2` + * `["$do", expr1, expr2]` expands to + `["do", ["fn", [], expr1], ["fn", [], expr2]]` + +* `["do", thunk1 R1, ..., thunk_last R2] R2` + * Executes multiple lambdas in series. + * Returns the evaluated value of the last lambda. * e.g. - * `["$invert", ["do", expr1, expr2]]` = `["do", ["$invert", expr1], ["$invert", expr2]]` + * `["$invert", ["do", thunk1, thunk2]]` = `["do", ["$invert", thunk1], ["$invert", thunk2]]` * Note that the arguments are reversed. -* `["parallel", msg string, expr1 R1, ..., expr_last R2] R2` - * Executes multiple expressions in parallel. - * Returns the evaluated value of the last expression. +* `["$parallel", expr1 Expr[* => R1], ..., expr_last Expr[* => R2]] R2` + * `["$parallel", expr1, expr2]` expands to + `["parallel", ["fn", [], expr1], ["fn", [], expr2]]` + +* `["parallel", thunk1 Func[* => R1], ..., thunk_last Func[* => R2]] R2` + * Executes multiple lambdas in parallel. + * Returns the evaluated value of the last lambda. * e.g. - * `["$invert", ["parallel", expr1, expr2]]` = `["parallel", ["$invert", expr2], ["$invert", expr1]]` + * `["$invert", ["parallel", thunk1, thunk2]]` = `["parallel", ["$invert", thunk2], ["$invert", thunk1]]` * The arguments are **not** reversed because parallel does not care of - execution order of given expressions. + execution order of given value. ### Repository operators From 1cb653a0d23cf5494123d3136998b0a3f132a947 Mon Sep 17 00:00:00 2001 From: tyru Date: Mon, 23 Apr 2018 17:55:03 +0900 Subject: [PATCH 68/79] refactor: remove NoRollback, use nil instead --- dsl/execute.go | 4 +++- dsl/ops/func_lockjson_write.go | 4 +--- dsl/ops/func_migrate_plugconf_config_func.go | 3 +-- dsl/ops/macro_array.go | 2 +- dsl/ops/macro_eval.go | 2 +- dsl/ops/macro_invert.go | 4 ++-- dsl/ops/rollback.go | 5 ----- dsl/types/json.go | 12 ++++++------ 8 files changed, 15 insertions(+), 21 deletions(-) delete mode 100644 dsl/ops/rollback.go diff --git a/dsl/execute.go b/dsl/execute.go index f26113ea..ada292b1 100644 --- a/dsl/execute.go +++ b/dsl/execute.go @@ -48,7 +48,9 @@ func Execute(ctx context.Context, expr types.Expr) (_ types.Value, result error) val, rollback, err := expr.Eval(ctx) if err != nil { - rollback() + if rollback != nil { + rollback() + } return nil, errors.Wrap(err, "expression returned an error") } return val, nil diff --git a/dsl/ops/func_lockjson_write.go b/dsl/ops/func_lockjson_write.go index 58354c34..62226027 100644 --- a/dsl/ops/func_lockjson_write.go +++ b/dsl/ops/func_lockjson_write.go @@ -33,9 +33,7 @@ func (*lockJSONWriteOp) InvertExpr(_ context.Context, args []types.Value) (types return LockJSONWriteOp.Bind(args...) } -func (*lockJSONWriteOp) EvalExpr(ctx context.Context, args []types.Value) (_ types.Value, rollback func(), result error) { - rollback = NoRollback - +func (*lockJSONWriteOp) EvalExpr(ctx context.Context, args []types.Value) (_ types.Value, _ func(), result error) { lockJSON := ctx.Value(dslctx.LockJSONKey).(*lockjson.LockJSON) result = lockJSON.Write() if result != nil { diff --git a/dsl/ops/func_migrate_plugconf_config_func.go b/dsl/ops/func_migrate_plugconf_config_func.go index cd879c09..eb707ad3 100644 --- a/dsl/ops/func_migrate_plugconf_config_func.go +++ b/dsl/ops/func_migrate_plugconf_config_func.go @@ -44,8 +44,7 @@ func (*migratePlugconfConfigFuncOp) InvertExpr(_ context.Context, args []types.V return MigratePlugconfConfigFuncOp.Bind(args...) } -func (*migratePlugconfConfigFuncOp) EvalExpr(ctx context.Context, args []types.Value) (_ types.Value, rollback func(), result error) { - rollback = NoRollback +func (*migratePlugconfConfigFuncOp) EvalExpr(ctx context.Context, args []types.Value) (_ types.Value, _ func(), result error) { lockJSON := ctx.Value(dslctx.LockJSONKey).(*lockjson.LockJSON) cfg := ctx.Value(dslctx.ConfigKey).(*config.Config) diff --git a/dsl/ops/macro_array.go b/dsl/ops/macro_array.go index dcd13ae4..e2808433 100644 --- a/dsl/ops/macro_array.go +++ b/dsl/ops/macro_array.go @@ -28,5 +28,5 @@ func (*arrayOp) Bind(args ...types.Value) (types.Expr, error) { } func (*arrayOp) EvalExpr(ctx context.Context, args []types.Value) (types.Value, func(), error) { - return types.NewArray(args, types.AnyValue), NoRollback, nil + return types.NewArray(args, types.AnyValue), nil, nil } diff --git a/dsl/ops/macro_eval.go b/dsl/ops/macro_eval.go index a3e09f3e..4716aa5f 100644 --- a/dsl/ops/macro_eval.go +++ b/dsl/ops/macro_eval.go @@ -30,7 +30,7 @@ func (*evalOp) Bind(args ...types.Value) (types.Expr, error) { func (*evalOp) EvalExpr(ctx context.Context, args []types.Value) (types.Value, func(), error) { if err := util.Signature(types.AnyValue).Check(args); err != nil { - return nil, NoRollback, err + return nil, nil, err } return args[0].Eval(ctx) } diff --git a/dsl/ops/macro_invert.go b/dsl/ops/macro_invert.go index 5c9ca5f1..123e806d 100644 --- a/dsl/ops/macro_invert.go +++ b/dsl/ops/macro_invert.go @@ -30,8 +30,8 @@ func (*invertOp) Bind(args ...types.Value) (types.Expr, error) { func (*invertOp) EvalExpr(ctx context.Context, args []types.Value) (types.Value, func(), error) { if err := util.Signature(types.AnyValue).Check(args); err != nil { - return nil, NoRollback, err + return nil, nil, err } val, err := args[0].Invert(ctx) - return val, NoRollback, err + return val, nil, err } diff --git a/dsl/ops/rollback.go b/dsl/ops/rollback.go deleted file mode 100644 index c2554dc7..00000000 --- a/dsl/ops/rollback.go +++ /dev/null @@ -1,5 +0,0 @@ -package ops - -// NoRollback is an empty function. -// intepreter can skip this function. -var NoRollback = func() {} diff --git a/dsl/types/json.go b/dsl/types/json.go index bf2c9cdf..a0718148 100644 --- a/dsl/types/json.go +++ b/dsl/types/json.go @@ -14,7 +14,7 @@ func (*nullT) Invert(context.Context) (Value, error) { } func (v *nullT) Eval(context.Context) (val Value, rollback func(), err error) { - return v, func() {}, nil + return v, nil, nil } func (*nullT) Type() Type { @@ -58,7 +58,7 @@ func (v *boolT) Invert(context.Context) (Value, error) { } func (v *boolT) Eval(context.Context) (val Value, rollback func(), err error) { - return v, func() {}, nil + return v, nil, nil } func (*boolT) Type() Type { @@ -93,7 +93,7 @@ func (v *numberT) Invert(context.Context) (Value, error) { } func (v *numberT) Eval(context.Context) (val Value, rollback func(), err error) { - return v, func() {}, nil + return v, nil, nil } func (*numberT) Type() Type { @@ -128,7 +128,7 @@ func (v *stringT) Invert(context.Context) (Value, error) { } func (v *stringT) Eval(context.Context) (val Value, rollback func(), err error) { - return v, func() {}, nil + return v, nil, nil } func (*stringT) Type() Type { @@ -166,7 +166,7 @@ func (v *arrayT) Invert(context.Context) (Value, error) { } func (v *arrayT) Eval(context.Context) (val Value, rollback func(), err error) { - return v, func() {}, nil + return v, nil, nil } func (v *arrayT) Type() Type { @@ -204,7 +204,7 @@ func (v *objectT) Invert(context.Context) (Value, error) { } func (v *objectT) Eval(context.Context) (val Value, rollback func(), err error) { - return v, func() {}, nil + return v, nil, nil } func (v *objectT) Type() Type { From 7061c431308ebc1da0bad871d7b24e2c860ee8ee Mon Sep 17 00:00:00 2001 From: tyru Date: Mon, 23 Apr 2018 18:10:05 +0900 Subject: [PATCH 69/79] refactor: early return --- dsl/types/types.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/dsl/types/types.go b/dsl/types/types.go index d6a63ebc..ef07f5aa 100644 --- a/dsl/types/types.go +++ b/dsl/types/types.go @@ -115,10 +115,11 @@ func (t *arrayType) String() string { } func (t *arrayType) InstanceOf(t2 Type) bool { - if array, ok := t2.(*arrayType); ok { - return t.arg.InstanceOf(array.arg) + array, ok := t2.(*arrayType) + if !ok { + return false } - return false + return t.arg.InstanceOf(array.arg) } // ===================== Object type ===================== // @@ -137,10 +138,11 @@ func (t *objectType) String() string { } func (t *objectType) InstanceOf(t2 Type) bool { - if array, ok := t2.(*objectType); ok { - return t.arg.InstanceOf(array.arg) + object, ok := t2.(*objectType) + if !ok { + return false } - return false + return t.arg.InstanceOf(object.arg) } // ===================== Any type ===================== // From 8c07188ae6f5e1728de580c1e4a1ce05cedfe268 Mon Sep 17 00:00:00 2001 From: tyru Date: Mon, 23 Apr 2018 18:18:43 +0900 Subject: [PATCH 70/79] feat: add lambda type --- dsl/types/types.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/dsl/types/types.go b/dsl/types/types.go index ef07f5aa..394d37c9 100644 --- a/dsl/types/types.go +++ b/dsl/types/types.go @@ -1,5 +1,9 @@ package types +import ( + "bytes" +) + // Type is a type of a value type Type interface { // String returns a string like "" @@ -159,3 +163,43 @@ func (*anyType) String() string { func (*anyType) InstanceOf(_ Type) bool { return true } + +// ===================== Lambda type ===================== // + +// NewLambdaType creates lambda type instance. +// Signature must have 1 type at least for a return type. +func NewLambdaType(t Type, rest ...Type) Type { + signature := append([]Type{t}, rest...) + return &lambdaType{signature: signature} +} + +type lambdaType struct { + signature []Type +} + +func (t *lambdaType) String() string { + var arg bytes.Buffer + for i := range t.signature { + if i > 0 { + arg.WriteString(",") + } + arg.WriteString(t.signature[i].String()) + } + return "Lambda[" + arg.String() + "]" +} + +func (t *lambdaType) InstanceOf(t2 Type) bool { + lambda, ok := t2.(*lambdaType) + if !ok { + return false + } + if len(t.signature) != len(lambda.signature) { + return false + } + for i := range t.signature { + if !t.signature[i].InstanceOf(lambda.signature[i]) { + return false + } + } + return true +} From a9a6422aed14c06a4cc8e2b4eceb559e431de9db Mon Sep 17 00:00:00 2001 From: tyru Date: Mon, 23 Apr 2018 18:27:23 +0900 Subject: [PATCH 71/79] refactor: fix comments --- dsl/types/expr.go | 6 +++--- dsl/types/op.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dsl/types/expr.go b/dsl/types/expr.go index 7cf89182..da440b2c 100644 --- a/dsl/types/expr.go +++ b/dsl/types/expr.go @@ -6,13 +6,13 @@ import "context" type Expr interface { Value - // Op returns operator of Expr + // Op returns operator Op() Op - // Args returns arguments of Expr + // Args returns arguments Args() []Value - // RetType returns return type of Expr + // RetType returns return type RetType() Type } diff --git a/dsl/types/op.go b/dsl/types/op.go index 4d4629d7..ccfd1d8f 100644 --- a/dsl/types/op.go +++ b/dsl/types/op.go @@ -2,7 +2,7 @@ package types import "context" -// Op is an operation of JSON DSL +// Op is an operator of JSON DSL type Op interface { // String returns function name String() string From 3768dd6481ad24137b050975d8872d06011981cf Mon Sep 17 00:00:00 2001 From: tyru Date: Mon, 23 Apr 2018 18:35:18 +0900 Subject: [PATCH 72/79] perf: reduce unnecessary instance creation of Type() --- dsl/types/json.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dsl/types/json.go b/dsl/types/json.go index a0718148..188e6f06 100644 --- a/dsl/types/json.go +++ b/dsl/types/json.go @@ -149,12 +149,12 @@ type Array interface { // NewArray creates Array instance func NewArray(value []Value, argType Type) Array { - return &arrayT{value: value, argType: argType} + return &arrayT{value: value, typ: NewArrayType(argType)} } type arrayT struct { - value []Value - argType Type + value []Value + typ Type } func (v *arrayT) Value() []Value { @@ -170,7 +170,7 @@ func (v *arrayT) Eval(context.Context) (val Value, rollback func(), err error) { } func (v *arrayT) Type() Type { - return NewArrayType(v.argType) + return v.typ } // ================ Object ================ @@ -187,12 +187,12 @@ type Object interface { // NewObject creates Object instance func NewObject(value map[string]Value, argType Type) Object { - return &objectT{value: value, argType: argType} + return &objectT{value: value, typ: NewObjectType(argType)} } type objectT struct { - value map[string]Value - argType Type + value map[string]Value + typ Type } func (v *objectT) Value() map[string]Value { @@ -208,5 +208,5 @@ func (v *objectT) Eval(context.Context) (val Value, rollback func(), err error) } func (v *objectT) Type() Type { - return NewObjectType(v.argType) + return v.typ } From 3567fb5739e109888227c4b411484bd3cd2f2652 Mon Sep 17 00:00:00 2001 From: tyru Date: Mon, 23 Apr 2018 18:48:23 +0900 Subject: [PATCH 73/79] refactor: change Guard interface --- dsl/ops/func_do.go | 18 ++++++++++-------- dsl/ops/util/guard.go | 43 ++++++++++++++++++++++--------------------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/dsl/ops/func_do.go b/dsl/ops/func_do.go index 66a7ab22..dd9735ca 100644 --- a/dsl/ops/func_do.go +++ b/dsl/ops/func_do.go @@ -42,19 +42,21 @@ func (*doOp) InvertExpr(ctx context.Context, args []types.Value) (types.Value, e return DoOp.Bind(newargs...) } -func (*doOp) EvalExpr(ctx context.Context, args []types.Value) (val types.Value, rollback func(), result error) { +func (*doOp) EvalExpr(ctx context.Context, args []types.Value) (_ types.Value, _ func(), result error) { g := util.FuncGuard(DoOp.String()) - defer func() { result = g.Rollback(recover()) }() - rollback = g.RollbackForcefully + defer func() { + result = g.Error(recover()) + }() + var lastVal types.Value for i := range args { - v, rbFunc, e := args[i].Eval(ctx) + v, rbFunc, err := args[i].Eval(ctx) g.Add(rbFunc) - if e != nil { - result = g.Rollback(e) + if err != nil { + result = g.Error(err) return } - val = v + lastVal = v } - return + return lastVal, g.Rollback, nil } diff --git a/dsl/ops/util/guard.go b/dsl/ops/util/guard.go index f08d649c..46aec1a4 100644 --- a/dsl/ops/util/guard.go +++ b/dsl/ops/util/guard.go @@ -8,23 +8,25 @@ import ( // Guard invokes "rollback functions" if Rollback method received non-nil value // (e.g. recover(), non-nil error). type Guard interface { - // Rollback rolls back if v is non-nil. + // Error sets v as an error if v is non-nil. + // This returns the error. // - // defer func() { err = g.Rollback(recover()) }() + // defer func() { + // result = g.Error(recover()) + // }() // // // or // - // if e != nil { - // err = g.Rollback(e) - // err = g.Rollback(e) // this won't call rollback functions twice! - // return + // if err != nil { + // return g.Error(err) // } - Rollback(v interface{}) error + // + Error(v interface{}) error - // RollbackForcefully calls rollback functions in reversed order - RollbackForcefully() + // Rollback calls rollback functions in reversed order + Rollback() - // Add adds given rollback functions + // Add adds given rollback functions, but skips if f == nil Add(f func()) } @@ -35,23 +37,20 @@ func FuncGuard(name string) Guard { type guard struct { errMsg string + err error rbFuncs []func() } -func (g *guard) Rollback(v interface{}) error { - var err error - if e, ok := v.(error); ok { - err = e +func (g *guard) Error(v interface{}) error { + if err, ok := v.(error); ok { + g.err = errors.Wrap(err, g.errMsg) } else if v != nil { - err = fmt.Errorf("%s", v) - } - if err != nil { - g.RollbackForcefully() + g.err = errors.Wrap(fmt.Errorf("%s", v), g.errMsg) } - return errors.Wrap(err, g.errMsg) + return g.err } -func (g *guard) RollbackForcefully() { +func (g *guard) Rollback() { for i := len(g.rbFuncs) - 1; i >= 0; i-- { g.rbFuncs[i]() } @@ -59,5 +58,7 @@ func (g *guard) RollbackForcefully() { } func (g *guard) Add(f func()) { - g.rbFuncs = append(g.rbFuncs, f) + if f != nil { + g.rbFuncs = append(g.rbFuncs, f) + } } From 304a7abce52ae8e115928e03cef7d342a0112709 Mon Sep 17 00:00:00 2001 From: tyru Date: Mon, 23 Apr 2018 18:49:46 +0900 Subject: [PATCH 74/79] destructive: change to eager evaluation --- dsl/execute.go | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/dsl/execute.go b/dsl/execute.go index ada292b1..14914c58 100644 --- a/dsl/execute.go +++ b/dsl/execute.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/vim-volt/volt/config" "github.com/vim-volt/volt/dsl/dslctx" + "github.com/vim-volt/volt/dsl/ops/util" "github.com/vim-volt/volt/dsl/types" "github.com/vim-volt/volt/lockjson" "github.com/vim-volt/volt/pathutil" @@ -46,7 +47,7 @@ func Execute(ctx context.Context, expr types.Expr) (_ types.Value, result error) return nil, err } - val, rollback, err := expr.Eval(ctx) + val, rollback, err := evalDepthFirst(ctx, expr) if err != nil { if rollback != nil { rollback() @@ -56,6 +57,35 @@ func Execute(ctx context.Context, expr types.Expr) (_ types.Value, result error) return val, nil } +func evalDepthFirst(ctx context.Context, expr types.Expr) (_ types.Value, _ func(), result error) { + op := expr.Op() + g := util.FuncGuard(op.String()) + defer func() { + result = g.Error(recover()) + }() + + // Evaluate arguments first + args := expr.Args() + newArgs := make([]types.Value, 0, len(args)) + for i := range args { + innerExpr, ok := args[i].(types.Expr) + if !ok { + newArgs = append(newArgs, args[i]) + continue + } + ret, rbFunc, err := evalDepthFirst(ctx, innerExpr) + g.Add(rbFunc) + if err != nil { + return nil, g.Rollback, g.Error(err) + } + newArgs = append(newArgs, ret) + } + + ret, rbFunc, err := op.EvalExpr(ctx, newArgs) + g.Add(rbFunc) + return ret, g.Rollback, g.Error(err) +} + func expandMacro(expr types.Expr) (types.Expr, error) { val, err := doExpandMacro(expr) if err != nil { From 464b73f370148b8d453c14fc546d07b557bc29c6 Mon Sep 17 00:00:00 2001 From: tyru Date: Mon, 23 Apr 2018 18:51:07 +0900 Subject: [PATCH 75/79] destructive: change "do" signature ... to take lambda(s), not any value. --- dsl/ops/func_do.go | 6 ++++-- dsl/types/lambda.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 dsl/types/lambda.go diff --git a/dsl/ops/func_do.go b/dsl/ops/func_do.go index dd9735ca..e14c96c6 100644 --- a/dsl/ops/func_do.go +++ b/dsl/ops/func_do.go @@ -19,9 +19,10 @@ type doOp struct { var DoOp = &doOp{funcBase("do")} func (*doOp) Bind(args ...types.Value) (types.Expr, error) { + thunkType := types.NewLambdaType(types.AnyValue) sig := make([]types.Type, 0, len(args)) for i := 0; i < len(args); i++ { - sig = append(sig, types.AnyValue) + sig = append(sig, thunkType) } if err := util.Signature(sig...).Check(args); err != nil { return nil, err @@ -49,8 +50,9 @@ func (*doOp) EvalExpr(ctx context.Context, args []types.Value) (_ types.Value, _ }() var lastVal types.Value + empty := make([]types.Value, 0) for i := range args { - v, rbFunc, err := args[i].Eval(ctx) + v, rbFunc, err := args[i].(types.Op).EvalExpr(ctx, empty) g.Add(rbFunc) if err != nil { result = g.Error(err) diff --git a/dsl/types/lambda.go b/dsl/types/lambda.go new file mode 100644 index 00000000..827619d7 --- /dev/null +++ b/dsl/types/lambda.go @@ -0,0 +1,28 @@ +package types + +import "context" + +// Lambda can be applicable, and it has an expression to execute. +type Lambda Value + +// NewLambda creates lambda value. +// Signature must have 1 type at least for a return type. +func NewLambda(t Type, rest ...Type) Lambda { + return &lambdaT{typ: NewLambdaType(t, rest...)} +} + +type lambdaT struct { + typ Type +} + +func (v *lambdaT) Invert(context.Context) (Value, error) { + return v, nil +} + +func (v *lambdaT) Eval(context.Context) (Value, func(), error) { + return v, nil, nil +} + +func (v *lambdaT) Type() Type { + return v.typ +} From 31449a26bf7cc6a0993371a1ea8a9054e229e1a6 Mon Sep 17 00:00:00 2001 From: tyru Date: Mon, 23 Apr 2018 19:07:15 +0900 Subject: [PATCH 76/79] fix: context is not passed to rollback function --- dsl/execute.go | 4 ++-- dsl/ops/func_do.go | 2 +- dsl/ops/func_lockjson_write.go | 2 +- dsl/ops/func_migrate_plugconf_config_func.go | 2 +- dsl/ops/macro.go | 2 +- dsl/ops/macro_array.go | 2 +- dsl/ops/macro_eval.go | 2 +- dsl/ops/macro_invert.go | 2 +- dsl/ops/util/guard.go | 14 ++++++++------ dsl/types/expr.go | 2 +- dsl/types/json.go | 12 ++++++------ dsl/types/lambda.go | 2 +- dsl/types/op.go | 2 +- dsl/types/value.go | 2 +- 14 files changed, 27 insertions(+), 25 deletions(-) diff --git a/dsl/execute.go b/dsl/execute.go index 14914c58..118896bb 100644 --- a/dsl/execute.go +++ b/dsl/execute.go @@ -50,14 +50,14 @@ func Execute(ctx context.Context, expr types.Expr) (_ types.Value, result error) val, rollback, err := evalDepthFirst(ctx, expr) if err != nil { if rollback != nil { - rollback() + rollback(ctx) } return nil, errors.Wrap(err, "expression returned an error") } return val, nil } -func evalDepthFirst(ctx context.Context, expr types.Expr) (_ types.Value, _ func(), result error) { +func evalDepthFirst(ctx context.Context, expr types.Expr) (_ types.Value, _ func(context.Context), result error) { op := expr.Op() g := util.FuncGuard(op.String()) defer func() { diff --git a/dsl/ops/func_do.go b/dsl/ops/func_do.go index e14c96c6..435dcc26 100644 --- a/dsl/ops/func_do.go +++ b/dsl/ops/func_do.go @@ -43,7 +43,7 @@ func (*doOp) InvertExpr(ctx context.Context, args []types.Value) (types.Value, e return DoOp.Bind(newargs...) } -func (*doOp) EvalExpr(ctx context.Context, args []types.Value) (_ types.Value, _ func(), result error) { +func (*doOp) EvalExpr(ctx context.Context, args []types.Value) (_ types.Value, _ func(context.Context), result error) { g := util.FuncGuard(DoOp.String()) defer func() { result = g.Error(recover()) diff --git a/dsl/ops/func_lockjson_write.go b/dsl/ops/func_lockjson_write.go index 62226027..25bc097d 100644 --- a/dsl/ops/func_lockjson_write.go +++ b/dsl/ops/func_lockjson_write.go @@ -33,7 +33,7 @@ func (*lockJSONWriteOp) InvertExpr(_ context.Context, args []types.Value) (types return LockJSONWriteOp.Bind(args...) } -func (*lockJSONWriteOp) EvalExpr(ctx context.Context, args []types.Value) (_ types.Value, _ func(), result error) { +func (*lockJSONWriteOp) EvalExpr(ctx context.Context, args []types.Value) (_ types.Value, _ func(context.Context), result error) { lockJSON := ctx.Value(dslctx.LockJSONKey).(*lockjson.LockJSON) result = lockJSON.Write() if result != nil { diff --git a/dsl/ops/func_migrate_plugconf_config_func.go b/dsl/ops/func_migrate_plugconf_config_func.go index eb707ad3..f0d8d59b 100644 --- a/dsl/ops/func_migrate_plugconf_config_func.go +++ b/dsl/ops/func_migrate_plugconf_config_func.go @@ -44,7 +44,7 @@ func (*migratePlugconfConfigFuncOp) InvertExpr(_ context.Context, args []types.V return MigratePlugconfConfigFuncOp.Bind(args...) } -func (*migratePlugconfConfigFuncOp) EvalExpr(ctx context.Context, args []types.Value) (_ types.Value, _ func(), result error) { +func (*migratePlugconfConfigFuncOp) EvalExpr(ctx context.Context, args []types.Value) (_ types.Value, _ func(context.Context), result error) { lockJSON := ctx.Value(dslctx.LockJSONKey).(*lockjson.LockJSON) cfg := ctx.Value(dslctx.ConfigKey).(*config.Config) diff --git a/dsl/ops/macro.go b/dsl/ops/macro.go index c27ec44d..07421bd1 100644 --- a/dsl/ops/macro.go +++ b/dsl/ops/macro.go @@ -17,7 +17,7 @@ func (*macroBase) IsMacro() bool { } // macroInvertExpr inverts the result of op.Execute() which expands an expression -func (*macroBase) macroInvertExpr(ctx context.Context, val types.Value, _ func(), err error) (types.Value, error) { +func (*macroBase) macroInvertExpr(ctx context.Context, val types.Value, _ func(context.Context), err error) (types.Value, error) { if err != nil { return nil, err } diff --git a/dsl/ops/macro_array.go b/dsl/ops/macro_array.go index e2808433..1c96b65a 100644 --- a/dsl/ops/macro_array.go +++ b/dsl/ops/macro_array.go @@ -27,6 +27,6 @@ func (*arrayOp) Bind(args ...types.Value) (types.Expr, error) { return expr, nil } -func (*arrayOp) EvalExpr(ctx context.Context, args []types.Value) (types.Value, func(), error) { +func (*arrayOp) EvalExpr(ctx context.Context, args []types.Value) (types.Value, func(context.Context), error) { return types.NewArray(args, types.AnyValue), nil, nil } diff --git a/dsl/ops/macro_eval.go b/dsl/ops/macro_eval.go index 4716aa5f..9c22f7a9 100644 --- a/dsl/ops/macro_eval.go +++ b/dsl/ops/macro_eval.go @@ -28,7 +28,7 @@ func (*evalOp) Bind(args ...types.Value) (types.Expr, error) { return expr, nil } -func (*evalOp) EvalExpr(ctx context.Context, args []types.Value) (types.Value, func(), error) { +func (*evalOp) EvalExpr(ctx context.Context, args []types.Value) (types.Value, func(context.Context), error) { if err := util.Signature(types.AnyValue).Check(args); err != nil { return nil, nil, err } diff --git a/dsl/ops/macro_invert.go b/dsl/ops/macro_invert.go index 123e806d..c40a7248 100644 --- a/dsl/ops/macro_invert.go +++ b/dsl/ops/macro_invert.go @@ -28,7 +28,7 @@ func (*invertOp) Bind(args ...types.Value) (types.Expr, error) { return expr, nil } -func (*invertOp) EvalExpr(ctx context.Context, args []types.Value) (types.Value, func(), error) { +func (*invertOp) EvalExpr(ctx context.Context, args []types.Value) (types.Value, func(context.Context), error) { if err := util.Signature(types.AnyValue).Check(args); err != nil { return nil, nil, err } diff --git a/dsl/ops/util/guard.go b/dsl/ops/util/guard.go index 46aec1a4..edc17587 100644 --- a/dsl/ops/util/guard.go +++ b/dsl/ops/util/guard.go @@ -1,7 +1,9 @@ package util import ( + "context" "fmt" + "github.com/pkg/errors" ) @@ -24,10 +26,10 @@ type Guard interface { Error(v interface{}) error // Rollback calls rollback functions in reversed order - Rollback() + Rollback(ctx context.Context) // Add adds given rollback functions, but skips if f == nil - Add(f func()) + Add(f func(context.Context)) } // FuncGuard returns Guard instance for function @@ -38,7 +40,7 @@ func FuncGuard(name string) Guard { type guard struct { errMsg string err error - rbFuncs []func() + rbFuncs []func(context.Context) } func (g *guard) Error(v interface{}) error { @@ -50,14 +52,14 @@ func (g *guard) Error(v interface{}) error { return g.err } -func (g *guard) Rollback() { +func (g *guard) Rollback(ctx context.Context) { for i := len(g.rbFuncs) - 1; i >= 0; i-- { - g.rbFuncs[i]() + g.rbFuncs[i](ctx) } g.rbFuncs = nil // do not rollback twice } -func (g *guard) Add(f func()) { +func (g *guard) Add(f func(context.Context)) { if f != nil { g.rbFuncs = append(g.rbFuncs, f) } diff --git a/dsl/types/expr.go b/dsl/types/expr.go index da440b2c..10845b15 100644 --- a/dsl/types/expr.go +++ b/dsl/types/expr.go @@ -39,7 +39,7 @@ func (expr *expr) RetType() Type { return expr.retType } -func (expr *expr) Eval(ctx context.Context) (val Value, rollback func(), err error) { +func (expr *expr) Eval(ctx context.Context) (val Value, rollback func(context.Context), err error) { return expr.op.EvalExpr(ctx, expr.args) } diff --git a/dsl/types/json.go b/dsl/types/json.go index 188e6f06..e8fde755 100644 --- a/dsl/types/json.go +++ b/dsl/types/json.go @@ -13,7 +13,7 @@ func (*nullT) Invert(context.Context) (Value, error) { return NullValue, nil } -func (v *nullT) Eval(context.Context) (val Value, rollback func(), err error) { +func (v *nullT) Eval(context.Context) (val Value, rollback func(context.Context), err error) { return v, nil, nil } @@ -57,7 +57,7 @@ func (v *boolT) Invert(context.Context) (Value, error) { return v, nil } -func (v *boolT) Eval(context.Context) (val Value, rollback func(), err error) { +func (v *boolT) Eval(context.Context) (val Value, rollback func(context.Context), err error) { return v, nil, nil } @@ -92,7 +92,7 @@ func (v *numberT) Invert(context.Context) (Value, error) { return v, nil } -func (v *numberT) Eval(context.Context) (val Value, rollback func(), err error) { +func (v *numberT) Eval(context.Context) (val Value, rollback func(context.Context), err error) { return v, nil, nil } @@ -127,7 +127,7 @@ func (v *stringT) Invert(context.Context) (Value, error) { return v, nil } -func (v *stringT) Eval(context.Context) (val Value, rollback func(), err error) { +func (v *stringT) Eval(context.Context) (val Value, rollback func(context.Context), err error) { return v, nil, nil } @@ -165,7 +165,7 @@ func (v *arrayT) Invert(context.Context) (Value, error) { return v, nil } -func (v *arrayT) Eval(context.Context) (val Value, rollback func(), err error) { +func (v *arrayT) Eval(context.Context) (val Value, rollback func(context.Context), err error) { return v, nil, nil } @@ -203,7 +203,7 @@ func (v *objectT) Invert(context.Context) (Value, error) { return v, nil } -func (v *objectT) Eval(context.Context) (val Value, rollback func(), err error) { +func (v *objectT) Eval(context.Context) (val Value, rollback func(context.Context), err error) { return v, nil, nil } diff --git a/dsl/types/lambda.go b/dsl/types/lambda.go index 827619d7..69f5984f 100644 --- a/dsl/types/lambda.go +++ b/dsl/types/lambda.go @@ -19,7 +19,7 @@ func (v *lambdaT) Invert(context.Context) (Value, error) { return v, nil } -func (v *lambdaT) Eval(context.Context) (Value, func(), error) { +func (v *lambdaT) Eval(context.Context) (Value, func(context.Context), error) { return v, nil, nil } diff --git a/dsl/types/op.go b/dsl/types/op.go index ccfd1d8f..565ed96a 100644 --- a/dsl/types/op.go +++ b/dsl/types/op.go @@ -17,7 +17,7 @@ type Op interface { // If this operator is a function, it executes the operation and returns its // result and error. // If this operator is a macro, this expands expression. - EvalExpr(ctx context.Context, args []Value) (ret Value, rollback func(), err error) + EvalExpr(ctx context.Context, args []Value) (ret Value, rollback func(context.Context), err error) // IsMacro returns true if this operator is a macro IsMacro() bool diff --git a/dsl/types/value.go b/dsl/types/value.go index 0439b09a..0f716b85 100644 --- a/dsl/types/value.go +++ b/dsl/types/value.go @@ -12,7 +12,7 @@ type Value interface { // Eval returns a evaluated value. // Literals like string,number,... return itself as-is. - Eval(ctx context.Context) (val Value, rollback func(), err error) + Eval(ctx context.Context) (val Value, rollback func(context.Context), err error) // Type returns the type of this value. Type() Type From b832a41ac370b2ffb4ea88375c3912121ec3fdd5 Mon Sep 17 00:00:00 2001 From: tyru Date: Mon, 23 Apr 2018 19:39:47 +0900 Subject: [PATCH 77/79] refactor: mv dsl/deparse.go dsl/deparse/deparse.go --- dsl/{ => deparse}/deparse.go | 2 +- dsl/execute.go | 3 ++- dsl/{ => parse}/parse.go | 0 3 files changed, 3 insertions(+), 2 deletions(-) rename dsl/{ => deparse}/deparse.go (98%) rename dsl/{ => parse}/parse.go (100%) diff --git a/dsl/deparse.go b/dsl/deparse/deparse.go similarity index 98% rename from dsl/deparse.go rename to dsl/deparse/deparse.go index 03b2fb55..0c50dfa2 100644 --- a/dsl/deparse.go +++ b/dsl/deparse/deparse.go @@ -1,4 +1,4 @@ -package dsl +package deparse import ( "github.com/pkg/errors" diff --git a/dsl/execute.go b/dsl/execute.go index 118896bb..3e5137c5 100644 --- a/dsl/execute.go +++ b/dsl/execute.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "github.com/vim-volt/volt/config" + "github.com/vim-volt/volt/dsl/deparse" "github.com/vim-volt/volt/dsl/dslctx" "github.com/vim-volt/volt/dsl/ops/util" "github.com/vim-volt/volt/dsl/types" @@ -120,7 +121,7 @@ func doExpandMacro(expr types.Expr) (types.Value, error) { } func writeTrxLog(ctx context.Context, expr types.Expr) (result error) { - deparsed, err := Deparse(expr) + deparsed, err := deparse.Deparse(expr) if err != nil { return errors.Wrap(err, "failed to deparse expression") } diff --git a/dsl/parse.go b/dsl/parse/parse.go similarity index 100% rename from dsl/parse.go rename to dsl/parse/parse.go From 397d5cf4b963776b1eac0405112d2b2a8c562731 Mon Sep 17 00:00:00 2001 From: tyru Date: Wed, 25 Apr 2018 00:10:12 +0900 Subject: [PATCH 78/79] doc: change "fn" syntax --- _docs/json-dsl.md | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/_docs/json-dsl.md b/_docs/json-dsl.md index a47f8d30..e5c62b94 100644 --- a/_docs/json-dsl.md +++ b/_docs/json-dsl.md @@ -122,10 +122,14 @@ Parameter types are * expression (only macro can treat this) But all values must be able to be serialized to JSON. Because AST of whole -process is serialized and saved as a "transaction log file". The process can be -rolled back, when an error occur while the process, or user send SIGINT signal, -or `volt history undo` command is executed. The transaction log file does not -have ID but the saved directory `{id}` does: +process is serialized and saved as a "transaction log file". + +NOTE: All macros has `$` prefixed name for readability. +Macros are not saved in transaction log (expanded before saving). + +The process can be rolled back, when an error occur while the process, or user +send SIGINT signal, or `volt history undo` command is executed. The transaction +log file does not have ID but the saved directory `{id}` does: ``` $VOLTPATH/trx/{id}/log.json @@ -367,10 +371,7 @@ As the above signature shows, operators must take care the following points: TODO: Move to Godoc. -### Basic macros - -All macros has `$` prefixed name for readability. -Macros are not saved in transaction log (expanded before saving). +### Basic operators * `["$array", v1 Value, ...] Array` * Returns inverse expression of given expression. @@ -390,20 +391,31 @@ Macros are not saved in transaction log (expanded before saving). instead of its expression. * See `repos/git/fetch`, `repos/git/update` for concrete example. -### Basic operators +* `["fn", [args ...[]string], body Expr[R]]R` + * Returns a lambda with `arity` number arguments and `body` expression. + * e.g. + * `["fn", [], ["lockjson/write"]]` + * see below + +```json +[["fn", [["path", "string"], ["profiles", ["array", "string"]]], + ["lockjson/add", ["repos/get", ["arg", "path"]], ["arg", "profiles"]]], + "github.com/tyru/caw.vim", + ["$array", "default"]] +``` -* `["$label", linenum: number, tmpl string, expr Expr[* => R]] R` +* `["$label", linenum number, tmpl string, expr Expr[R]] R` * `["$label", linenum, tmpl, expr]` expands to `["label", linenum, tmpl, ["fn", [] expr]]` -* `["label", linenum: number, tmpl string, thunk Func[* => R]] R` +* `["label", linenum number, tmpl string, thunk Func[R]] R` * Render `tmpl` by text/template to `linenum` line (1-origin). Returns the evaluated value of `thunk`. * e.g. * `["$invert", ["label", linenum, "msg", thunk]]` = `["label", ["$invert", linenum], "revert: \"msg\"", ["$invert", thunk]]` * See `Label examples` section for more details -* `["$do", expr1 Expr[* => R1], ..., expr_last Expr[* => R2]] R2` +* `["$do", expr1 Expr[R1], ..., expr_last Expr[R2]] R2` * `["$do", expr1, expr2]` expands to `["do", ["fn", [], expr1], ["fn", [], expr2]]` @@ -414,11 +426,11 @@ Macros are not saved in transaction log (expanded before saving). * `["$invert", ["do", thunk1, thunk2]]` = `["do", ["$invert", thunk1], ["$invert", thunk2]]` * Note that the arguments are reversed. -* `["$parallel", expr1 Expr[* => R1], ..., expr_last Expr[* => R2]] R2` +* `["$parallel", expr1 Expr[R1], ..., expr_last Expr[R2]] R2` * `["$parallel", expr1, expr2]` expands to `["parallel", ["fn", [], expr1], ["fn", [], expr2]]` -* `["parallel", thunk1 Func[* => R1], ..., thunk_last Func[* => R2]] R2` +* `["parallel", thunk1 Func[R1], ..., thunk_last Func[R2]] R2` * Executes multiple lambdas in parallel. * Returns the evaluated value of the last lambda. * e.g. @@ -549,7 +561,7 @@ Macros are not saved in transaction log (expanded before saving). ### Vim directory operators -* `["vimdir/with-install", paths "all" | []ReposPath, expr Expr[* => R]] R` +* `["vimdir/with-install", paths "all" | []ReposPath, expr Expr[R]] R` * `paths` is the list of repositories to build after `expr` is executed. * `"all"` means all repositories of current profile. * e.g. From 07f6cf2d2f5189f3c75a73e8995cd2d03728ba1a Mon Sep 17 00:00:00 2001 From: tyru Date: Wed, 25 Apr 2018 00:17:46 +0900 Subject: [PATCH 79/79] feat: add lambda --- dsl/ops/func_do.go | 3 +- dsl/types/lambda.go | 74 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/dsl/ops/func_do.go b/dsl/ops/func_do.go index 435dcc26..a33a0c39 100644 --- a/dsl/ops/func_do.go +++ b/dsl/ops/func_do.go @@ -50,9 +50,8 @@ func (*doOp) EvalExpr(ctx context.Context, args []types.Value) (_ types.Value, _ }() var lastVal types.Value - empty := make([]types.Value, 0) for i := range args { - v, rbFunc, err := args[i].(types.Op).EvalExpr(ctx, empty) + v, rbFunc, err := args[i].(types.Lambda).Call(ctx) g.Add(rbFunc) if err != nil { result = g.Error(err) diff --git a/dsl/types/lambda.go b/dsl/types/lambda.go index 69f5984f..35c5e469 100644 --- a/dsl/types/lambda.go +++ b/dsl/types/lambda.go @@ -1,18 +1,82 @@ package types -import "context" +import ( + "context" + + "github.com/pkg/errors" +) // Lambda can be applicable, and it has an expression to execute. -type Lambda Value +type Lambda interface { + Value + + // Call calls this lambda with given args + Call(ctx context.Context, args ...Value) (Value, func(context.Context), error) +} + +type argT Array + +// ArgsDef is passed to builder function, the argument of NewLambda() +type ArgsDef struct { + args []argT +} + +// Define returns placeholder expression of given argument +func (def *ArgsDef) Define(n int, name String, typ Type) (Value, error) { + if n <= 0 { + return nil, errors.New("the number of argument must be positive") + } + for n > len(def.args) { + def.args = append(def.args, nil) + } + if def.args[n-1] != nil { + return nil, errors.Errorf("the %dth argument is already taken", n) + } + argExpr := []Value{NewString("arg"), name} + def.args[n-1] = NewArray(argExpr, AnyValue) + return def.args[n-1], nil +} + +// Inject replaces expr of ["arg", expr] with given values +func (def *ArgsDef) Inject(args []Value) error { + if len(args) != len(def.args) { + return errors.Errorf("expected %d arity but got %d", len(def.args), len(args)) + } + for i := range args { + if def.args[i] == nil { + return errors.Errorf("%dth arg is not taken", i+1) + } + def.args[i].Value()[1] = args[i] + } + return nil +} // NewLambda creates lambda value. // Signature must have 1 type at least for a return type. -func NewLambda(t Type, rest ...Type) Lambda { - return &lambdaT{typ: NewLambdaType(t, rest...)} +func NewLambda(builder func(*ArgsDef) (Expr, []Type, error)) (Lambda, error) { + def := &ArgsDef{args: make([]argT, 0)} + expr, sig, err := builder(def) + if err != nil { + return nil, errors.Wrap(err, "builder function returned an error") + } + return &lambdaT{ + def: def, + expr: expr, + typ: NewLambdaType(sig[0], sig[1:]...), + }, nil } type lambdaT struct { - typ Type + def *ArgsDef + expr Expr + typ Type +} + +func (v *lambdaT) Call(ctx context.Context, args ...Value) (Value, func(context.Context), error) { + if err := v.def.Inject(args); err != nil { + return nil, nil, err + } + return v.expr.Eval(ctx) } func (v *lambdaT) Invert(context.Context) (Value, error) {