teapot 2.2.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -19,75 +19,77 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require 'samovar'
22
+ require 'console/terminal'
23
+
24
+ require_relative 'selection'
22
25
 
23
26
  module Teapot
24
27
  module Command
25
- class List < Samovar::Command
28
+ class List < Selection
26
29
  self.description = "List provisions and dependencies of the specified package."
27
30
 
28
- many :packages, "Limit the listing to only these packages, or all packages if none specified."
29
-
30
- def only
31
- if @packages.any?
32
- Set.new(@packages)
31
+ def terminal(output = $stdout)
32
+ Console::Terminal.for(output).tap do |terminal|
33
+ terminal[:definition] = terminal.style(nil, nil, :bright)
34
+ terminal[:dependency] = terminal.style(:blue)
35
+ terminal[:provision] = terminal.style(:green)
36
+ terminal[:package] = terminal.style(:yellow)
37
+ terminal[:import] = terminal.style(:cyan)
38
+ terminal[:error] = terminal.style(:red)
33
39
  end
34
40
  end
35
41
 
36
- def invoke(parent)
37
- context = parent.context
42
+ def process(selection)
43
+ context = selection.context
44
+ terminal = self.terminal
38
45
 
39
- logger = parent.logger
40
-
41
- context.configuration.packages.each do |package|
42
- # The root package is the local package for this context:
43
- next unless only == nil or only.include?(package.name)
46
+ selection.resolved.each do |package|
47
+ terminal.puts "Package #{package.name} (from #{package.path}):"
44
48
 
45
- logger.info "Package #{package.name} (from #{package.path}):".bright
46
-
47
49
  begin
48
50
  script = context.load(package)
49
51
  definitions = script.defined
50
52
 
51
53
  definitions.each do |definition|
52
- logger.info "\t#{definition}"
54
+ terminal.puts "\t#{definition}", style: :definition
53
55
 
54
56
  definition.description.each_line do |line|
55
- logger.info "\t\t#{line.chomp}".color(:cyan)
57
+ terminal.puts "\t\t#{line.chomp}", style: :description
56
58
  end if definition.description
57
59
 
58
60
  case definition
59
61
  when Project
60
- logger.info "\t\t- Summary: #{definition.summary}" if definition.summary
61
- logger.info "\t\t- License: #{definition.license}" if definition.license
62
- logger.info "\t\t- Website: #{definition.website}" if definition.website
63
- logger.info "\t\t- Version: #{definition.version}" if definition.version
62
+ terminal.puts "\t\t- Summary: #{definition.summary}" if definition.summary
63
+ terminal.puts "\t\t- License: #{definition.license}" if definition.license
64
+ terminal.puts "\t\t- Website: #{definition.website}" if definition.website
65
+ terminal.puts "\t\t- Version: #{definition.version}" if definition.version
64
66
 
65
67
  definition.authors.each do |author|
66
- contact_text = [author.email, author.website].compact.collect{|text|" <#{text}>"}.join
67
- logger.info "\t\t- Author: #{author.name}" + contact_text
68
+ contact_text = [author.email, author.website].compact.collect{|text| " <#{text}>"}.join
69
+ terminal.puts "\t\t- Author: #{author.name}" + contact_text
68
70
  end
69
71
  when Target
70
72
  definition.dependencies.each do |dependency|
71
- logger.info "\t\t- #{dependency}".color(:red)
73
+ terminal.puts "\t\t- #{dependency}", style: :dependency
72
74
  end
73
75
 
74
76
  definition.provisions.each do |name, provision|
75
- logger.info "\t\t- #{provision}".color(:green)
77
+ terminal.puts "\t\t- #{provision}", style: :provision
76
78
  end
77
79
  when Configuration
78
80
  definition.packages.each do |package|
79
- logger.info "\t\t- #{package}".color(:green)
81
+ terminal.puts "\t\t- #{package}", style: :package
80
82
  end
81
83
 
82
84
  definition.imports.select(&:explicit).each do |import|
83
- logger.info "\t\t- import #{import.name}".color(:red)
85
+ terminal.puts "\t\t- import #{import.name}", style: :import
84
86
  end
85
87
  end
86
88
  end
87
- rescue NonexistantTeapotError => error
88
- logger.info "\t#{error.message}".color(:red)
89
+ rescue MissingTeapotError => error
90
+ terminal.puts "\t#{error.message}", style: :error
89
91
  rescue IncompatibleTeapotError => error
90
- logger.info "\t#{error.message}".color(:red)
92
+ terminal.puts "\t#{error.message}", style: :error
91
93
  end
92
94
  end
93
95
  end
@@ -1,15 +1,15 @@
1
- # Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
5
5
  # in the Software without restriction, including without limitation the rights
6
6
  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
7
  # copies of the Software, and to permit persons to whom the Software is
8
8
  # furnished to do so, subject to the following conditions:
9
- #
9
+ #
10
10
  # The above copyright notice and this permission notice shall be included in
11
11
  # all copies or substantial portions of the Software.
12
- #
12
+ #
13
13
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
14
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
15
  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -18,61 +18,34 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'forwardable'
21
+ require 'samovar'
22
22
 
23
23
  module Teapot
24
- # Very similar to a set but uses a specific callback (defaults to &:name) for object identity.
25
- class IdentitySet
26
- include Enumerable
27
-
28
- def initialize(contents = [])
29
- @table = {}
24
+ module Command
25
+ class Selection < Samovar::Command
26
+ options
30
27
 
31
- contents.each do |object|
32
- add(object)
28
+ many :targets, "Only consider the specified targets, if any."
29
+
30
+ def targets
31
+ if @targets and @targets.any?
32
+ Set.new(@targets)
33
+ end
33
34
  end
34
- end
35
-
36
- attr :table
37
-
38
- def freeze
39
- @table.freeze
40
35
 
41
- super
42
- end
43
-
44
- def initialize_dup(other)
45
- @table = other.table.dup
46
- end
47
-
48
- def identity(object)
49
- object.name
50
- end
51
-
52
- def add(object)
53
- @table[identity(object)] = object
54
- end
55
-
56
- alias << add
57
-
58
- def delete(object)
59
- @table.delete(identity(object))
60
- end
61
-
62
- def include?(object)
63
- @table.include?(identity(object))
64
- end
65
-
66
- def each(&block)
67
- @table.each_value(&block)
68
- end
69
-
70
- def slice(names)
71
- names.collect{|name| @table[name]}
36
+ def selection(context)
37
+ if targets = self.targets
38
+ context.select(targets)
39
+ else
40
+ context.select(context.configuration[:build])
41
+ end
42
+ end
43
+
44
+ def invoke
45
+ context = parent.context
46
+
47
+ self.process(selection(parent.context))
48
+ end
72
49
  end
73
-
74
- extend Forwardable
75
-
76
- def_delegators :@table, :size, :empty?, :clear, :count, :[], :to_s, :inspect
77
50
  end
78
51
  end
@@ -18,30 +18,28 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'samovar'
21
+ require_relative 'selection'
22
22
  require 'rugged'
23
+ require 'console/terminal'
23
24
 
24
25
  module Teapot
25
26
  module Command
26
- class Status < Samovar::Command
27
+ class Status < Selection
27
28
  self.description = "List the git status of the specified package(s)."
28
29
 
29
- many :packages, "Limit the listing to only these packages, or all packages if none specified."
30
-
31
- def only
32
- if @packages.any?
33
- Set.new(@packages)
30
+ def terminal(output = $stdout)
31
+ Console::Terminal.for(output).tap do |terminal|
32
+ terminal[:worktree_new] = terminal.style(:green)
33
+ terminal[:worktree_modified] = terminal.style(:yellow)
34
+ terminal[:worktree_deleted] = terminal.style(:red)
34
35
  end
35
36
  end
36
37
 
37
- def invoke(parent)
38
- context = parent.context
39
- logger = parent.logger
38
+ def process(selection)
39
+ context = selection.context
40
+ terminal = self.terminal
40
41
 
41
- context.configuration.packages.each do |package|
42
- # The root package is the local package for this context:
43
- next unless only == nil or only.include?(package.name)
44
-
42
+ selection.resolved.each do |package|
45
43
  repository = Rugged::Repository.new(package.path.to_s)
46
44
 
47
45
  changes = {}
@@ -53,18 +51,10 @@ module Teapot
53
51
 
54
52
  next if changes.empty?
55
53
 
56
- logger.info "Package #{package.name} (from #{package.path}):".bright
54
+ terminal.puts "Package #{package.name} (from #{package.path}):"
57
55
 
58
- changes.each do |file, status|
59
- if status == [:worktree_new]
60
- logger.info "\t#{file}".color(:blue)
61
- elsif status == [:worktree_modified]
62
- logger.info "\t#{file}".color(:orange)
63
- elsif status == [:worktree_deleted]
64
- logger.info "\t#{file}".color(:red)
65
- else
66
- logger.info "\t#{file} #{status.inspect}"
67
- end
56
+ changes.each do |file, statuses|
57
+ terminal.puts "\t#{file} (#{statuses})", style: statuses.last
68
58
  end
69
59
  end
70
60
  end
@@ -18,12 +18,12 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'samovar'
21
+ require_relative 'selection'
22
22
  require 'graphviz'
23
23
 
24
24
  module Teapot
25
25
  module Command
26
- class Visualize < Samovar::Command
26
+ class Visualize < Selection
27
27
  self.description = "Generate a picture of the dependency graph."
28
28
 
29
29
  options do
@@ -31,8 +31,6 @@ module Teapot
31
31
  option '-d/--dependency-name <name>', "Show the partial chain for the given named dependency."
32
32
  end
33
33
 
34
- many :targets, "Visualize these targets, or use them to help the dependency resolution process."
35
-
36
34
  def dependency_names
37
35
  @targets || []
38
36
  end
@@ -41,15 +39,13 @@ module Teapot
41
39
  @options[:dependency_name]
42
40
  end
43
41
 
44
- def invoke(parent)
45
- context = parent.context
46
- selection = context.select(dependency_names)
42
+ def process(selection)
43
+ context = selection.context
47
44
  chain = selection.chain
48
45
 
49
46
  if dependency_name
50
47
  provider = selection.dependencies[dependency_name]
51
48
 
52
- # TODO The visualisation generated isn't quite right. It's introspecting too much from the packages and not reflecting #ordered and #provisions.
53
49
  chain = chain.partial(provider)
54
50
  end
55
51
 
@@ -58,7 +54,9 @@ module Teapot
58
54
  graph = visualization.generate(chain)
59
55
 
60
56
  if output_path = @options[:output_path]
61
- Graphviz::output(graph, :path => output_path)
57
+ Graphviz.output(graph, path: output_path, format: :svg)
58
+ else
59
+ $stdout.puts graph.to_dot
62
60
  end
63
61
 
64
62
  return graph
@@ -23,7 +23,7 @@ require 'set'
23
23
 
24
24
  require 'yaml/store'
25
25
 
26
- require_relative 'identity_set'
26
+ require 'build/dependency/set'
27
27
  require_relative 'definition'
28
28
 
29
29
  module Teapot
@@ -40,8 +40,8 @@ module Teapot
40
40
 
41
41
  @options = DEFAULT_OPTIONS.merge(options)
42
42
 
43
- @packages = IdentitySet.new(packages)
44
- @imports = IdentitySet.new
43
+ @packages = Build::Dependency::Set.new(packages)
44
+ @imports = Build::Dependency::Set.new
45
45
 
46
46
  @visibility = :private
47
47
 
@@ -50,7 +50,7 @@ module Teapot
50
50
  end
51
51
 
52
52
  def freeze
53
- return if frozen?
53
+ return self if frozen?
54
54
 
55
55
  @options.freeze
56
56
  @packages.freeze
@@ -63,6 +63,15 @@ module Teapot
63
63
  super
64
64
  end
65
65
 
66
+ def environment
67
+ configuration = self
68
+
69
+ Build::Environment.new(name: self.name) do
70
+ default build_path configuration.build_path
71
+ default platforms_path configuration.build_path
72
+ end
73
+ end
74
+
66
75
  # Controls how the configuration is exposed in the context.
67
76
  attr :visibility
68
77
 
@@ -128,11 +137,13 @@ module Teapot
128
137
  context.root + "teapot/packages/#{name}"
129
138
  end
130
139
 
131
- # The path where built products will be installed.
132
- def platforms_path
133
- context.root + "teapot/platforms/#{name}"
140
+ # The path where built products will be placed.
141
+ def build_path
142
+ context.root + "teapot/build/#{name}"
134
143
  end
135
144
 
145
+ alias platforms_path build_path
146
+
136
147
  def lock_path
137
148
  context.root + "#{@name}-lock.yml"
138
149
  end
@@ -146,7 +157,7 @@ module Teapot
146
157
  end
147
158
 
148
159
  # Process all import directives and return a new configuration based on the current configuration. Import directives bring packages and other import directives from the specififed configuration definition.
149
- def traverse(configurations, imported = IdentitySet.new, &block)
160
+ def traverse(configurations, imported = Build::Dependency::Set.new, &block)
150
161
  yield self # Whatever happens here, should ensure that...
151
162
 
152
163
  @imports.each do |import|
@@ -18,135 +18,9 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require_relative 'loader'
22
- require_relative 'package'
23
-
24
- require 'build/rulebook'
25
- require 'build/text/substitutions'
26
- require 'build/text/merge'
21
+ require_relative 'select'
27
22
 
28
23
  module Teapot
29
- class AlreadyDefinedError < StandardError
30
- def initialize(definition, previous)
31
- super "Definition #{definition.name} in #{definition.path} has already been defined in #{previous.path}!"
32
- end
33
-
34
- def self.check(definition, definitions)
35
- previous = definitions[definition.name]
36
-
37
- raise self.new(definition, previous) if previous
38
- end
39
- end
40
-
41
- # A selection is a specific view of the data exposed by the context at a specific point in time.
42
- class Select
43
- def initialize(context, configuration, names = [])
44
- @context = context
45
- @configuration = Configuration.new(context, configuration.package, configuration.name, [], configuration.options)
46
-
47
- @targets = {}
48
- @configurations = {}
49
- @projects = {}
50
- @rules = Build::Rulebook.new
51
-
52
- @dependencies = []
53
- @selection = Set.new
54
- @unresolved = Set.new
55
-
56
- load!(configuration, names)
57
-
58
- @chain = nil
59
- end
60
-
61
- attr :context
62
- attr :configuration
63
-
64
- attr :targets
65
- attr :projects
66
-
67
- # Alises as defined by Configuration#targets
68
- attr :aliases
69
-
70
- # All public configurations.
71
- attr :configurations
72
-
73
- attr :rules
74
-
75
- attr :dependencies
76
- attr :selection
77
- attr :unresolved
78
-
79
- def chain
80
- @chain ||= Build::Dependency::Chain.expand(@dependencies, @targets.values, @selection)
81
- end
82
-
83
- def direct_targets(ordered)
84
- @dependencies.collect do |dependency|
85
- ordered.find{|(package, _)| package.provides? dependency}
86
- end.compact
87
- end
88
-
89
- private
90
-
91
- # Add a definition to the current context.
92
- def append definition
93
- case definition
94
- when Target
95
- AlreadyDefinedError.check(definition, @targets)
96
- @targets[definition.name] = definition
97
- when Configuration
98
- # We define configurations in two cases, if they are public, or if they are part of the root package of this context.
99
- if definition.public? or definition.package == @context.root_package
100
- AlreadyDefinedError.check(definition, @configurations)
101
- @configurations[definition.name] = definition
102
- end
103
- when Project
104
- AlreadyDefinedError.check(definition, @projects)
105
- @projects[definition.name] = definition
106
- when Rule
107
- AlreadyDefinedError.check(definition, @rules)
108
- @rules << definition
109
- end
110
- end
111
-
112
- def load_package!(package)
113
- begin
114
- script = @context.load(package)
115
-
116
- # Load the definitions into the current selection:
117
- script.defined.each do |definition|
118
- append(definition)
119
- end
120
- rescue NonexistantTeapotError, IncompatibleTeapotError
121
- # If the package doesn't exist or the teapot version is too old, it failed:
122
- @unresolved << package
123
- end
124
- end
125
-
126
- def load!(configuration, names)
127
- # Load the root package which makes all the named configurations and targets available.
128
- load_package!(@context.root_package)
129
-
130
- # Load all packages defined by this configuration.
131
- configuration.traverse(@configurations) do |configuration|
132
- @configuration.merge(configuration) do |package|
133
- # puts "Load package: #{package} from #{configuration}"
134
- load_package!(package)
135
- end
136
- end
137
-
138
- @configuration.freeze
139
-
140
- names.each do |name|
141
- if @targets.key? name
142
- @selection << name
143
- else
144
- @dependencies << name
145
- end
146
- end
147
- end
148
- end
149
-
150
24
  # A context represents a specific root package instance with a given configuration and all related definitions. A context is stateful in the sense that package selection is specialized based on #select and #dependency_chain. These parameters are usually set up initially as part of the context setup.
151
25
  class Context
152
26
  def initialize(root, **options)
@@ -158,7 +32,7 @@ module Teapot
158
32
 
159
33
  @loaded = {}
160
34
 
161
- load_root_package(options) unless options[:load_root] == false
35
+ load_root_package(**options)
162
36
  end
163
37
 
164
38
  attr :root
@@ -174,8 +48,8 @@ module Teapot
174
48
  @repository ||= Rugged::Repository.new(@root.to_s)
175
49
  end
176
50
 
177
- def select(names = [], configuration = @configuration)
178
- Select.new(self, configuration, names)
51
+ def select(names = nil, configuration = @configuration)
52
+ Select.new(self, configuration, names || [])
179
53
  end
180
54
 
181
55
  def substitutions
@@ -195,6 +69,9 @@ module Teapot
195
69
  # e.g. foo-bar, typically used for targets, executables
196
70
  substitutions['PROJECT_TARGET_NAME'] = name.target
197
71
 
72
+ # e.g. foo_bar, typically used for variables.
73
+ substitutions['PROJECT_VARIABLE_NAME'] = name.key
74
+
198
75
  substitutions['LICENSE'] = @project.license
199
76
  end
200
77
 
@@ -228,13 +105,13 @@ module Teapot
228
105
 
229
106
  private
230
107
 
231
- def load_root_package(options)
108
+ def load_root_package(**options)
232
109
  # Load the root package:
233
110
  script = load(root_package)
234
-
111
+
235
112
  # Find the default configuration, if it exists:
236
113
  if configuration_name = options[:configuration]
237
- @configuration = @configurations[configuration_name]
114
+ @configuration = script.configurations[configuration_name]
238
115
  else
239
116
  @configuration = script.default_configuration
240
117
  end