Skip to content

Commit

Permalink
Abstract "task" API: spell incantation, kind and caster
Browse files Browse the repository at this point in the history
The "wand" API is inspired by the fantasy novel "Harry Potter" [1] and
uses an abstract view to define interfaces. The main motivation to
create a matching naming to the overall "magic" topic and the actual
target project Mage [2]. This might be too abstract for some, but is
kept understandable insofar as it should allow everyone to use the
"task" API and to derive their own tasks from it.

- <I> `cast.Caster` - A `interface` type that casts a
  `spell.Incantation` using a command for a specific `spell.Kind`:
  - `Cast(spell.Incantation) error` - casts a spell incantation.
  - `Handles() spell.Kind` - returns the spell kind that can be casted.
  - `Validate() error` - validates the caster command.
- <I> `cast.BinaryCaster` - A `interface` type that composes
  `cast.Caster` to run commands using a binary executable:
  - `GetExec() string` - returns the path to the binary executable of
    the command.
- <I> `spell.Incantation` - A `interface` type that is the abstract
  representation of parameters for a command or action:
  - `Formula() []string` - returns all parameters of a spell.
  - `Kind() Kind` - returns the Kind of a spell.
  - `Options() interface{}` - return the options of a spell.
- <I> `cast.Binary` - A `interface` type that composes `cast.Caster` for
  commands which are using a binary executable:
  - `Env() map[string]string` - returns additional environment
    variables.
- <I> `cast.GoCode` - A `interface` type that composes `cast.Caster` for
  actions that can be casted without a `cast.Caster`:
  - `Cast() (interface{}, error)` - casts itself
- <I> `cast.GoModule` - A `interface` type that composes `cast.Binary`
  for commands that are compiled from a [Go module][3]:
  - `GoModuleID() *project.GoModuleID` - returns the identifier of a Go
    module.
- <T> `spell.Kind` - A `struct` type that defines the kind of a spell.

The API components can be roughly translated to their purpose:

- `cast.Caster` -> a executable command
  It validates the command and defines which `spell.Kind` can be handled
  by this caster. It could be executed without parameters
  (`spell.Incantation`), but in most cases needs at least one parameter.
  - `cast.BinaryCaster` -> a composed `cast.Caster` to run commands
    using a binary executable.
    It ensures that the executable file exists and stores information
    like the path. It could also be executed without parameters
    (`spell.Incantation`), but would not have any effect im many cases.
- `spell.Incantation` -> the parameters of a executable command
  It assemble all parameters based on the given options and ensures the
  they are correctly formatted for the execution in a shell environment.
  Except for special incantations like `spell.GoCode` a incantation
  cannot be used alone but must be passed to a `cast.Caster` that is
  able to handle the `spell.Kind` of this incantation.
  - `spell.Binary` -> a composed `spell.Incantation` to run commands
    that are using binary executable.
    It can inject or override environment variables in the shell
    environment in which the the command will be run.
  - `spell.GoCode` -> a composed `spell.Incantation` for pure Go code
    instead of a (binary) executable command.
    It can “cast itself“, e.g. to simply delete a directory using
    packages like `os` from the Go standard library. It has been
    designed this way to also allow such tasks to be handled by the
    incantation API.
  - `spell.GoModule` -> a composed `spell.Binary` to run binary commands
    managed by a [Go module][3], in other words executables installed in
    `GOBIN` or received via `go get`.
    It requires the module identifier (`path@version`) in order to
    download and run the executable.

[1]: https://en.wikipedia.org/wiki/Harry_Potter
[2]: https://magefile.org
[3]: https://golang.org/ref/mod

GH-14
  • Loading branch information
svengreb committed Nov 17, 2020
1 parent 5d41725 commit fbd016c
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 0 deletions.
36 changes: 36 additions & 0 deletions pkg/cast/cast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

package cast

import (
"github.com/svengreb/wand/pkg/spell"
)

// Caster casts a spell.Incantation using a command for a specific spell.Kind.
//
// The abstract view and naming is inspired by the fantasy novel "Harry Potter" in which a caster can cast a magic spell
// through a incantation.
//
// See
//
// (1) https://en.wikipedia.org/wiki/Magic_in_Harry_Potter#Spellcasting
// (2) https://en.wikipedia.org/wiki/Incantation
type Caster interface {
// Cast casts a spell incantation.
Cast(spell.Incantation) error

// Handles returns the spell kind that can be casted.
Handles() spell.Kind

// Validate validates the caster command.
Validate() error
}

// BinaryCaster is a Caster to run commands using a binary executable.
type BinaryCaster interface {
Caster

// GetExec returns the path to the binary executable of the command.
GetExec() string
}
79 changes: 79 additions & 0 deletions pkg/spell/kind.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

package spell

import (
"fmt"
"strings"
)

const (
// KindNameBinary is the Kind name for binary spells.
KindNameBinary = "binary"
// KindNameGoCode is the Kind name for Go code spells.
KindNameGoCode = "go.code"
// KindNameGoModule is the Kind name for Go module spells.
KindNameGoModule = "go.module"
// KindNameUnknown is the name for a unknown spell Kind.
KindNameUnknown = "unknown"
)

const (
// KindBinary is the Kind for binary spells.
KindBinary Kind = iota
// KindGoCode is the Kind for Go code spells.
KindGoCode
// KindGoModule is the Kind for Go module spells.
KindGoModule
)

// Kind defines the kind of a spell.
type Kind uint32

// MarshalText returns the textual representation of itself.
func (k Kind) MarshalText() ([]byte, error) {
switch k {
case KindBinary:
return []byte(KindNameBinary), nil
case KindGoCode:
return []byte(KindNameGoCode), nil
case KindGoModule:
return []byte(KindNameGoModule), nil
}

return nil, fmt.Errorf("not a valid kind %d", k)
}

func (k Kind) String() string {
if b, err := k.MarshalText(); err == nil {
return string(b)
}
return KindNameUnknown
}

// UnmarshalText implements encoding.TextUnmarshaler to unmarshal a textual representation of itself.
func (k *Kind) UnmarshalText(text []byte) error {
parsed, err := ParseKind(string(text))
if err != nil {
return err
}

*k = parsed
return nil
}

// ParseKind takes a kind name and returns the Kind constant.
func ParseKind(name string) (Kind, error) {
switch strings.ToLower(name) {
case KindNameBinary:
return KindBinary, nil
case KindNameGoCode:
return KindGoCode, nil
case KindNameGoModule:
return KindGoModule, nil
}

var k Kind
return k, fmt.Errorf("not a valid kind: %q", name)
}
72 changes: 72 additions & 0 deletions pkg/spell/spell.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

package spell

import "github.com/svengreb/wand/pkg/project"

// Incantation is the abstract representation of parameters for a command or action.
// It is mainly handled by a cast.Caster that provides the corresponding information about the command like the path
// to the executable.
//
// The separation of parameters from commands enables a flexible usage, e.g. when the parameters can be reused for a
// different command.
//
// The abstract view and naming is inspired by the fantasy novel "Harry Potter" in which it is almost only possible to
// cast a magic spell through a incantation.
//
// See
//
// (1) https://en.wikipedia.org/wiki/Incantation
// (2) https://en.wikipedia.org/wiki/Magic_in_Harry_Potter#Spellcasting
// (3) https://scifi.stackexchange.com/a/33234
// (4) https://harrypotter.fandom.com/wiki/Spell
// (5) https://diffsense.com/diff/incantation/spell
type Incantation interface {
// Formula returns all parameters of a spell.
Formula() []string

// Kind returns the Kind of a spell.
Kind() Kind

// Options return the options of a spell.
Options() interface{}
}

// Binary is a Incantation for commands which are using a binary executable.
type Binary interface {
Incantation

// Env returns additional environment variables.
Env() map[string]string
}

// GoCode is a Incantation for actions that can be casted without a cast.Caster.
// It is a special incantations in that it allows to use Go code as spell while still being compatible to the
// incantation API.
// Note that the Incantation.Formula of a GoCode must always return an empty slice,
// otherwise it is a "normal" Incantation that requires a cast.Caster.
//
// Seen from the abstract "Harry Potter" view this is equal to a "non-verbal" spell that is a special technique that can
// be used for spells that have been specially designed to be used non-verbally.
//
// See
//
// (1) https://en.wikipedia.org/wiki/Magic_in_Harry_Potter#Spellcasting
// (2) https://www.reddit.com/r/harrypotter/comments/4z9rwl/what_is_the_difference_between_a_spell_charm
type GoCode interface {
Incantation

// Cast casts itself.
Cast() (interface{}, error)
}

// GoModule is a Binary for binary command executables managed by a Go module.
//
// See https://golang.org/ref/mod for more details.
type GoModule interface {
Binary

// GoModuleID returns the identifier of a Go module.
GoModuleID() *project.GoModuleID
}

0 comments on commit fbd016c

Please sign in to comment.