bake 0.12.1 → 0.15.0

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
  SHA256:
3
- metadata.gz: 5451e66cf2062b167b8b9983bb0308612b6234de8f99af360ddb1098e74d973e
4
- data.tar.gz: feb9f86818d18175eab079b7875c3b3810d1017dcb82f717aaab2f02d04d44ed
3
+ metadata.gz: e9aacfa1d30a7a1c8c888d399ea3d24c9f8039b5a58d3c6e6444be9bdaa39910
4
+ data.tar.gz: e3b8d501ac13f9346d1bf07fcee7314bde74eeccd9bb7cfe119819913dd568da
5
5
  SHA512:
6
- metadata.gz: 6043f4715eafdb780fea3c09ad50fe262d3e2a80234c4c76884b887bf0460e96f652bca576f5f5215d1d0aada420f33a0c8894fbc45ef2b10e716a075890e97c
7
- data.tar.gz: dd58f182d931c1844d5b50794e55d0dd4ec5933fa201e2206adb6abf879b2aff19ab6030c0b2035186e748d5ef1070c34c518573d5fc3c4b7c8f679687ffe5af
6
+ metadata.gz: dc423bf25a37828f28fe9a508976d54c6913223e1507b46ae19e9ffd5e8d9a8254a37bc174e9025425acdc3b847d1929389bc799c5154801e28d96b4e8d8e352
7
+ data.tar.gz: 816410e988ba73a4f6ca130ab27cce179ea18d791ed8811e20ebad9e036f5a486c9a1f3e8122d057e3f253c5700ef1ed6bfc502a0bbbf1a5e77d24a00156581a
@@ -0,0 +1,120 @@
1
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'types'
22
+ require_relative 'documentation'
23
+
24
+ module Bake
25
+ # Structured access to arguments.
26
+ class Arguments
27
+ def self.extract(recipe, arguments)
28
+ self.new(recipe).extract(arguments)
29
+ end
30
+
31
+ def initialize(recipe)
32
+ @recipe = recipe
33
+
34
+ @types = recipe.types
35
+ @parameters = recipe.parameters
36
+ @arity = recipe.arity
37
+
38
+ @ordered = []
39
+ @options = {}
40
+ end
41
+
42
+ attr :ordered
43
+ attr :options
44
+
45
+ def extract(arguments)
46
+ while argument = arguments.first
47
+ if /^--(?<name>.*)$/ =~ argument
48
+ # Consume the argument:
49
+ arguments.shift
50
+
51
+ if name.empty?
52
+ break
53
+ end
54
+
55
+ name = name.to_sym
56
+
57
+ # Extract the trailing arguments:
58
+ @options[name] = extract_arguments(name, arguments)
59
+ elsif /^(?<name>.*?)=(?<value>.*)$/ =~ argument
60
+ # Consume the argument:
61
+ arguments.shift
62
+
63
+ name = name.to_sym
64
+
65
+ # Extract the single argument:
66
+ @options[name] = extract_argument(name, value)
67
+ elsif @ordered.size < @arity
68
+ _, name = @parameters.shift
69
+ value = arguments.shift
70
+
71
+ # Consume it:
72
+ @ordered << extract_argument(name, value)
73
+ else
74
+ break
75
+ end
76
+ end
77
+
78
+ return @ordered, @options
79
+ end
80
+
81
+ private
82
+
83
+ def delimiter_index(arguments)
84
+ arguments.index{|argument| argument =~ /\A(--|;\z)/}
85
+ end
86
+
87
+ def extract_arguments(name, arguments)
88
+ value = nil
89
+ type = @types[name]
90
+
91
+ # Can this named parameter accept more than one input argument?
92
+ if type&.composite?
93
+ if count = delimiter_index(arguments)
94
+ value = arguments.shift(count)
95
+ arguments.shift if arguments.first == ';'
96
+ else
97
+ value = arguments.dup
98
+ arguments.clear
99
+ end
100
+ else
101
+ # Otherwise we just take one item:
102
+ value = arguments.shift
103
+ end
104
+
105
+ if type
106
+ value = type.parse(value)
107
+ end
108
+
109
+ return value
110
+ end
111
+
112
+ def extract_argument(name, value)
113
+ if type = @types[name]
114
+ value = type.parse(value)
115
+ end
116
+
117
+ return value
118
+ end
119
+ end
120
+ end
@@ -22,7 +22,10 @@ require_relative 'recipe'
22
22
  require_relative 'scope'
23
23
 
24
24
  module Bake
25
+ # The base class for including {Scope} instances which define {Recipe} instances.
25
26
  class Base < Struct.new(:context)
27
+ # Generate a base class for the specified path.
28
+ # @parameter path [Array(String)] The command path.
26
29
  def self.derive(path = [])
27
30
  klass = Class.new(self)
28
31
 
@@ -31,6 +34,8 @@ module Bake
31
34
  return klass
32
35
  end
33
36
 
37
+ # Format the class as a command.
38
+ # @returns [String]
34
39
  def self.to_s
35
40
  if path = self.path
36
41
  path.join(':')
@@ -47,20 +52,31 @@ module Bake
47
52
  end
48
53
  end
49
54
 
55
+ # The path of this derived base class.
56
+ # @returns [Array(String)]
50
57
  def self.path
51
58
  self.const_get(:PATH)
52
59
  rescue
53
60
  nil
54
61
  end
55
62
 
63
+ # The path for this derived base class.
64
+ # @returns [Array(String)]
56
65
  def path
57
66
  self.class.path
58
67
  end
59
68
 
69
+ # Proxy a method call using command line arguments through to the {Context} instance.
70
+ # @parameter arguments [Array(String)]
60
71
  def call(*arguments)
61
72
  self.context.call(*arguments)
62
73
  end
63
74
 
75
+ # Recipes defined in this scope.
76
+ #
77
+ # @yields {|recipe| ...}
78
+ # @parameter recipe [Recipe]
79
+ # @returns [Enumerable]
64
80
  def recipes
65
81
  return to_enum(:recipes) unless block_given?
66
82
 
@@ -71,6 +87,9 @@ module Bake
71
87
  end
72
88
  end
73
89
 
90
+ # Look up a recipe with a specific name.
91
+ #
92
+ # @parameter name [String] The instance method to look up.
74
93
  def recipe_for(name)
75
94
  Recipe.new(self, name)
76
95
  end
@@ -21,6 +21,7 @@
21
21
  require_relative 'command/top'
22
22
 
23
23
  module Bake
24
+ # The command line interface.
24
25
  module Command
25
26
  def self.call(*arguments)
26
27
  Top.call(*arguments)
@@ -26,6 +26,7 @@ require_relative '../context'
26
26
 
27
27
  module Bake
28
28
  module Command
29
+ # Execute one or more commands.
29
30
  class Call < Samovar::Command
30
31
  self.description = "Execute one or more commands."
31
32
 
@@ -33,7 +34,7 @@ module Bake
33
34
  @parent.bakefile
34
35
  end
35
36
 
36
- many :commands, "The commands & arguments to invoke.", default: ["default"]
37
+ many :commands, "The commands & arguments to invoke.", default: ["default"], stop: false
37
38
 
38
39
  def call
39
40
  context = @parent.context
@@ -23,7 +23,10 @@ require 'set'
23
23
 
24
24
  module Bake
25
25
  module Command
26
+ # List all available commands.
26
27
  class List < Samovar::Command
28
+ self.description = "List all available commands."
29
+
27
30
  one :pattern, "The pattern to filter tasks by."
28
31
 
29
32
  def format_parameters(parameters, terminal)
@@ -26,6 +26,7 @@ require_relative 'list'
26
26
 
27
27
  module Bake
28
28
  module Command
29
+ # The top level command line application.
29
30
  class Top < Samovar::Command
30
31
  self.description = "Execute tasks using Ruby."
31
32
 
@@ -56,12 +57,12 @@ module Bake
56
57
  return terminal
57
58
  end
58
59
 
59
- def bakefile
60
+ def bakefile_path
60
61
  @options[:bakefile] || Dir.pwd
61
62
  end
62
63
 
63
64
  def context
64
- Context.load(self.bakefile)
65
+ Context.load(self.bakefile_path)
65
66
  end
66
67
 
67
68
  def call
@@ -21,11 +21,14 @@
21
21
  require_relative 'base'
22
22
 
23
23
  module Bake
24
+ # The default file name for the top level bakefile.
24
25
  BAKEFILE = "bake.rb"
25
26
 
27
+ # Represents a context of task execution, containing all relevant state.
26
28
  class Context
29
+ # Search upwards from the specified path for a {BAKEFILE}.
27
30
  # If path points to a file, assume it's a `bake.rb` file. Otherwise, recursively search up the directory tree starting from `path` to find the specified bakefile.
28
- # @return [String, nil] the path to the bakefile if it could be found.
31
+ # @returns [String | Nil] The path to the bakefile if it could be found.
29
32
  def self.bakefile_path(path, bakefile: BAKEFILE)
30
33
  if File.file?(path)
31
34
  return path
@@ -52,6 +55,8 @@ module Bake
52
55
  return nil
53
56
  end
54
57
 
58
+ # Load a context from the specified path.
59
+ # @path [String] A file-system path.
55
60
  def self.load(path = Dir.pwd)
56
61
  if bakefile_path = self.bakefile_path(path)
57
62
  scope = Scope.load(bakefile_path)
@@ -68,6 +73,8 @@ module Bake
68
73
  return self.new(loaders, scope, working_directory)
69
74
  end
70
75
 
76
+ # Initialize the context with the specified loaders.
77
+ # @parameter loaders [Loaders]
71
78
  def initialize(loaders, scope = nil, root = nil)
72
79
  @loaders = loaders
73
80
 
@@ -92,10 +99,18 @@ module Bake
92
99
  end
93
100
  end
94
101
 
102
+ # The loaders which will be used to resolve recipes in this context.
103
+ attr :loaders
104
+
105
+ # The scope for the root {BAKEFILE}.
95
106
  attr :scope
107
+
108
+ # The root path of this context.
109
+ # @returns [String | Nil]
96
110
  attr :root
97
- attr :loaders
98
111
 
112
+ # Invoke recipes on the context using command line arguments.
113
+ # @parameter commands [Array(String)]
99
114
  def call(*commands)
100
115
  last_result = nil
101
116
 
@@ -111,6 +126,8 @@ module Bake
111
126
  return last_result
112
127
  end
113
128
 
129
+ # Lookup a recipe for the given command name.
130
+ # @parameter command [String] The command name, e.g. `bundler:release`.
114
131
  def lookup(command)
115
132
  @recipes[command]
116
133
  end
@@ -147,7 +164,7 @@ module Bake
147
164
  end
148
165
  end
149
166
 
150
- # @param path [Array<String>] the path for the scope.
167
+ # @parameter path [Array<String>] the path for the scope.
151
168
  def base_for(path)
152
169
  base = nil
153
170
 
@@ -21,14 +21,22 @@
21
21
  require_relative 'scope'
22
22
 
23
23
  module Bake
24
+ # Structured access to a set of comment lines.
24
25
  class Documentation
25
-
26
+ # Initialize the documentation with an array of comments.
27
+ #
28
+ # @parameter comments [Array(String)] An array of comment lines.
26
29
  def initialize(comments)
27
30
  @comments = comments
28
31
  end
29
32
 
30
33
  DESCRIPTION = /\A\s*([^@\s].*)?\z/
31
34
 
35
+ # The text-only lines of the comment block.
36
+ #
37
+ # @yields {|match| ...}
38
+ # @parameter match [MatchData] The regular expression match for each line of documentation.
39
+ # @returns [Enumerable] If no block given.
32
40
  def description
33
41
  return to_enum(:description) unless block_given?
34
42
 
@@ -55,6 +63,12 @@ module Bake
55
63
 
56
64
  ATTRIBUTE = /\A\s*@(?<name>.*?)\s+(?<value>.*?)\z/
57
65
 
66
+ # The attribute lines of the comment block.
67
+ # e.g. `@returns [String]`.
68
+ #
69
+ # @yields {|match| ...}
70
+ # @parameter match [MatchData] The regular expression match with `name` and `value` keys.
71
+ # @returns [Enumerable] If no block given.
58
72
  def attributes
59
73
  return to_enum(:attributes) unless block_given?
60
74
 
@@ -65,8 +79,14 @@ module Bake
65
79
  end
66
80
  end
67
81
 
68
- PARAMETER = /\A\s*@param\s+(?<name>.*?)\s+\[(?<type>.*?)\]\s+(?<details>.*?)\z/
82
+ PARAMETER = /\A\s*@param(eter)?\s+(?<name>.*?)\s+\[(?<type>.*?)\](\s+(?<details>.*?))?\z/
69
83
 
84
+ # The parameter lines of the comment block.
85
+ # e.g. `@parameter value [String] The value.`
86
+ #
87
+ # @yields {|match| ...}
88
+ # @parameter match [MatchData] The regular expression match with `name`, `type` and `details` keys.
89
+ # @returns [Enumerable] If no block given.
70
90
  def parameters
71
91
  return to_enum(:parameters) unless block_given?
72
92
 
@@ -21,7 +21,10 @@
21
21
  require_relative 'scope'
22
22
 
23
23
  module Bake
24
+ # Represents a directory which contains bakefiles.
24
25
  class Loader
26
+ # Initialize the loader with the specified root path.
27
+ # @parameter root [String] A file-system path.
25
28
  def initialize(root, name: nil)
26
29
  @root = root
27
30
  @name = name
@@ -31,8 +34,12 @@ module Bake
31
34
  "#{self.class} #{@name || @root}"
32
35
  end
33
36
 
37
+ # The root path for this loader.
34
38
  attr :root
35
39
 
40
+ # Enumerate all bakefiles within the loaders root directory.
41
+ # @yields {|path| ...}
42
+ # @parameter path [String] The Ruby source file path.
36
43
  def each
37
44
  return to_enum unless block_given?
38
45
 
@@ -41,18 +48,8 @@ module Bake
41
48
  end
42
49
  end
43
50
 
44
- def recipe_for(path)
45
- if book = lookup(path)
46
- return book.lookup(path.last)
47
- else
48
- *path, name = *path
49
-
50
- if book = lookup(path)
51
- return book.lookup(name)
52
- end
53
- end
54
- end
55
-
51
+ # Load the {Scope} for the specified relative path within this loader, if it exists.
52
+ # @parameter path [Array(String)] A relative path.
56
53
  def scope_for(path)
57
54
  *directory, file = *path
58
55
 
@@ -23,9 +23,12 @@ require 'console'
23
23
  require_relative 'loader'
24
24
 
25
25
  module Bake
26
+ # Structured access to the working directory and loaded gems for loading bakefiles.
26
27
  class Loaders
27
28
  include Enumerable
28
29
 
30
+ # Create a loader using the specified working directory.
31
+ # @parameter working_directory [String]
29
32
  def self.default(working_directory)
30
33
  loaders = self.new
31
34
 
@@ -34,15 +37,20 @@ module Bake
34
37
  return loaders
35
38
  end
36
39
 
40
+ # Initialize an empty array of loaders.
37
41
  def initialize
38
42
  @roots = {}
39
43
  @ordered = Array.new
40
44
  end
41
45
 
46
+ # Whether any loaders are defined.
47
+ # @returns [Boolean]
42
48
  def empty?
43
49
  @ordered.empty?
44
50
  end
45
51
 
52
+ # Add loaders according to the current working directory and loaded gems.
53
+ # @parameter working_directory [String]
46
54
  def append_defaults(working_directory)
47
55
  # Load recipes from working directory:
48
56
  self.append_path(working_directory)
@@ -51,32 +59,26 @@ module Bake
51
59
  self.append_from_gems
52
60
  end
53
61
 
62
+ # Enumerate the loaders in order.
54
63
  def each(&block)
55
- return to_enum unless block_given?
56
-
57
64
  @ordered.each(&block)
58
65
  end
59
66
 
67
+ # Append a specific project path to the search path for recipes.
68
+ # The computed path will have `bake` appended to it.
69
+ # @parameter current [String] The path to add.
60
70
  def append_path(current = Dir.pwd, **options)
61
- recipes_path = File.join(current, "recipes")
62
71
  bake_path = File.join(current, "bake")
63
72
 
64
73
  if File.directory?(bake_path)
65
74
  return insert(bake_path, **options)
66
75
  end
67
76
 
68
- if File.directory?(recipes_path)
69
- Console.logger.warn(self) do |buffer|
70
- buffer.puts "Recipes path: #{recipes_path.inspect} is deprecated."
71
- buffer.puts "Please use #{bake_path.inspect} instead."
72
- end
73
-
74
- return insert(recipes_path, **options)
75
- end
76
-
77
77
  return false
78
78
  end
79
79
 
80
+ # Search from the current working directory until a suitable bakefile is found and add it.
81
+ # @parameter current [String] The path to start searching from.
80
82
  def append_from_root(current = Dir.pwd, **options)
81
83
  while current
82
84
  append_path(current, **options)
@@ -91,6 +93,7 @@ module Bake
91
93
  end
92
94
  end
93
95
 
96
+ # Enumerate all loaded gems and add them.
94
97
  def append_from_gems
95
98
  Gem.loaded_specs.each do |name, spec|
96
99
  Console.logger.debug(self) {"Checking gem #{name}: #{spec.full_gem_path}..."}
@@ -101,12 +104,6 @@ module Bake
101
104
  end
102
105
  end
103
106
 
104
- def append_from_paths(paths, **options)
105
- paths.each do |path|
106
- append_path(path, **options)
107
- end
108
- end
109
-
110
107
  protected
111
108
 
112
109
  def insert(directory, **options)
@@ -19,10 +19,17 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require_relative 'types'
22
+ require_relative 'arguments'
22
23
  require_relative 'documentation'
23
24
 
24
25
  module Bake
26
+ # Structured access to an instance method in a bakefile.
25
27
  class Recipe
28
+ # Initialize the recipe.
29
+ #
30
+ # @parameter instance [Base] The instance this recipe is attached to.
31
+ # @parameter name [String] The method name.
32
+ # @parameter method [Method | Nil] The method if already known.
26
33
  def initialize(instance, name, method = nil)
27
34
  @instance = instance
28
35
  @name = name
@@ -35,21 +42,29 @@ module Bake
35
42
  @arity = nil
36
43
  end
37
44
 
45
+ # The {Base} instance that this recipe is attached to.
38
46
  attr :instance
47
+
48
+ # The name of this recipe.
39
49
  attr :name
40
50
 
51
+ # Sort by location in source file.
41
52
  def <=> other
42
53
  self.source_location <=> other.source_location
43
54
  end
44
55
 
56
+ # The method implementation.
45
57
  def method
46
58
  @method ||= @instance.method(@name)
47
59
  end
48
60
 
61
+ # The source location of this recipe.
49
62
  def source_location
50
63
  self.method.source_location
51
64
  end
52
65
 
66
+ # The recipe's formal parameters, if any.
67
+ # @returns [Array | Nil]
53
68
  def parameters
54
69
  parameters = method.parameters
55
70
 
@@ -58,6 +73,8 @@ module Bake
58
73
  end
59
74
  end
60
75
 
76
+ # Whether this recipe has optional arguments.
77
+ # @returns [Boolean]
61
78
  def options?
62
79
  if parameters = self.parameters
63
80
  type, name = parameters.last
@@ -66,6 +83,7 @@ module Bake
66
83
  end
67
84
  end
68
85
 
86
+ # The command name for this recipe.
69
87
  def command
70
88
  @command ||= compute_command
71
89
  end
@@ -74,6 +92,7 @@ module Bake
74
92
  self.command
75
93
  end
76
94
 
95
+ # The method's arity, the required number of positional arguments.
77
96
  def arity
78
97
  if @arity.nil?
79
98
  @arity = method.parameters.count{|type, name| type == :req}
@@ -82,43 +101,15 @@ module Bake
82
101
  return @arity
83
102
  end
84
103
 
104
+ # Process command line arguments into the ordered and optional arguments.
105
+ # @parameter arguments [Array(String)] The command line arguments
106
+ # @returns ordered [Array]
107
+ # @returns options [Hash]
85
108
  def prepare(arguments)
86
- offset = 0
87
- ordered = []
88
- options = {}
89
- parameters = method.parameters.dup
90
- types = self.types
91
-
92
- while argument = arguments.first
93
- name, value = argument.split('=', 2)
94
-
95
- if name and value
96
- # Consume it:
97
- arguments.shift
98
-
99
- if type = types[name.to_sym]
100
- value = type.parse(value)
101
- end
102
-
103
- options[name.to_sym] = value
104
- elsif ordered.size < self.arity
105
- _, name = parameters.shift
106
- value = arguments.shift
107
-
108
- if type = types[name]
109
- value = type.parse(value)
110
- end
111
-
112
- # Consume it:
113
- ordered << value
114
- else
115
- break
116
- end
117
- end
118
-
119
- return ordered, options
109
+ Arguments.extract(self, arguments)
120
110
  end
121
111
 
112
+ # Call the recipe with the specified arguments and options.
122
113
  def call(*arguments, **options)
123
114
  if options?
124
115
  @instance.send(@name, *arguments, **options)
@@ -128,28 +119,39 @@ module Bake
128
119
  end
129
120
  end
130
121
 
131
- def explain(context, *arguments, **options)
132
- if options?
133
- puts "#{self}(#{arguments.join(", ")}, #{options.inspect})"
134
- else
135
- puts "#{self}(#{arguments.join(", ")})"
136
- end
137
- end
138
-
122
+ # Any comments associated with the source code which defined the method.
123
+ # @returns [Array(String)] The comment lines.
139
124
  def comments
140
125
  @comments ||= read_comments
141
126
  end
142
127
 
128
+ # The documentation object which provides structured access to the {comments}.
129
+ # @returns [Documentation]
143
130
  def documentation
144
131
  @documentation ||= Documentation.new(self.comments)
145
132
  end
146
133
 
134
+ # The documented type signature of the recipe.
135
+ # @returns [Array] An array of {Types} instances.
147
136
  def types
148
137
  @types ||= read_types
149
138
  end
150
139
 
151
140
  private
152
141
 
142
+ def parse(name, value, arguments, types)
143
+ if count = arguments.index(';')
144
+ value = arguments.shift(count)
145
+ arguments.shift
146
+ end
147
+
148
+ if type = types[name]
149
+ value = type.parse(value)
150
+ end
151
+
152
+ return value
153
+ end
154
+
153
155
  def compute_command
154
156
  path = @instance.path
155
157
 
@@ -21,7 +21,9 @@
21
21
  require_relative 'recipe'
22
22
 
23
23
  module Bake
24
+ # Used for containing all methods defined in a bakefile.
24
25
  module Scope
26
+ # Load the specified file into a unique scope module, which can then be included into a {Base} instance.
25
27
  def self.load(file_path, path = [])
26
28
  scope = Module.new
27
29
  scope.extend(self)
@@ -38,10 +40,11 @@ module Bake
38
40
  "Bake::Scope<#{self.const_get(:FILE_PATH)}>"
39
41
  end
40
42
 
41
- def recipe(name, **options, &block)
42
- define_method(name, &block)
43
- end
44
-
43
+ # Recipes defined in this scope.
44
+ #
45
+ # @yields {|recipe| ...}
46
+ # @parameter recipe [Recipe]
47
+ # @returns [Enumerable]
45
48
  def recipes
46
49
  return to_enum(:recipes) unless block_given?
47
50
 
@@ -52,10 +55,14 @@ module Bake
52
55
  end
53
56
  end
54
57
 
58
+ # The path of the file that was used to {load} this scope.
55
59
  def path
56
60
  self.const_get(:PATH)
57
61
  end
58
62
 
63
+ # Look up a recipe with a specific name.
64
+ #
65
+ # @parameter name [String] The instance method to look up.
59
66
  def recipe_for(name)
60
67
  Recipe.new(self, name, self.instance_method(name))
61
68
  end
@@ -20,43 +20,70 @@
20
20
 
21
21
  module Bake
22
22
  module Types
23
- module Type
24
- def | other
25
- Any.new([self, other])
26
- end
27
- end
28
-
23
+ # An ordered list of types. The first type to match the input is used.
24
+ #
25
+ # ```ruby
26
+ # type = Bake::Types::Any(Bake::Types::String, Bake::Types::Integer)
27
+ # ```
28
+ #
29
29
  class Any
30
+ # Initialize the instance with an array of types.
31
+ # @parameter types [Array] the array of types.
30
32
  def initialize(types)
31
33
  @types = types
32
34
  end
33
35
 
36
+ # Create a copy of the current instance with the other type appended.
37
+ # @parameter other [Type] the type instance to append.
34
38
  def | other
35
39
  self.class.new([*@types, other])
36
40
  end
37
41
 
42
+ # Whether any of the listed types is a composite type.
43
+ # @returns [Boolean] true if any of the listed types is `composite?`.
38
44
  def composite?
39
45
  @types.any?{|type| type.composite?}
40
46
  end
41
47
 
48
+ # Parse an input string, trying the listed types in order, returning the first one which doesn't raise an exception.
49
+ # @parameter input [String] the input to parse, e.g. `"5"`.
42
50
  def parse(input)
43
51
  @types.each do |type|
44
52
  return type.parse(input)
45
53
  rescue
46
- # Ignore
54
+ # Ignore.
47
55
  end
48
56
  end
49
57
 
58
+ # As a class type, accepts any value.
50
59
  def self.parse(value)
51
60
  value
52
61
  end
53
62
 
63
+ # Generate a readable string representation of the listed types.
54
64
  def to_s
55
65
  "any of #{@types.join(', ')}"
56
66
  end
57
67
  end
58
68
 
59
- def self.Any(types)
69
+ # An extension module which allows constructing `Any` types using the `|` operator.
70
+ module Type
71
+ # Create an instance of `Any` with the arguments as types.
72
+ # @parameter other [Type] the alternative type to match.
73
+ def | other
74
+ Any.new([self, other])
75
+ end
76
+ end
77
+
78
+ # A type constructor.
79
+ #
80
+ # ```ruby
81
+ # Any(Integer, String)
82
+ # ```
83
+ #
84
+ # See [Any.initialize](#Bake::Types::Any::initialize).
85
+ #
86
+ def self.Any(*types)
60
87
  Any.new(types)
61
88
  end
62
89
  end
@@ -33,8 +33,19 @@ module Bake
33
33
  true
34
34
  end
35
35
 
36
+ def map(values)
37
+ values.map{|value| @item_type.parse(value)}
38
+ end
39
+
36
40
  def parse(input)
37
- input.split(',').map{|value| @item_type.parse(value)}
41
+ case input
42
+ when ::String
43
+ return input.split(',').map{|value| @item_type.parse(value)}
44
+ when ::Array
45
+ return input.map{|value| @item_type.parse(value)}
46
+ else
47
+ raise ArgumentError, "Cannot coerce #{input.inspect} into array!"
48
+ end
38
49
  end
39
50
 
40
51
  def to_s
@@ -32,6 +32,10 @@ module Bake
32
32
  def self.parse(input)
33
33
  if input =~ /t(rue)?|y(es)?/i
34
34
  return true
35
+ elsif input =~ /f(alse)?|n(o)?/i
36
+ return false
37
+ else
38
+ raise ArgumentError, "Cannot coerce #{input.inspect} into boolean!"
35
39
  end
36
40
  end
37
41
  end
@@ -33,7 +33,7 @@ module Bake
33
33
  if input =~ /nil|null/i
34
34
  return nil
35
35
  else
36
- raise ArgumentError, "Cannot coerce #{input} into nil!"
36
+ raise ArgumentError, "Cannot coerce #{input.inspect} into nil!"
37
37
  end
38
38
  end
39
39
  end
@@ -34,7 +34,14 @@ module Bake
34
34
  end
35
35
 
36
36
  def parse(input)
37
- input.split(',').map{|value| @item_type.parse(value)}
37
+ case input
38
+ when ::String
39
+ return input.split(',').map{|value| @item_type.parse(value)}
40
+ when ::Array
41
+ return input.map{|value| @item_type.parse(value)}
42
+ else
43
+ raise ArgumentError, "Cannot coerce #{input.inspect} into tuple!"
44
+ end
38
45
  end
39
46
 
40
47
  def to_s
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Bake
22
- VERSION = "0.12.1"
22
+ VERSION = "0.15.0"
23
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bake
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.1
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-30 00:00:00.000000000 Z
11
+ date: 2020-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: samovar
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.1'
27
27
  - !ruby/object:Gem::Dependency
28
- name: bake-bundler
28
+ name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -52,20 +52,6 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: rspec
71
57
  requirement: !ruby/object:Gem::Requirement
@@ -80,38 +66,16 @@ dependencies:
80
66
  - - ">="
81
67
  - !ruby/object:Gem::Version
82
68
  version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: rake
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- description:
69
+ description:
98
70
  email:
99
- - samuel.williams@oriontransfer.co.nz
100
71
  executables:
101
72
  - bake
102
73
  extensions: []
103
74
  extra_rdoc_files: []
104
75
  files:
105
- - ".github/workflows/development.yml"
106
- - ".gitignore"
107
- - ".rspec"
108
- - README.md
109
- - bake.gemspec
110
- - bake.rb
111
76
  - bin/bake
112
- - example.png
113
- - gems.rb
114
77
  - lib/bake.rb
78
+ - lib/bake/arguments.rb
115
79
  - lib/bake/base.rb
116
80
  - lib/bake/command.rb
117
81
  - lib/bake/command/call.rb
@@ -142,8 +106,8 @@ homepage: https://github.com/ioquatix/bake
142
106
  licenses:
143
107
  - MIT
144
108
  metadata:
145
- donation_uri: https://github.com/sponsors/ioquatix
146
- post_install_message:
109
+ funding_uri: https://github.com/sponsors/ioquatix/
110
+ post_install_message:
147
111
  rdoc_options: []
148
112
  require_paths:
149
113
  - lib
@@ -159,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
123
  version: '0'
160
124
  requirements: []
161
125
  rubygems_version: 3.1.2
162
- signing_key:
126
+ signing_key:
163
127
  specification_version: 4
164
128
  summary: A replacement for rake with a simpler syntax.
165
129
  test_files: []
@@ -1,35 +0,0 @@
1
- name: Development
2
-
3
- on: [push]
4
-
5
- jobs:
6
- test:
7
- strategy:
8
- matrix:
9
- os:
10
- - ubuntu
11
- - macos
12
-
13
- ruby:
14
- - 2.5
15
- - 2.6
16
- - 2.7
17
-
18
- include:
19
- - os: 'ubuntu'
20
- ruby: '2.6'
21
- env: COVERAGE=PartialSummary,Coveralls
22
-
23
- runs-on: ${{matrix.os}}-latest
24
-
25
- steps:
26
- - uses: actions/checkout@v1
27
- - uses: actions/setup-ruby@v1
28
- with:
29
- ruby-version: ${{matrix.ruby}}
30
- - name: Install dependencies
31
- run: |
32
- command -v bundler || gem install bundler
33
- bundle install
34
- - name: Run tests
35
- run: ${{matrix.env}} bundle exec rspec
data/.gitignore DELETED
@@ -1,13 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- gems.locked
11
-
12
- # rspec failure tracking
13
- .rspec_status
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/README.md DELETED
@@ -1,138 +0,0 @@
1
- # Bake
2
-
3
- Bake is a task execution tool, inspired by Rake, but codifying many of the use cases which are typically implemented in an ad-hoc manner.
4
-
5
- [![Actions Status](https://github.com/ioquatix/bake/workflows/Development/badge.svg)](https://github.com/ioquatix/bake/actions?workflow=Development)
6
-
7
- ## Motivation
8
-
9
- Rake is an awesome tool and loved by the community. So, why reinvent it? Bake provides the following features that Rake does not:
10
-
11
- - On demand loading of files following a standard convention. This avoid loading all your rake tasks just to execute a single command.
12
- - Better argument handling including support for positional and optional arguments.
13
- - Focused on task execution not dependency resolution. Implementation is simpler and a bit more predictable.
14
- - Canonical structure for integration with gems.
15
-
16
- That being said, Rake and Bake can exist side by side in the same project.
17
-
18
- ## Installation
19
-
20
- Execute the following in your project:
21
-
22
- bundle add bake
23
-
24
- ## Usage
25
-
26
- Bake follows similar patterns to Rake.
27
-
28
- ![Example](example.png)
29
-
30
- ## Bakefile
31
-
32
- There is a root level `bake.rb` e.g.:
33
-
34
- ```ruby
35
- def cake
36
- ingredients = call 'supermarket:shop', 'flour,sugar,cocoa'
37
- lookup('mixer:add').call(ingredients)
38
- end
39
- ```
40
-
41
- This file is project specific and is the only file which can expose top level tasks (i.e. without a defined namespace). When used in a gem, these tasks are not exposed to other gems/projects.
42
-
43
- ## Recipes
44
-
45
- Alongside the `bake.rb`, there is a `bake/` directory which contains files like `supermarket.rb`. These files contain recipes, e.g.:
46
-
47
- ```ruby
48
- # @param ingredients [Array(Any)] the ingredients to purchase.
49
- def shop(ingredients)
50
- supermarket = Supermarket.best
51
-
52
- return supermarket.purchase(ingredients)
53
- end
54
- ```
55
-
56
- These methods are automatically scoped according to the file name, e.g. `bake/supermarket.rb` will define `supermarket:shop`.
57
-
58
- ## Gems
59
-
60
- Adding a `bake/` directory to your gem will allow other gems and projects to consume those recipes. In order to prevent collisions, you *should* prefix your commands with the name of the gem, e.g. in `mygem/bake/mygem.rb`:
61
-
62
- ```ruby
63
- def setup
64
- # ...
65
- end
66
- ```
67
-
68
- Then, in your project `myproject` which depends on `mygem`:
69
-
70
- ```
71
- bake mygem:setup
72
- ```
73
-
74
- ## Arguments
75
-
76
- Arguments work as normal. Documented types are used to parse strings from the command line. Both positional and optional parameters are supported.
77
-
78
- ### Positional Parameters
79
-
80
- ```ruby
81
- # @param x [Integer]
82
- # @param y [Integer]
83
- def add(x, y)
84
- puts x + y
85
- end
86
- ```
87
-
88
- Which is invoked by `bake add 1 2`.
89
-
90
- ### Optional Parameters
91
-
92
- ```ruby
93
- # @param x [Integer]
94
- # @param y [Integer]
95
- def add(x:, y:)
96
- puts x + y
97
- end
98
- ```
99
-
100
- Which is invoked by `bake add x=1 y=2`.
101
-
102
- ## Contributing
103
-
104
- 1. Fork it
105
- 2. Create your feature branch (`git checkout -b my-new-feature`)
106
- 3. Commit your changes (`git commit -am 'Add some feature'`)
107
- 4. Push to the branch (`git push origin my-new-feature`)
108
- 5. Create new Pull Request
109
-
110
- ## See Also
111
-
112
- - [Console](https://github.com/socketry/console) — A logging framework which integrates with bake.
113
- - [Variant](https://github.com/socketry/variant) — A framework for selecting different environments, including bake tasks.
114
- - [Utopia](https://github.com/socketry/utopia) — A website framework which uses bake for maintenance tasks.
115
-
116
- ## License
117
-
118
- Released under the MIT license.
119
-
120
- Copyright, 2020, by [Samuel G. D. Williams](http://www.codeotaku.com).
121
-
122
- Permission is hereby granted, free of charge, to any person obtaining a copy
123
- of this software and associated documentation files (the "Software"), to deal
124
- in the Software without restriction, including without limitation the rights
125
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
126
- copies of the Software, and to permit persons to whom the Software is
127
- furnished to do so, subject to the following conditions:
128
-
129
- The above copyright notice and this permission notice shall be included in
130
- all copies or substantial portions of the Software.
131
-
132
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
133
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
134
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
135
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
136
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
137
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
138
- THE SOFTWARE.
@@ -1,32 +0,0 @@
1
- require_relative 'lib/bake/version'
2
-
3
- Gem::Specification.new do |spec|
4
- spec.name = "bake"
5
- spec.version = Bake::VERSION
6
- spec.authors = ["Samuel Williams"]
7
- spec.email = ["samuel.williams@oriontransfer.co.nz"]
8
-
9
- spec.summary = "A replacement for rake with a simpler syntax."
10
- spec.homepage = "https://github.com/ioquatix/bake"
11
- spec.license = "MIT"
12
- spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
13
-
14
- spec.metadata["donation_uri"] = "https://github.com/sponsors/ioquatix"
15
-
16
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
17
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
- end
19
-
20
- spec.bindir = "bin"
21
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
- spec.require_paths = ["lib"]
23
-
24
- spec.add_dependency 'samovar', '~> 2.1'
25
-
26
- spec.add_development_dependency 'bake-bundler'
27
-
28
- spec.add_development_dependency 'covered'
29
- spec.add_development_dependency 'bundler'
30
- spec.add_development_dependency 'rspec'
31
- spec.add_development_dependency 'rake'
32
- end
data/bake.rb DELETED
File without changes
Binary file
data/gems.rb DELETED
@@ -1,5 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in bake.gemspec
4
- gemspec
5
-