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 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: []