evoke 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|