bake 0.11.0 → 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)