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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: de1ac06c3626655a6e1d041392fd749cfde7c820
4
- data.tar.gz: 7d634505e1b52a26b03bc2aaef687678f458d62e
3
+ metadata.gz: 46a11e1fe28465d778a34db82f633bff896279f3
4
+ data.tar.gz: 3e1fa45b6a7ba1759eb1c968388797f5ec7f0e4a
5
5
  SHA512:
6
- metadata.gz: 3bef13d6b793bbdaa4225cdf375579a2788e05b3dd47fa508ce301ce2e9f4c6809b847d69f26d127a3773b974866b833b6a37fd82468c22761ea20bd93e76a2e
7
- data.tar.gz: 9b12056349fedfbbf8845e135f1303a574b6e8a637c13fa77b3a252894fa63ce67b070f4c777b739780c5a9464e29af8fe8b2f58dc6cf80af8a25fbb646d466b
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 build tool for Ruby.'
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 for #{@command.inspect}"
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 usage of this task to the console.
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
- # Describes the task. This message will be printed to the console when
14
- # evoke is called without any arguments.
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
@@ -1,3 +1,3 @@
1
1
  module Evoke
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
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.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 build tool for Ruby.
107
+ summary: A low-profile, zero-dependency command-line task tool for Ruby.
105
108
  test_files: []