Skip to content

Commit

Permalink
Add task lazy execution
Browse files Browse the repository at this point in the history
  • Loading branch information
imdrasil committed Aug 14, 2018
1 parent 965ab15 commit 51c16d9
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 43 deletions.
52 changes: 30 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ dependencies:
### Simple example
Create `sam.cr` file in your app root directory and paste next:
Create `sam.cr` file in your app root directory and paste next:

```crystal
# here you should load your app configuration if
# 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: requrie 2 named arguments"
desc "Outputs smth: requires 2 named arguments"
task "load" do |t, args|
puts args["f1"]
t.invoke("1")
Expand All @@ -48,7 +49,7 @@ Sam.namespace "db" do
task "schema" do
puts "same as namespace"
end
task "migrate" do
puts "migrate"
end
Expand All @@ -73,7 +74,7 @@ To get list of all available tasks:
$ crystal sam.cr -- help
```

Each tasks has own "path" which consists of namespace names and task name joined together by ":".
Each tasks has own "path" which consists of namespace names and task name joined together by ":".

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

Expand All @@ -83,13 +84,14 @@ Also tasks can accept space separated arguments from prompt. To pass named argum
- `name="value with spaces"`

Also just array of arguments can be passed - just past everything needed without any flags anywhere:

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

All arguments from prompt will be parsed as `String`.
All arguments from prompt will be realized as `String`.

So to invoke first task from example ("load") will be used next command:
To invoke a task, e.g. the first task "load" from example above:

```shell
crystal sam.cr -- db:schema:load -f1 asd
Expand All @@ -100,7 +102,8 @@ Makefile-like usage is supported. To autogenerate receipt just call
```shell
$ crystal sam.cr -- generate:makefile
```
This will modify existing Makefile or creates new one. Be carefull - this will silent all unexisting tasks. For more details take a look on template in code. This will allow to call tasks in the next way:

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:

```shell
$ make sam some:task raw_arg1
Expand All @@ -109,7 +112,7 @@ $ make sam some:task raw_arg1
But for named argument you need to add `--`

```shell
$ make sam db:shema:load -- -f1 asd
$ make sam db:schema:load -- -f1 asd
```

By default it will try to use your samfile in the app root. To override it pass proper way as second argument
Expand All @@ -118,22 +121,23 @@ By default it will try to use your samfile in the app root. To override it pass
$ crystal src/sam.cr -- generate:makefile "src/sam.cr"
```

To autoload Sam files from your dependencies - just past
To autoload Sam files from your dependencies - just past

```crystal
load_dependencies "dep1", "dep2"`
```

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

```crystal
load_dependencies "lib1", "lib2": "special_file", "lib3": ["special_file"], "lib3": ["/root_special_file"]
```

By default any nested dependency will be loaded from "tasks" folder at the lib root level. Any dependecy 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`.
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`.

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

```crystal
```shell
$ crystal sam.cr -- namespace1:task1 arg1=2 @ other_task arg1=3
```

Expand All @@ -144,36 +148,40 @@ Each task will be executed only if the previous one is successfully finished (wi
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.

#### Task

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.

Another task could be invoked from current using `invoke` method. It has next signatures:

-
- `name : String` - task path
-
- `name : String` - task path

-
-
- `name : String` - task path
- `args : Args` - prepared argument object

-
-
- `name : String` - task path
- `hash : Hash(String, String | Int32, Float32)` - hash with arguments

-
-
- `name : String` - task path
- `args : Tuple` - raw arguments

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

#### Routing

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.

#### Args
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.

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.

## Development

Before running tests call

```shell
$ crystal examples/sam.cr -- setup
```
Expand All @@ -188,4 +196,4 @@ $ crystal examples/sam.cr -- setup

## Contributors

- [imdrasil](https://github.com/[your-github-name]) Roman Kalnytskyi - creator, maintainer
- [imdrasil](https://github.com/imdrasil/sam.cr) Roman Kalnytskyi - creator, maintainer
4 changes: 2 additions & 2 deletions shard.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name: sam
version: 0.2.4
version: 0.3.0

authors:
- Roman Kalnytskyi <moranibaca@gmail.com>

crystal: 0.23.1
crystal: 0.26.0

license: MIT
82 changes: 67 additions & 15 deletions spec/task_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ require "./spec_helper"

describe Sam::Task do
namespace = Sam::Namespace.new("n", nil)
empty_args = Sam::Args.new

describe "#find!" do
it "properly invokes task with same name as parent namespace" do
Expand Down Expand Up @@ -31,25 +32,25 @@ describe Sam::Task do
namespace.task("t1") { arr << 1 }
namespace.task("t2") { arr << 2 }
t = namespace.task("t3", ["t2", "t1"]) { arr << 3 }
t.call(Sam::Args.new)
t.call(empty_args)
arr.should eq([2, 1, 3])
end

it "raises exception and not invokes if dependency raise exception" do
arr = [] of Int32
namespace.task("t2") { 1 / 0 }
t = namespace.task("t3", ["t2", "t1"]) { arr << 3 }
t = namespace.task("t3", ["t2"]) { arr << 3 }
expect_raises(DivisionByZeroError) do
t.call(Sam::Args.new)
t.call(empty_args)
end
arr.empty?.should eq(true)
end
end

context "no arguments" do
it "invokes without arguments" do
it "works without arguments" do
t = namespace.task("t") { }
t.call(Sam::Args.new)
t.call(empty_args)
end
end

Expand All @@ -60,21 +61,21 @@ describe Sam::Task do
count += 1
task.is_a?(Sam::Task).should eq(true)
end
t.call(Sam::Args.new)
t.call(empty_args)
count.should eq(1)
end
end

context "with 2 arguments" do
it "invokes with rask and arguments" do
count = 0
it "invokes with task and command line arguments" do
invoked = false
t = namespace.task("t") do |task, args|
count += 1
invoked = true
task.is_a?(Sam::Task).should eq(true)
args.is_a?(Sam::Args).should eq(true)
end
t.call(Sam::Args.new)
count.should eq(1)
t.call(empty_args)
invoked.should be_true
end
end
end
Expand All @@ -83,21 +84,21 @@ describe Sam::Task do
it "accepts no arguments" do
count = 0
namespace.task("t1") { count += 1 }
namespace.task("t2") { |t| t.invoke("t1") }.call(Sam::Args.new)
namespace.task("t2") { |t| t.invoke("t1") }.call(empty_args)
count.should eq(1)
end

it "accepts tuple at the end" do
count = 0
namespace.task("t1") { |t, args| count += args[0].as(Int32) }
namespace.task("t2") { |t| t.invoke("t1", 1) }.call(Sam::Args.new)
namespace.task("t2") { |t| t.invoke("t1", 1) }.call(empty_args)
count.should eq(1)
end

it "accepts hash" do
count = 0
namespace.task("t1") { |t, args| count += args["count"].as(Int32) }
namespace.task("t2") { |t| t.invoke("t1", {"count" => 2}) }.call(Sam::Args.new)
namespace.task("t2") { |t| t.invoke("t1", {"count" => 2}) }.call(empty_args)
count.should eq(2)
end

Expand All @@ -111,8 +112,59 @@ describe Sam::Task do
it "accepts hash and array" do
count = 0
namespace.task("t1") { |t, args| count += args["count"].as(Int32) + args[0].as(Int32) }
namespace.task("t2") { |t| t.invoke("t1", {"count" => 2}, [1]) }.call(Sam::Args.new)
namespace.task("t2") { |t| t.invoke("t1", {"count" => 2}, [1]) }.call(empty_args)
count.should eq(3)
end

it "ignores invoked tasks" do
count = 0
namespace.task("t1") { count += 1 }
namespace.task("t2", ["t1"]) { |t| t.invoke("t1") }.call(empty_args)
count.should eq(1)
end
end

describe "#execute" do
it "accepts no arguments" do
count = 0
namespace.task("t1") { count += 1 }
namespace.task("t2") { |t| t.execute("t1") }.call(empty_args)
count.should eq(1)
end

it "accepts tuple at the end" do
count = 0
namespace.task("t1") { |t, args| count += args[0].as(Int32) }
namespace.task("t2") { |t| t.execute("t1", 1) }.call(empty_args)
count.should eq(1)
end

it "accepts hash" do
count = 0
namespace.task("t1") { |t, args| count += args["count"].as(Int32) }
namespace.task("t2") { |t| t.execute("t1", {"count" => 2}) }.call(empty_args)
count.should eq(2)
end

it "accepts arg object" do
count = 0
namespace.task("t1") { |t, args| count += args["count"].as(Int32) }
namespace.task("t2") { |t, args| t.execute("t1", args) }.call(Sam::Args.new({"count" => 2}))
count.should eq(2)
end

it "accepts hash and array" do
count = 0
namespace.task("t1") { |t, args| count += args["count"].as(Int32) + args[0].as(Int32) }
namespace.task("t2") { |t| t.execute("t1", {"count" => 2}, [1]) }.call(empty_args)
count.should eq(3)
end

it "ignores invoked tasks" do
count = 0
namespace.task("t1") { count += 1 }
namespace.task("t2", ["t1"]) { |t| t.execute("t1") }.call(empty_args)
count.should eq(2)
end
end
end
6 changes: 3 additions & 3 deletions src/sam.cr
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ module Sam
def self.pretty_print
descs = [] of String
tasks = @@root_namespace.all_tasks
pathes = tasks.map(&.path)
max_length = pathes.map(&.size).max
paths = tasks.map(&.path)
max_length = paths.map(&.size).max
puts "Tasks:"
puts "-" * (max_length + 2) + ":" + "-" * 20
tasks.each_with_index do |task, i|
puts pathes[i].ljust(max_length + 5) + task.description
puts paths[i].ljust(max_length + 5) + task.description
end
end

Expand Down
3 changes: 3 additions & 0 deletions src/sam/namespace.cr
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ module Sam
end
end

# Sets description to the next defined task.
def desc(description : String)
@@description = description
end

# Defines nested namespace.
def namespace(name)
with touch_namespace(name) yield
@namespaces[name]
Expand All @@ -31,6 +33,7 @@ module Sam
@namespaces[name] ||= Namespace.new(name, self)
end

# Defines new task.
def task(name, dependencies = [] of String, &block : Task, Args -> Void)
task = (@tasks[name] = Task.new(block, dependencies, self, name, @@description))
@@description = nil
Expand Down
Loading

0 comments on commit 51c16d9

Please sign in to comment.