evoke 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +18 -0
- data/README.md +37 -0
- data/evoke.gemspec +1 -1
- data/lib/evoke/cli.rb +17 -2
- data/lib/evoke/comment.rb +77 -0
- data/lib/evoke/parameters.rb +86 -0
- data/lib/evoke/task.rb +34 -3
- data/lib/evoke/version.rb +1 -1
- data/lib/evoke.rb +5 -4
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46a11e1fe28465d778a34db82f633bff896279f3
|
4
|
+
data.tar.gz: 3e1fa45b6a7ba1759eb1c968388797f5ec7f0e4a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d9ad91ede3f0176a1641b2da5e8a486065f9f354433d4e0ab725cf813fd47484fa9d3789f581cd4ff2b6f5dc70c3a4014d5ab2d3248b0221fe9af4fb6c8a81b
|
7
|
+
data.tar.gz: 6847b441d5195c99eeb8bcfa790b11ae44d1e7b811c71331a6e9d68770dd35aa134873a7079ce8bf26428a805ff6c71eb3b4c52c00d7e9fdc0f919f31133e625
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Style/EmptyLineBetweenDefs:
|
2
|
+
Enabled: false
|
3
|
+
|
4
|
+
Style/SpaceAroundEqualsInParameterDefault:
|
5
|
+
EnforcedStyle: no_space
|
6
|
+
|
7
|
+
Lint/UnusedMethodArgument:
|
8
|
+
Enabled: false
|
9
|
+
|
10
|
+
Style/EmptyLinesAroundClassBody:
|
11
|
+
Enabled: false
|
12
|
+
|
13
|
+
Metrics/MethodLength:
|
14
|
+
Enabled: true
|
15
|
+
Max: 15
|
16
|
+
|
17
|
+
Metrics/AbcSize:
|
18
|
+
Enabled: false
|
data/README.md
CHANGED
@@ -78,6 +78,43 @@ end
|
|
78
78
|
|
79
79
|
This task would be invoked from the command-line with `evoke math/add A=5 B=10`.
|
80
80
|
|
81
|
+
#### Syntax Usage
|
82
|
+
|
83
|
+
Using `evoke help` will give the user a more detailed description on how to use
|
84
|
+
the task. Providing this description is as easy as adding a comment to the
|
85
|
+
task's class. For example:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
# This is a completely useless task that allows you to add two numbers together
|
89
|
+
# in the console using Evoke, a command-line task tool for Ruby.
|
90
|
+
#
|
91
|
+
# To use the task provide two integers via `evoke add A B`, where A and B are
|
92
|
+
# the integers. For example: `evoke add 1 5` will result in the number 6 being
|
93
|
+
# printed to the console. How completely pointless is that?
|
94
|
+
#
|
95
|
+
# This comment is displayed for this task when you run `evoke help add`.
|
96
|
+
class Add < Evoke::Task
|
97
|
+
desc "Prints the sum of two integers."
|
98
|
+
|
99
|
+
def invoke(a, b)
|
100
|
+
puts a.to_i + b.to_i
|
101
|
+
end
|
102
|
+
end
|
103
|
+
```
|
104
|
+
|
105
|
+
Alternately, you can use the #syntax class method:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
class Add < Evoke::Task
|
109
|
+
desc "Prints the sum of two integers."
|
110
|
+
syntax "Provide two integers as arguments to this task."
|
111
|
+
|
112
|
+
def invoke
|
113
|
+
puts a.to_i + b.to_i
|
114
|
+
end
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
81
118
|
### Loading Tasks
|
82
119
|
|
83
120
|
There are two ways tasks can be loaded.
|
data/evoke.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ['Travis Haynes']
|
10
10
|
spec.email = ['travis@hi5dev.com']
|
11
11
|
|
12
|
-
spec.summary = 'A
|
12
|
+
spec.summary = 'A low-profile, zero-dependency command-line task tool for Ruby.'
|
13
13
|
spec.license = 'MIT'
|
14
14
|
|
15
15
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
data/lib/evoke/cli.rb
CHANGED
@@ -15,6 +15,8 @@ module Evoke
|
|
15
15
|
|
16
16
|
return usage if @command.nil?
|
17
17
|
|
18
|
+
return syntax if @command == "help"
|
19
|
+
|
18
20
|
task = Evoke.find_task(@command) unless @command.nil?
|
19
21
|
|
20
22
|
return unknown_command if task.nil?
|
@@ -22,9 +24,22 @@ module Evoke
|
|
22
24
|
Evoke.invoke(task, *@arguments)
|
23
25
|
end
|
24
26
|
|
27
|
+
# Prints the syntax usage of the task requested by help.
|
28
|
+
def syntax
|
29
|
+
return usage if @arguments.empty?
|
30
|
+
|
31
|
+
@command = @arguments.shift
|
32
|
+
|
33
|
+
task = Evoke.find_task(@command)
|
34
|
+
|
35
|
+
return unknown_command if task.nil?
|
36
|
+
|
37
|
+
task.print_syntax
|
38
|
+
end
|
39
|
+
|
25
40
|
# Prints the usage for all the discovered tasks.
|
26
41
|
def usage
|
27
|
-
name_col_size = task_names.group_by(&:size).keys.max + 2
|
42
|
+
name_col_size = task_names.group_by(&:size).keys.max.to_i + 2
|
28
43
|
tasks.each {|task| task.print_usage(name_col_size) }
|
29
44
|
|
30
45
|
exit(2)
|
@@ -62,7 +77,7 @@ module Evoke
|
|
62
77
|
|
63
78
|
# Tells the user that the supplied task could not be found.
|
64
79
|
def unknown_command
|
65
|
-
STDERR.puts "No task
|
80
|
+
STDERR.puts "No task named #{@command.inspect}"
|
66
81
|
exit(1)
|
67
82
|
end
|
68
83
|
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Evoke
|
2
|
+
# Extendable module for extracting multi-line comments for a class at runtime.
|
3
|
+
#
|
4
|
+
# @example Reading the comment of a class.
|
5
|
+
#
|
6
|
+
# # This is the multi-line
|
7
|
+
# # comment that will be read.
|
8
|
+
# class HelloWorld
|
9
|
+
# extend Evoke::Comment
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# HelloWorld.class_comment # => the multi-line comment before the class
|
13
|
+
#
|
14
|
+
module Comment
|
15
|
+
# Extracts the comment prefixing a class.
|
16
|
+
#
|
17
|
+
# @return [String] The class' comment.
|
18
|
+
def class_comment
|
19
|
+
start_line, lines = start_index_and_code_lines
|
20
|
+
|
21
|
+
comment = []
|
22
|
+
|
23
|
+
(start_line - 1).downto(0).each do |i|
|
24
|
+
line = lines[i].strip
|
25
|
+
|
26
|
+
if line.start_with?('#')
|
27
|
+
comment << line[1..line.length].strip
|
28
|
+
elsif comment != ''
|
29
|
+
break
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
comment.reverse.join("\n")
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Gets the lines in the class file and the line number that the class is
|
39
|
+
# actually defined.
|
40
|
+
#
|
41
|
+
# @param [Array] The start position is first, followed by the code lines.
|
42
|
+
# @private
|
43
|
+
def start_index_and_code_lines
|
44
|
+
data = read_class_file
|
45
|
+
pos = data =~ /class.*\s*#{simple_name}.*\s*\<.*/
|
46
|
+
[data[0..pos].count("\n"), data.split("\n")]
|
47
|
+
end
|
48
|
+
|
49
|
+
# The name of the class without a namespace.
|
50
|
+
#
|
51
|
+
# @return [String] The class' simple name.
|
52
|
+
# @private
|
53
|
+
def simple_name
|
54
|
+
name.rpartition('::')[2]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Reads the file the class that extends this module is in.
|
58
|
+
#
|
59
|
+
# @return [String] The file's contents.
|
60
|
+
# @private
|
61
|
+
def read_class_file
|
62
|
+
path = defined_methods.first[0]
|
63
|
+
File.read(path)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Gets all the methods a class has that are not inherited.
|
67
|
+
#
|
68
|
+
# @return [Array] The source locations of every method in the class.
|
69
|
+
# @private
|
70
|
+
def defined_methods
|
71
|
+
methods = methods(false).map { |m| method(m) }
|
72
|
+
methods += instance_methods(false).map { |m| instance_method(m) }
|
73
|
+
methods.map!(&:source_location)
|
74
|
+
methods.compact
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Evoke
|
2
|
+
# Extendable module for providing access to method parameters during runtime.
|
3
|
+
module Parameters
|
4
|
+
# Gets all the required parameters for a method.
|
5
|
+
#
|
6
|
+
# @param [UnboundMethod] method The method to scan.
|
7
|
+
# @return [Array] An array of the method names.
|
8
|
+
def required_parameters(method)
|
9
|
+
select_parameters_by_type(method, :req)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Gets all the optional parameters for a method.
|
13
|
+
#
|
14
|
+
# @param [UnboundMethod] method The method to scan.
|
15
|
+
# @return [Array] An array of the method names.
|
16
|
+
def optional_parameters(method)
|
17
|
+
select_parameters_by_type(method, :opt)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Finds all the parameters of a given type for the supplied method.
|
21
|
+
#
|
22
|
+
# @example Get the key parameters for a method.
|
23
|
+
#
|
24
|
+
# class Example
|
25
|
+
# extend Evoke::Parameters
|
26
|
+
#
|
27
|
+
# def hello(to: "world")
|
28
|
+
# puts "Hello #{to}"
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# method = Example.instance_method(:hello)
|
33
|
+
# key_params = Example.select_parameters_by_type(method, :key)
|
34
|
+
#
|
35
|
+
# @param [UnboundMethod] method The method to scan.
|
36
|
+
# @param [Symbol] type The type of method.
|
37
|
+
# @return [Array] An array of the method names.
|
38
|
+
def select_parameters_by_type(method, type)
|
39
|
+
args = method.parameters.select { |param| param[0] == type }
|
40
|
+
args.map { |arg| arg[1] }
|
41
|
+
end
|
42
|
+
|
43
|
+
# Parses the parameters for the supplied method into parameters that can be
|
44
|
+
# understood by humans. The method names are capitalized. Optional
|
45
|
+
# parameters are surrounded in brackets.
|
46
|
+
#
|
47
|
+
# @note Key and &block parameters are not supported.
|
48
|
+
#
|
49
|
+
# @param [UnboundMethod] method The method to scan.
|
50
|
+
# @return [Array] The human-readable method names.
|
51
|
+
# @raise [ArgumentError] if an unsupported parameter type is detected.
|
52
|
+
def parameter_names(method)
|
53
|
+
method.parameters.map { |type, name| parameter_name(method, type, name) }
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Parses a method parameter into a format that humans can understand.
|
59
|
+
#
|
60
|
+
# @param [UnboundMethod] method The method the parameter belongs to.
|
61
|
+
# @param [Symbol] type The parameter's type. Only :req and :opt supported.
|
62
|
+
# @param [Symbol] name The method's name.
|
63
|
+
# @return [String] The formatted parameter name.
|
64
|
+
# @raise [ArgumentError] if an unsupported parameter type is supplied.
|
65
|
+
# @private
|
66
|
+
def parameter_name(method, type, name)
|
67
|
+
case type
|
68
|
+
when :req then name.to_s.upcase
|
69
|
+
when :opt then "[#{name.to_s.upcase}]"
|
70
|
+
else unsupported_argument(method, name)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Raised when a method has an unsupported parameter type.
|
75
|
+
#
|
76
|
+
# @param [UnboundMethod] method The method the parameter belongs o.
|
77
|
+
# @param [Symbol] name The method's name.
|
78
|
+
# @raise [ArgumentError] Backtraces to the source of the method.
|
79
|
+
# @private
|
80
|
+
def unsupported_argument(method, name)
|
81
|
+
message = "##{method.name} uses unsupported parameter type for #{name}"
|
82
|
+
source_location = method.source_location.join(':')
|
83
|
+
fail ArgumentError, message, source_location
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/evoke/task.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
module Evoke
|
2
2
|
class Task
|
3
|
+
extend Evoke::Comment
|
4
|
+
extend Evoke::Parameters
|
5
|
+
|
3
6
|
class << self
|
4
|
-
# Prints the
|
7
|
+
# Prints the name and description of this task to the console.
|
5
8
|
#
|
6
9
|
# @param [Integer] name_col_size The size of the name column.
|
7
10
|
# @private
|
@@ -10,8 +13,18 @@ module Evoke
|
|
10
13
|
$stdout.puts "# #{@desc || 'No description.'}"
|
11
14
|
end
|
12
15
|
|
13
|
-
#
|
14
|
-
#
|
16
|
+
# Prints the syntax usage for this task to the console.
|
17
|
+
# @private
|
18
|
+
def print_syntax
|
19
|
+
params = parameter_names(instance_method(:invoke)).join(' ')
|
20
|
+
$stdout.puts "Usage: evoke #{name.underscore} #{params}"
|
21
|
+
$stdout.puts "\n#{syntax}"
|
22
|
+
end
|
23
|
+
|
24
|
+
# A short, one line description of the task.
|
25
|
+
#
|
26
|
+
# This message will be printed to the console when evoke is called without
|
27
|
+
# any arguments.
|
15
28
|
#
|
16
29
|
# @note All descriptions end with a period. One will be added if missing.
|
17
30
|
#
|
@@ -22,6 +35,24 @@ module Evoke
|
|
22
35
|
@desc = value
|
23
36
|
end
|
24
37
|
|
38
|
+
# Describes the syntax of the task.
|
39
|
+
#
|
40
|
+
# The syntax is displayed to the user when they call `evoke help` for this
|
41
|
+
# task. This can be a detailed, multi-line description of how to use the
|
42
|
+
# task. By default the comment before the task's class will be used.
|
43
|
+
#
|
44
|
+
# @param [String] value The details on how to use the task.
|
45
|
+
# @return [String] The syntax - this method is both a getter and setter.
|
46
|
+
def syntax(value=nil)
|
47
|
+
if value.nil?
|
48
|
+
@syntax ||= class_comment
|
49
|
+
else
|
50
|
+
@syntax = value unless value.nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
@syntax
|
54
|
+
end
|
55
|
+
|
25
56
|
# Ensures that the task's #invoke method has the same amount of arguments
|
26
57
|
# as the user supplied on the command-line. If not, an error message is
|
27
58
|
# printed to STDERR and Evoke is terminated with an exit-status of 1.
|
data/lib/evoke/version.rb
CHANGED
data/lib/evoke.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
require 'evoke/version'
|
2
|
-
require 'evoke/task'
|
3
|
-
|
4
1
|
module Evoke
|
2
|
+
require 'evoke/version'
|
5
3
|
require 'evoke/inflections'
|
4
|
+
require 'evoke/comment'
|
5
|
+
require 'evoke/parameters'
|
6
|
+
require 'evoke/task'
|
6
7
|
|
7
8
|
class << self
|
8
9
|
# Gets all the classes that descend from Evoke::Task.
|
@@ -83,7 +84,7 @@ module Evoke
|
|
83
84
|
# @param [Evoke::Task] task The task instance that is being invoked.
|
84
85
|
# @param [Array] args The arguments that are being passed to the task.
|
85
86
|
def call_before_hooks(task, *args)
|
86
|
-
Array(@before_hooks).each {|hook| hook.call(*args) }
|
87
|
+
Array(@before_hooks).each {|hook| hook.call(task, *args) }
|
87
88
|
end
|
88
89
|
end
|
89
90
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: evoke
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Travis Haynes
|
@@ -61,6 +61,7 @@ extensions: []
|
|
61
61
|
extra_rdoc_files: []
|
62
62
|
files:
|
63
63
|
- .gitignore
|
64
|
+
- .rubocop.yml
|
64
65
|
- .travis.yml
|
65
66
|
- CODE_OF_CONDUCT.md
|
66
67
|
- Gemfile
|
@@ -73,9 +74,11 @@ files:
|
|
73
74
|
- exe/evoke
|
74
75
|
- lib/evoke.rb
|
75
76
|
- lib/evoke/cli.rb
|
77
|
+
- lib/evoke/comment.rb
|
76
78
|
- lib/evoke/inflections.rb
|
77
79
|
- lib/evoke/inflections/camelize.rb
|
78
80
|
- lib/evoke/inflections/underscore.rb
|
81
|
+
- lib/evoke/parameters.rb
|
79
82
|
- lib/evoke/task.rb
|
80
83
|
- lib/evoke/version.rb
|
81
84
|
homepage:
|
@@ -101,5 +104,5 @@ rubyforge_project:
|
|
101
104
|
rubygems_version: 2.2.2
|
102
105
|
signing_key:
|
103
106
|
specification_version: 4
|
104
|
-
summary: A
|
107
|
+
summary: A low-profile, zero-dependency command-line task tool for Ruby.
|
105
108
|
test_files: []
|