Skip to content

Commit

Permalink
Merge pull request imdrasil#8 from imdrasil/enhance_dsl
Browse files Browse the repository at this point in the history
Enhance DSL
  • Loading branch information
imdrasil authored Aug 25, 2019
2 parents bf03da4 950d189 commit 4258f9c
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 192 deletions.
208 changes: 107 additions & 101 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 1,6 @@
# Sam [![Build Status](https://travis-ci.org/imdrasil/sam.cr.svg)](https://travis-ci.org/imdrasil/sam.cr) [![Latest Release](https://img.shields.io/github/release/imdrasil/sam.cr.svg)](https://github.com/imdrasil/sam.cr/releases)

Sam is a Make-like utility which allows to specify tasks like Ruby's Rake do using plain Crystal.
Sam is a Make-like utility which allows to specify tasks like Ruby's Rake do using plain Crystal code. This allows you to reuse existing application code base and/or include tasks from your dependencies.

## Installation

Expand All @@ -12,171 12,177 @@ dependencies:
github: imdrasil/sam.cr
```
## Usage
### Simple example
After executing `shards install` Sam-file will be added to the root of your project (unless you already have one).

Create `sam.cr` file in your app root directory and paste next:
## Usage

```crystal
# here you should load your app configuration if
# it will be needed to perform tasks
Sam.namespace "db" do
namespace "schema" do
desc "Outputs smth: requires 2 named arguments"
task "load" do |t, args|
puts args["f1"]
t.invoke("1")
t.invoke("schema:1")
t.invoke("db:migrate")
t.invoke("db:db:migrate")
t.invoke("db:ping")
t.invoke("din:dong")
puts "------"
t.invoke("2", {"f2" => 1})
end
### Task

task "1" do
puts "1"
end
Tasks are the main unit in `sam.cr`. Task has a name, a list of prerequisites and a list of actions (block of a code).

task "2", ["1", "db:migrate"] do |t, args|
puts args.named["f2"].as(Int32) 3
end
end
Sam extends the global context with own DSL. To define a task use `task` method which accepts the task name as the 1st argument.

namespace "db" do
task "schema" do
puts "same as namespace"
end
```crystal
task "name" do
end
```

task "migrate" do
puts "migrate"
end
end
If you want to define prerequisites, add the array with their names as the 2nd argument:

task "ping" do
puts "ping"
end
```crystal
task "name", ["prereq1", "prereq2"] do
end
Sam.help
```

To ran any of this task open prompt in root location and paste:
#### Executing a task

Sam does no magic with your `sam.cr` file - it is just a common `.cr` source file which allows you to recompile it with any possible code you want such amount of times you need. Therefore the most obvious way to execute any task is:

```shell
$ crystal sam.cr -- <your_task_path> [options]
$ crystal sam.cr -- name
```

To get list of all available tasks:
> `--` here means that `name` is passed as an argument to executed file not `crystal` utility.

In addition to this you are able to configure your makefile to invoke sam tasks. This allows you to use shorten variant

```shell
$ crystal sam.cr -- help
$ make sam name
```

Each tasks has own "path" which consists of namespace names and task name joined together by ":".
> This solution still requires `--` in some cases - see the following section.

Also tasks can accept space separated arguments from prompt. To pass named argument (which have associated name) use next rules:

- `-name value`
- `-name "value with spaces"`
- `name=value`
- `name="value with spaces"`

Also just array of arguments can be passed - just past everything needed without any flags anywhere:
To automatically preconfigure makefile run

```shell
$ crystal sam.cr -- <your_task_path> first_raw_option "options with spaces"
$ crystal sam.cr -- generate:makefile
```

All arguments from prompt will be realized as `String`.
This will modify existing Makefile or create new one. Be careful - this will silent all nonexisting makefile tasks on invocation.

To invoke a task, e.g. the first task "load" from example above:
To see a list of all available tasks with their descriptions:

```shell
crystal sam.cr -- db:schema:load -f1 asd
$ crystal sam.cr -- help
```

Makefile-like usage is supported. To autogenerate receipt just call
#### Tasks with arguments

To pass arguments to your task just list them after it's name:

```shell
$ crystal sam.cr -- generate:makefile
> crystal sam.cr -- name john rob ned
```

This will modify existing Makefile or creates new one. Be careful - this will silent all nonexisting tasks. For more details take a look on template in code. This will allow to call tasks in the next way:
They are passed to a task as a 2nd block argument.

```shell
$ make sam some:task raw_arg1
```crystal
task "name" do |_, args|
puts args[0].as(String)
end
```

But for named argument you need to add `--`
`args` here is an instance of `Sam::Args` class that contains arguments and named arguments passed to each task. Any argument passed from a console is treated as a `String` but `Int32` and `Float64` values also can be specified during task invocation from inside of another one.

```shell
$ make sam db:schema:load -- -f1 asd
```
> Each task has own collection of arguments; only prerequisites shares with target task same `Args` instance.

By default it will try to use your samfile in the app root. To override it pass proper way as second argument
As was mentioned named argument also can be specified by the following ways:

- `-argument value`
- `-argument "value with spaces"`
- `argument=value`
- `argument="value with spaces"`

One important restriction with named arguments usage and makefile-style task invocation: `--` should be placed to explicitly specify that specified arguments belongs to compiled program not crystal compiler:

```shell
$ crystal src/sam.cr -- generate:makefile "src/sam.cr"
$ make sam name john
$ # but
$ make same name -- argument=john
```

To autoload Sam files from your dependencies - just past
More than one task can be specified (even with own arguments) - just separate them by `@` symbol:

```crystal
load_dependencies "dep1", "dep2"`
```shell
$ crystal sam.cr -- name john @ surname argument=snow
```

If library provides some optional files with tasks they could be loaded as well using named tuple literal:
#### Accessing tasks programmatically

Sam allow you to invoke tasks within another ones and even passing own args object. To do this just call `#invoke` method with task name (and arguments if needed) on task object passed as 1st argument:

```crystal
load_dependencies "lib1", "lib2": "special_file", "lib3": ["special_file"], "lib3": ["/root_special_file"]
task "name" do |t|
t.invoke("surname")
end
task "surname" do
puts "Snow"
end
```

By default any nested dependency will be loaded from "tasks" folder at the lib root level. Any dependency with leading "/" makes to load them using given path. So `root_special_file` for `lib3` will be loaded with `lib3/src/lib3/root_special_file.cr`.
If specified task was already invoked before - it will be ignored. To force task invocation - use `#execute`.

To execute multiple tasks at once just list them separated by `@` character:

```shell
$ crystal sam.cr -- namespace1:task1 arg1=2 @ other_task arg1=3
```
Another task could be invoked from current using `invoke` method. It has next signatures:

### Namespaces

Each task will be executed only if the previous one is successfully finished (without throwing any exception).
as projects grow amount of defined tasks grow as well. To simplify navigation and increase readability tasks can be grouped in namespaces:

#### Namespace
```crystal
namespace "main" do
task "build" do
# Build the main program
end
end
To define namespace (for now they only used for namespacing tasks) use `Sam.namespace` (opens `root` namespace) or just `namespace` inside of it. `Sam.namespace` can be called any times - everything will be added to existing staff.
namespace "samples" do
task "build" do
# Build the sample programs
end
end
#### Task
task "build", %w[main:build samples:build] do
end
```

To define task use `task` method with it's name and block. Given block could take 0..2 arguments: `Task` object and `Args` object. Also as second parameter could be provided array of dependent tasks which will be invoked before current.
#### Name resolution

Another task could be invoked from current using `invoke` method. It has next signatures:
When task is invoked from other one, provided path will float up through current task namespace and search given task path on each level until top level. Task could have same name as any existing namespace.

-
- `name : String` - task path
```crystal
task "one" do
end
-
- `name : String` - task path
- `args : Args` - prepared argument object
namespace "one" do
namespace "two"
task "test" do |t|
t.invoke("one")
end
end
end
```

-
- `name : String` - task path
- `hash : Hash(String, String | Int32, Float32)` - hash with arguments
In the example above next paths are checked (in given order):

-
- `name : String` - task path
- `args : Tuple` - raw arguments
* `one:two:one`
* `one:one`
* `one` (as task not namespace)

Any already invoked task is ignored during further invocations. To avoid this `#execute` method could be used.
### Share tasks

#### Routing
Sam tasks can be loaded from installed dependencies. To do this helper macro `load_dependencies` can be used:

When task is invoked from other one provided path will float up through current task namespace nesting and search given path on each level. Task could have same name as any existing namespace.
```crystal
load_dependencies "lib1", "lib2"
```

#### Args
This is translated to

This class represents argument set for task. It can handle named arguments and just raw array of arguments. Now it supports only `String`, `Int32` and `Float64` types. To get access to named argument you can use `[](name : String)` and `[]?(name : String)` methods. For raw attributes there are `[](index : Int32)` and `[]?(index : Int32)` as well.
```crystal
require "./lib/lib1/tasks/sam.cr"
```

## Development

Expand Down
9 changes: 9 additions & 0 deletions examples/sam.template
Original file line number Diff line number Diff line change
@@ -0,0 1,9 @@
require "sam"

# Here you can define your tasks
# desc "with description to be used by help command"
# task "test" do
# puts "ping"
# end

Sam.help
7 changes: 5 additions & 2 deletions shard.yml
Original file line number Diff line number Diff line change
@@ -1,9 1,12 @@
name: sam
version: 0.3.0
version: 0.3.1

authors:
- Roman Kalnytskyi <[email protected]>

crystal: 0.26.0
crystal: 0.30.1

license: MIT

scripts:
postinstall: "false | cp -i examples/sam.template ../../sam.cr 2>/dev/null"
2 changes: 1 addition & 1 deletion spec/spec_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 28,7 @@ end

# Tasks

Sam.namespace "db" do
namespace "db" do
namespace "schema" do
task "load" do |t, args|
puts args["f1"]
Expand Down
Loading

0 comments on commit 4258f9c

Please sign in to comment.