bake 0.11.0 → 0.14.1

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.
@@ -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
 
@@ -23,8 +23,11 @@ require 'set'
23
23
 
24
24
  module Bake
25
25
  module Command
26
+ # List all available commands.
26
27
  class List < Samovar::Command
27
- PARAMETER = /@param\s+(?<name>.*?)\s+\[(?<type>.*?)\]\s+(?<details>.*?)\Z/
28
+ self.description = "List all available commands."
29
+
30
+ one :pattern, "The pattern to filter tasks by."
28
31
 
29
32
  def format_parameters(parameters, terminal)
30
33
  parameters.each do |type, name|
@@ -52,25 +55,39 @@ module Bake
52
55
  end
53
56
  end
54
57
 
55
- def print_scope(terminal, scope)
58
+ def print_scope(terminal, scope, printed: false)
56
59
  format_recipe = self.method(:format_recipe).curry
57
60
 
58
61
  scope.recipes.sort.each do |recipe|
62
+ if @pattern and !recipe.command.include?(pattern)
63
+ next
64
+ end
65
+
66
+ unless printed
67
+ yield
68
+
69
+ printed = true
70
+ end
71
+
59
72
  terminal.print_line
60
73
  terminal.print_line("\t", format_recipe[recipe])
61
74
 
62
- recipe.description.each do |line|
63
- if match = line.match(PARAMETER)
64
- terminal.print_line("\t\t",
65
- :parameter, match[:name], :reset, " [",
66
- :type, match[:type], :reset, "] ",
67
- :description, match[:details]
68
- )
69
- else
70
- terminal.print_line("\t\t", :description, line)
71
- end
75
+ documentation = recipe.documentation
76
+
77
+ documentation.description do |line|
78
+ terminal.print_line("\t\t", :description, line)
79
+ end
80
+
81
+ documentation.parameters do |parameter|
82
+ terminal.print_line("\t\t",
83
+ :parameter, parameter[:name], :reset, " [",
84
+ :type, parameter[:type], :reset, "] ",
85
+ :description, parameter[:details]
86
+ )
72
87
  end
73
88
  end
89
+
90
+ return printed
74
91
  end
75
92
 
76
93
  def call
@@ -79,23 +96,30 @@ module Bake
79
96
  context = @parent.context
80
97
 
81
98
  if scope = context.scope
82
- terminal.print_line(:context, context)
83
-
84
- print_scope(terminal, context.scope)
99
+ printed = print_scope(terminal, context.scope) do
100
+ terminal.print_line(:context, context)
101
+ end
85
102
 
86
- terminal.print_line
103
+ if printed
104
+ terminal.print_line
105
+ end
87
106
  end
88
107
 
89
108
  context.loaders.each do |loader|
90
- terminal.print_line(:loader, loader)
109
+ printed = false
91
110
 
92
111
  loader.each do |path|
93
112
  if scope = loader.scope_for(path)
94
- print_scope(terminal, scope)
113
+ print_scope(terminal, scope, printed: printed) do
114
+ terminal.print_line(:loader, loader)
115
+ printed = true
116
+ end
95
117
  end
96
118
  end
97
119
 
98
- terminal.print_line
120
+ if printed
121
+ terminal.print_line
122
+ end
99
123
  end
100
124
  end
101
125
  end
@@ -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
 
@@ -0,0 +1,100 @@
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 'scope'
22
+
23
+ module Bake
24
+ # Structured access to a set of comment lines.
25
+ class Documentation
26
+ # Initialize the documentation with an array of comments.
27
+ #
28
+ # @parameter comments [Array(String)] An array of comment lines.
29
+ def initialize(comments)
30
+ @comments = comments
31
+ end
32
+
33
+ DESCRIPTION = /\A\s*([^@\s].*)?\z/
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.
40
+ def description
41
+ return to_enum(:description) unless block_given?
42
+
43
+ # We track empty lines and only yield a single empty line when there is another line of text:
44
+ gap = false
45
+
46
+ @comments.each do |comment|
47
+ if match = comment.match(DESCRIPTION)
48
+ if match[1]
49
+ if gap
50
+ yield ""
51
+ gap = false
52
+ end
53
+
54
+ yield match[1]
55
+ else
56
+ gap = true
57
+ end
58
+ else
59
+ break
60
+ end
61
+ end
62
+ end
63
+
64
+ ATTRIBUTE = /\A\s*@(?<name>.*?)\s+(?<value>.*?)\z/
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.
72
+ def attributes
73
+ return to_enum(:attributes) unless block_given?
74
+
75
+ @comments.each do |comment|
76
+ if match = comment.match(ATTRIBUTE)
77
+ yield match
78
+ end
79
+ end
80
+ end
81
+
82
+ PARAMETER = /\A\s*@param(eter)?\s+(?<name>.*?)\s+\[(?<type>.*?)\](\s+(?<details>.*?))?\z/
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.
90
+ def parameters
91
+ return to_enum(:parameters) unless block_given?
92
+
93
+ @comments.each do |comment|
94
+ if match = comment.match(PARAMETER)
95
+ yield match
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -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)