loom-core 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +31 -0
  3. data/Gemfile.lock +19 -1
  4. data/Rakefile +2 -0
  5. data/bin/loom +3 -0
  6. data/docs/architecture.jpg +0 -0
  7. data/gentags.sh +2 -0
  8. data/lib/loom/all.rb +1 -1
  9. data/lib/loom/config.rb +1 -1
  10. data/lib/loom/mods/mod_loader.rb +7 -2
  11. data/lib/loom/pattern/all.rb +1 -0
  12. data/lib/loom/pattern/definition_context.rb +4 -4
  13. data/lib/loom/pattern/dsl.rb +176 -119
  14. data/lib/loom/pattern/expanding_reference.rb +88 -6
  15. data/lib/loom/pattern/loader.rb +1 -17
  16. data/lib/loom/pattern/pattern.rb +52 -0
  17. data/lib/loom/pattern/reference.rb +17 -13
  18. data/lib/loom/pattern/reference_set.rb +71 -50
  19. data/lib/loom/runner.rb +46 -33
  20. data/lib/loom/runner/all.rb +2 -0
  21. data/lib/loom/runner/execution_context.rb +9 -0
  22. data/lib/loom/{dsl.rb → runner/sshkit_connector.rb} +5 -7
  23. data/lib/loom/shell.rb +4 -2
  24. data/lib/loom/shell/core.rb +16 -16
  25. data/lib/loom/version.rb +1 -1
  26. data/lib/loomext/coremods/exec.rb +1 -0
  27. data/loom.TAGS +797 -0
  28. data/loom.gemspec +1 -0
  29. data/spec/.loom/error_handling.loom +1 -0
  30. data/spec/.loom/fail.loom +27 -13
  31. data/spec/.loom/files.loom +1 -0
  32. data/spec/.loom/inventory.yml +3 -0
  33. data/spec/.loom/net.loom +1 -0
  34. data/spec/.loom/pattern_context.loom +1 -0
  35. data/spec/.loom/pkg.loom +1 -0
  36. data/spec/.loom/shell.loom +1 -0
  37. data/spec/.loom/test.loom +17 -4
  38. data/spec/.loom/user.loom +1 -0
  39. data/spec/.loom/vms.loom +1 -0
  40. data/spec/loom/pattern/dsl_spec.rb +3 -2
  41. data/spec/shared/loom_internals_helper.rb +1 -1
  42. data/spec/test_loom_spec.rb +102 -42
  43. data/test +15 -0
  44. metadata +25 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11bd9487c4e4752e294000493fe1fe04537f97bfcea9385b1cf16e90c2c79a71
4
- data.tar.gz: 926227cb27d65b64103dd0f541acb1f523afdfac2df832180450f1aaaff4397f
3
+ metadata.gz: 8c95f594a71ab9a9a776b8f83cceb04224d17d7527546237f019f018971be6ad
4
+ data.tar.gz: d105521e8666b8bc50b7fd504e13534c02d3c216f1d99bdf72ef484602a54efb
5
5
  SHA512:
6
- metadata.gz: 687255010d7a4c709cb95b2022151a95175ae88decb368cfac503dd3bfdb24c677abdbd2ef055f16a80a783bad2d24762ab856bf20c085ec08d1f51734c78c01
7
- data.tar.gz: b789683f5aefd5a1997536e3f930f143bb7c12fc24be4e40d10fb0147e0c997c5af84798fad9e2da79d3cdbb7f5fe4a854d3f981f6deabe43ff08ab0092e3c68
6
+ metadata.gz: 71bce721c3d6168173522d90e56988cf415daef5bf6dcb97033a4fac8d9fa9f7ae9ae422c27b2dcd31027de60f3935c02f8cd12926dffd251272698ffbfa5314
7
+ data.tar.gz: 3196d487c91a75cb327b4b2050fbdbce95c5c1e1ea0f8890429999e16baf6a5349f127b0579eea362395ffeda7a19ebac5bb03fe05d5fab08e332c29576161ab
@@ -0,0 +1,31 @@
1
+ # https://rubocop.readthedocs.io/en/latest/cops_layout/
2
+ Layout/EmptyLineAfterGuardClause:
3
+ Enabled: false
4
+ Layout/EmptyLinesAroundModuleBody:
5
+ Enabled: false
6
+ Layout/EmptyLinesAroundClassBody:
7
+ Enabled: false
8
+ Layout/MultilineMethodCallBrace:
9
+ Enabled: false
10
+ Layout/MultilineMethodCallIndentation:
11
+ EnforcedStyle: indented
12
+
13
+ Lint/BooleanSymbol:
14
+ Enabled: false
15
+
16
+ # https://rubocop.readthedocs.io/en/latest/cops_metrics/
17
+ Metrics/LineLength:
18
+ Max: 100
19
+
20
+ Naming/HeredocDelimiterNaming:
21
+ Enable: false
22
+
23
+ # https://rubocop.readthedocs.io/en/latest/cops_style/
24
+ Style/Alias:
25
+ Enabled: false
26
+ Style/ClassAndModuleChildren:
27
+ Enabled: false
28
+ Style/Encoding:
29
+ Enabled: false
30
+ Sylte/IfUnlessModifier:
31
+ Enabled: false
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- loom-core (0.0.6)
4
+ loom-core (0.0.7)
5
5
  bcrypt_pbkdf (>= 1.0, < 2.0)
6
6
  commander (~> 4.4)
7
7
  ed25519 (>= 1.0, < 2.0)
@@ -12,6 +12,7 @@ PATH
12
12
  GEM
13
13
  remote: https://rubygems.org/
14
14
  specs:
15
+ ast (2.4.0)
15
16
  bcrypt_pbkdf (1.0.0)
16
17
  byebug (9.0.6)
17
18
  coderay (1.1.1)
@@ -36,6 +37,7 @@ GEM
36
37
  guard-compat (~> 1.1)
37
38
  rspec (>= 2.99.0, < 4.0)
38
39
  highline (1.7.10)
40
+ jaro_winkler (1.5.1)
39
41
  listen (3.1.5)
40
42
  rb-fsevent (~> 0.9, >= 0.9.4)
41
43
  rb-inotify (~> 0.9, >= 0.9.7)
@@ -49,6 +51,10 @@ GEM
49
51
  notiffany (0.1.1)
50
52
  nenv (~> 0.1)
51
53
  shellany (~> 0.0)
54
+ parallel (1.12.1)
55
+ parser (2.5.1.2)
56
+ ast (~> 2.4.0)
57
+ powerpack (0.1.2)
52
58
  pry (0.10.4)
53
59
  coderay (~> 1.1.0)
54
60
  method_source (~> 0.8.1)
@@ -56,6 +62,7 @@ GEM
56
62
  pry-byebug (3.4.0)
57
63
  byebug (~> 9.0)
58
64
  pry (~> 0.10)
65
+ rainbow (3.0.0)
59
66
  rake (11.3.0)
60
67
  rb-fsevent (0.9.8)
61
68
  rb-inotify (0.9.7)
@@ -77,6 +84,15 @@ GEM
77
84
  diff-lcs (>= 1.2.0, < 2.0)
78
85
  rspec-support (~> 3.5.0)
79
86
  rspec-support (3.5.0)
87
+ rubocop (0.60.0)
88
+ jaro_winkler (~> 1.5.1)
89
+ parallel (~> 1.10)
90
+ parser (>= 2.5, != 2.5.1.1)
91
+ powerpack (~> 0.1)
92
+ rainbow (>= 2.2.2, < 4.0)
93
+ ruby-progressbar (~> 1.7)
94
+ unicode-display_width (~> 1.4.0)
95
+ ruby-progressbar (1.10.0)
80
96
  ruby_dep (1.5.0)
81
97
  shellany (0.0.1)
82
98
  slop (3.6.0)
@@ -84,6 +100,7 @@ GEM
84
100
  net-scp (>= 1.1.2)
85
101
  net-ssh (>= 2.8.0)
86
102
  thor (0.19.1)
103
+ unicode-display_width (1.4.0)
87
104
 
88
105
  PLATFORMS
89
106
  ruby
@@ -96,6 +113,7 @@ DEPENDENCIES
96
113
  pry-byebug
97
114
  rake (~> 11.3)
98
115
  rspec (~> 3.5)
116
+ rubocop
99
117
 
100
118
  BUNDLED WITH
101
119
  1.16.2
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # TODO: I can replace this with Loom now.
2
+ # TODO: Make a Loom mod that converts Rake Tasks into Loom Patterns.
1
3
  require "bundler/gem_tasks"
2
4
  require "rspec/core/rake_task"
3
5
 
data/bin/loom CHANGED
@@ -158,6 +158,9 @@ EOS
158
158
  end
159
159
  end
160
160
  end
161
+ # TODO: p and patterns produce different output on failure, p is a
162
+ # truncated stack. Figure out why, and fix that. This is going to be
163
+ # somewhere in Commander.
161
164
  alias_command :"p", :"patterns"
162
165
 
163
166
  command :"config" do |c|
Binary file
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ find lib -name \*.rb | etags -l ruby --output=loom.TAGS -
@@ -9,12 +9,12 @@ require_relative "config"
9
9
 
10
10
  require_relative "shell"
11
11
  require_relative "host_spec"
12
- require_relative "dsl"
13
12
 
14
13
  require_relative "inventory"
15
14
  require_relative "facts"
16
15
  require_relative "pattern"
17
16
  require_relative "mods"
17
+ require_relative "runner/all"
18
18
  require_relative "runner"
19
19
 
20
20
  require_relative "version"
@@ -77,7 +77,7 @@ module Loom
77
77
  class FileManager
78
78
 
79
79
  def initialize(config)
80
- @loom_search_paths = config.loom_search_paths
80
+ @loom_search_paths = [config.loom_search_paths].flatten
81
81
  @loom_files = config.loom_files
82
82
  @loom_file_patterns = config.loom_file_patterns
83
83
  end
@@ -10,7 +10,12 @@ module Loom::Mods
10
10
  @loom_config = loom_config
11
11
  end
12
12
 
13
- def verify_shell_cmds(shell, mod_klass)
13
+ def load_mod_klass(mod_klass, shell)
14
+ verify_shell_cmds mod_klass, shell
15
+ end
16
+
17
+ private
18
+ def verify_shell_cmds(mod_klass, shell)
14
19
  Loom.log.debug2(self) { "verifying cmds for mod => #{mod_klass}" }
15
20
  mod_klass.required_commands.each do |cmd|
16
21
  begin
@@ -53,7 +58,7 @@ module Loom::Mods
53
58
  Loom.log.debug3(self) do
54
59
  "handling mod call => #{mod_klass}##{name} #{args} #{pattern_block}"
55
60
  end
56
- verify_shell_cmds shell, mod_klass
61
+ load_mod_klass mod_klass, shell
57
62
 
58
63
  mod = mod_klass.new shell, @loom_config
59
64
  mod.execute *args, &pattern_block
@@ -1,6 +1,7 @@
1
1
  require_relative "dsl"
2
2
  require_relative "hook"
3
3
  require_relative "definition_context"
4
+ require_relative "pattern"
4
5
  require_relative "reference"
5
6
  require_relative "expanding_reference"
6
7
  require_relative "reference_set"
@@ -8,11 +8,11 @@ module Loom::Pattern
8
8
 
9
9
  NilLetValueError = Class.new Loom::LoomError
10
10
 
11
- def initialize(pattern_module, parent_context=nil)
12
- @fact_map = pattern_module.facts.dup
13
- @let_map = pattern_module.let_map.dup
11
+ def initialize(dsl_builder, parent_context=nil)
12
+ @fact_map = dsl_builder.facts.dup
13
+ @let_map = dsl_builder.let_map.dup
14
14
 
15
- @hooks = pattern_module.hooks.dup
15
+ @hooks = dsl_builder.hooks.dup
16
16
  @parent_context = parent_context
17
17
 
18
18
  @merged_fact_map = merged_fact_map
@@ -1,13 +1,53 @@
1
+ # IN Progress:
2
+ # [wip-patternbuilder]
3
+ # * Add a phase to the pattern execution sequence to collect calls to factset and loom
4
+ # objects. Results collected from this ""pre-execute"" can be analyzed for errors, optimization,
5
+ # success assertions, verification, etc. The loom file then executes in 2 passes, analyze &
6
+ # execute.
7
+ # -- pre-fact collection - inject the facts (or loom) object as a recorder
8
+ # -- (like a mock in record mode) # instead of the factual fact set. no need
9
+ # -- to change any loom files.
10
+ # -- only run fact providers which are accessed in the pattern set
11
+ # -- only load modules accessed in the pattern set
12
+ #
13
+ # * Replace Pattern "mods" and "mod specs" in 80 Loom::Pattern::ReferenceSet
14
+ # with usages of a builder instead. Currently the internal data model in
15
+ # ReferenceSet is confusing, but luckily it's the only client of pattern
16
+ # modules, that have used Loom::Pattern::DSL. Change calls to
17
+ # DSL#pattern/report/weave (anythin else that creates a pattern) to add a new
18
+ # PatternBuilder to the module. Use the builder to implement the TODO above
19
+ # ("Add a phase..."). Implement analysis on the builder.
20
+ # [master]
21
+ # * ... ongoing ... ways to test and +verify+ pattern execution
22
+
1
23
  # TODO: DSL extensions:
2
- # - a way to test and verify pattern execution.... I still don't trust this
3
- # enough. this starts with fixing error reporting.
24
+ # - More Mods! .... ondeck:
25
+ # * bkblz
26
+ # * cassandra
27
+ # * apache (nginx?)
28
+ # * digital ocean
29
+ # * systemd-nspawj
30
+
31
+ # - Models Future:
32
+ # Notice each ondeck module above (bkblz, cassanadra, apache, digital ocean,
33
+ # system-nspawn): covers a unique infrastructure area, i.e.: bkblz:object and
34
+ # cold storage, cassandra:hyper-scale data storage, apache/nginx:content
35
+ # serving, digital-ocean/aws/gcp:remote virtual hosting, containers:local
36
+ # virtual hosting
37
+ # I could generalize each of the above infrastructure area into a high level
38
+ # mod. Should I? Maybe not.
39
+
40
+ # - auto mod documentation in the CLI
41
+
4
42
  # - Pattern+non_idempotent+ marks a pattern as explicitly not idempotent, this
5
43
  # let's additional warnings and checks to be added
44
+
6
45
  # - A history module, store a log of each executed command, a hash of the .loom
7
46
  # file, and the requisite facts (the let declarations) for each executed pattern
8
47
  # on the host it executes. /var/log/loom/history? Create this log on startup.
9
48
  # -- add a new set of history commands through the CLI and a history
10
49
  # FactProvider exposing host/loom/pattern_slug execution stats.
50
+
11
51
  # - Provide automatic command reversion support with a =Module= DSL that ties in
12
52
  # with local revision history.
13
53
  # -- allow Module actions/mods to define an "undo" command of itself given the
@@ -24,6 +64,7 @@
24
64
  # accesses to fact_set be done in let expressions (enforce this maybe?)
25
65
  # -- Later... before/after hooks can ensure the entire loom execution sequence
26
66
  # was "revertable"
67
+
27
68
  # - A mechanism to allow mods to register CLI flags and actions. Using an action predicate
28
69
  # mechanisms can add flags at the global or action levels. All CLI flags set config values.
29
70
  # -- Mods can also register for action namespaces similar to git. This is consistent with mod
@@ -31,33 +72,20 @@
31
72
  # -- Best way is to migrate loom/mods/module and loom/mods/action_proxy into base classes of
32
73
  # themselves. The isolate the shell specific behavior into a subclass of each to preserve the
33
74
  # current behavior. A new "cli" module and "cli" action proxy would enable the implementation.
34
- # - Add a phase to the pattern execution sequence to collect calls to factset and loom
35
- # objects. Results collected from this ""pre-execute"" can be analyzed for errors, optimization,
36
- # success assertions, verification, etc. The loom file then executes in 2 passes, analyze &
37
- # execute.
38
- # -- pre-fact collection - inject the facts (or loom) object as a recorder (like a mock in record mode)
39
- # instead of the factual fact set. no need to change any loom files.
40
- # -- only run fact providers which are accessed in the pattern set
41
- # -- only load modules accessed in the pattern set
42
- # - Replace Pattern "mods" and "mod specs" in Loom::Pattern::ReferenceSet with usages of a builder
43
- # instead. Currently the internal data model in ReferenceSet is confusing, but luckily it's the
44
- # only client of pattern modules, that have used Loom::Pattern::DSL. Change calls to
45
- # DSL#pattern/report/weave (anythin else that creates a pattern) to add a new PatternBuilder to
46
- # the module. Use the builder to implement the TODO above ("Add a phase..."). Implement analysis
47
- # on the builder.
48
75
 
49
76
  =begin
50
77
 
51
78
  ## .loom File DSL
52
79
 
53
80
  See:
54
- * spec/test.loom for a valid .loom file.
81
+ * spec/.loom/*.loom for a lots of valid .loom files.
82
+ * run these specs with `rspec spec/test_loom_spec.rb`
55
83
  * spec/loom/pattern/dsl_spec.rb for other examples
56
84
 
57
85
 
58
- I've tried to take inspriation from several ruby DSLs, including (but not
59
- limited to) RSpec, Thor, Commander, Sinatra... so hopefully it feels
60
- comfortable.
86
+ I've tried to take inspriation from several ruby DSLs, including RSpec, Thor,
87
+ Commander, Sinatra... so hopefully it feels comfortable. Thank you to all of
88
+ those projects.
61
89
 
62
90
  Loom::Pattern::DSL is the mixin that defines the declarative API for all .loom
63
91
  file defined modules. It is included into Loom::Pattern by default. The outer
@@ -83,15 +111,19 @@ end
83
111
 
84
112
  It declares a reference set with slugs:
85
113
 
86
- * top_level
114
+ * cmd
87
115
  * outer:first
88
116
  * outer:inner:second
89
117
 
90
- Defining the same pattern slug twice raises a DuplicatPatternRef error.
118
+ Defining the same pattern slug twice raises a `DuplicatePatternRef` error.
91
119
 
92
120
  #### Code Details
93
121
 
94
- To follow the code path for .loom file loading see:
122
+ Loom::DSL is a facade available to all .loom file ::Modules that include
123
+ Loom::Pattern. It provides Module singleton methods listed at
124
+ Loom::DSL::DSL_METHODS.
125
+
126
+ Code path for .loom file loading:
95
127
 
96
128
  Loom::Runner#load
97
129
  -> Loom::Pattern::Loader.load
@@ -103,12 +135,48 @@ file. A ReferenseSet being a collection of references with uniquely named
103
135
  slugs. The slug of a reference is computed from the module namespace and
104
136
  instance method name.
105
137
 
138
+ ### `weave`
139
+
140
+ The `weave` creates a specialized pattern, that allows aliasing a sequence of
141
+ pattern slugs as a single pattern name. Pattern execution will be flattened and
142
+ run sequentially before or after any other patterns in the `$ loom` invocation.
143
+
144
+ ``` ~ruby
145
+ pattern :step_1 { ... }
146
+ pattern :step_2 { ... }
147
+
148
+ module OtherTasks
149
+ ...
150
+ end
151
+
152
+ weave :do_it, [ :step_1, :step_2, :other_tasks:* ]
153
+ ```
154
+
155
+ This creates pattern :do_it, which when run `$ loom do_it` will run :step_1,
156
+ :step_2, and all slugs that match /other_tasks.*/. Recursive expansion is
157
+ explicitly disallowed, only pattern names (not weaves), are allowed in the list
158
+ of weave pattern slugs.
159
+
160
+ #### Code Details
161
+
162
+ Weave expansion to pattern slugs is accomplished by creating a
163
+ Loom::Pattern::ExpandingReference via the Loom::Pattern::Loader+load+ path
164
+ invoked via Loom::Runner+load+. Expansion happens on read via
165
+ Loom::Pattern::Loader+patterns+, thus the list of patterns is constant
166
+ throughout all phases of pattern execution.
167
+
106
168
  ### `report`
107
169
 
108
- Use `report` to create a pattern that outputs a fact, other value, or result of
109
- a block to yaml, json, or any other format.
170
+ Use `report` to create another specialized pattern that prints a fact, value,
171
+ or result of a block to yaml, json, or any other format to STDOUT.
110
172
 
111
- ### `let`, `before`, and `after`
173
+ ### `let`, `before`, and `after`: for examples spec/.loom/parent_context.loom
174
+
175
+ `let`, does the same as in RSpec (including the context details). It creates an
176
+ alias for a value, available in all patterns.
177
+
178
+ `before` and `after` are similar to the same in RSpec. Each before/after block
179
+ is run before/after respectively to EACH pattern.
112
180
 
113
181
  Module::Inner, from above, inherits all +let+ declarations from its outer
114
182
  contexts, both :: (root) and ::Outer. +before+ hooks are run from a top-down
@@ -179,31 +247,6 @@ Loom::Facts::FactSet as parameters.
179
247
  See Loom::Pattern::DefinitionContext for evaluation of `let` blocks and
180
248
  before/after context ordering.
181
249
 
182
- ### `weave`
183
-
184
- The `weave` keyword allows aliasing a sequence of patterns a single
185
- name. Pattern execution will be flattened and run sequentially before or after
186
- any other patterns in the `$ loom` invocation.
187
-
188
- ``` ~ruby
189
- pattern :step_1 { ... }
190
- pattern :step_2 { ... }
191
-
192
- weave :do_it, [ :step_1, :step_2 ]
193
- ```
194
-
195
- This creates pattern :do_it, which when run `$ loom do_it` will run :step_1,
196
- :step_2. Recursive expansion is explicitly disallowed, only pattern names (not
197
- weaves), are allowed in the 2nd param of `weave`.
198
-
199
- #### Code Details
200
-
201
- Weave expansion to pattern slugs is accomplished by creating a
202
- Loom::Pattern::ExpandingReference via the Loom::Pattern::Loader+load+ path
203
- invoked via Loom::Runner+load+. Expansion happens on read via
204
- Loom::Pattern::Loader+patterns+, thus the list of patterns is constant
205
- throughout all phases of pattern execution.
206
-
207
250
  #### Pattern Execution Sequence
208
251
 
209
252
  Once hosts and patterns are identified in earlier Loom::Runner phases,
@@ -250,14 +293,55 @@ module Loom::Pattern
250
293
 
251
294
  PatternDefinitionError = Class.new Loom::LoomError
252
295
 
253
- # TODO: clarify this DSL to only export:
254
- # - description
255
- # - pattern
256
- # - let
257
- # - after/before
258
- # other methods are utility methods used to process and run patterns.
296
+ DSL_METHODS = [
297
+ :desc,
298
+ :description,
299
+
300
+ :pattern,
301
+ :weave,
302
+ :report,
303
+
304
+ :with_facts,
305
+ :let,
306
+ :before,
307
+ :after,
308
+
309
+ :namespace
310
+ ]
311
+
312
+ ##
313
+ # The Loom DSL definition. See documentation above.
259
314
  module DSL
315
+ DSL_METHODS.each do |m|
316
+ define_method m do |*args, &block|
317
+ Loom.log.debug1(self) { "delegating Pattern::DSL call to DSLBuilder+#{m}+" }
318
+ @dsl_builder.send(m, *args, &block)
319
+ end
320
+ end
321
+
322
+ attr_reader :dsl_builder
323
+
324
+ class << self
325
+ def extended(receiving_mod)
326
+ # NB: Using Forwardable was awkward here due to the scope of extended, and
327
+ # the scope of where the fordwardable instance variable would live.
328
+ dsl_builder = PatternBuilder.new
329
+ receiving_mod.instance_variable_set :@dsl_builder, dsl_builder
330
+ end
331
+ end
332
+ end
333
+
334
+ class DSL::PatternBuilder
335
+
336
+ def initialize
337
+ @pattern_map = {}
338
+ @fact_map = {}
339
+ @let_map = {}
340
+ @hooks = []
341
+ @next_description = nil
342
+ end
260
343
 
344
+ # BEGIN DSL Implementation
261
345
  loom_accessor :namespace
262
346
 
263
347
  def description(description)
@@ -266,37 +350,33 @@ module Loom::Pattern
266
350
  alias_method :desc, :description
267
351
 
268
352
  def with_facts(**new_facts, &block)
269
- @facts ||= {}
270
- @facts.merge! new_facts
271
- yield_result = yield @facts if block_given?
272
- @facts = yield_result if yield_result.is_a? Hash
353
+ @fact_map.merge! new_facts
354
+ yield_result = yield @fact_map if block_given?
355
+ @fact_map = yield_result if yield_result.is_a? Hash
273
356
  end
274
357
 
275
358
  def let(name, default: nil, &block)
276
359
  raise "malformed let expression: missing block" unless block_given?
277
-
278
- @let_map ||= {}
279
360
  @let_map[name.to_sym] = LetMapEntry.new default, &block
280
361
  end
281
362
 
282
363
  def pattern(name, &block)
283
- Loom.log.debug1(self) { "defining pattern => #{name}" }
284
- define_pattern_internal(name, &block)
364
+ define_pattern_internal(name, kind: :pattern, &block)
285
365
  end
286
366
 
287
367
  ##
288
368
  # @param format[:yaml|:json|:raw] default is :yaml
289
369
  def report(name, format: :yaml, &block)
290
- Loom.log.debug1(self) { "defining reporting pattern => #{name}" }
291
- define_pattern_internal(name) do |loom, facts|
292
- # TODO: I don't like all of this logic in the dsl.
370
+ define_pattern_internal(name, kind: :report) do |loom, facts|
371
+ # TODO: I don't like all of this logic here. It feels like it belongs in
372
+ # a mod.
293
373
  result = if block_given?
294
374
  Loom.log.debug(self) { "report[#{name}] from block" }
295
- self.instance_exec(loom, facts, &block)
375
+ instance_exec(loom, facts, &block)
296
376
  elsif !Loom::Facts.is_empty?(facts[name])
297
377
  Loom.log.debug(self) { "report[#{name}] from facts[#{name}]" }
298
378
  facts[name]
299
- elsif self.respond_to?(name) && !self.send(name).nil?
379
+ elsif respond_to?(name) && !self.send(name).nil?
300
380
  Loom.log.debug(self) { "report[#{name}] from let{#{name}}" }
301
381
  self.send name
302
382
  else
@@ -318,15 +398,10 @@ module Loom::Pattern
318
398
  end
319
399
 
320
400
  def weave(name, pattern_slugs)
321
- Loom.log.debug1(self) { "defining weave => #{name}" }
322
- @weave_slugs ||= {}
323
- @weave_slugs[name.to_sym] = pattern_slugs.map { |s| s.to_s }
324
-
325
401
  unless @next_description
326
402
  @next_description = "Weave runs patterns: %s" % pattern_slugs.join(", ")
327
403
  end
328
-
329
- define_pattern_internal(name) { |_, _| true }
404
+ define_pattern_internal(name, kind: :weave, expanded_slugs: pattern_slugs) { true }
330
405
  end
331
406
 
332
407
  def before(&block)
@@ -336,68 +411,50 @@ module Loom::Pattern
336
411
  def after(&block)
337
412
  hook :after, &block
338
413
  end
414
+ # END DSL Implementation
339
415
 
340
- def weave_slugs
341
- @weave_slugs || {}
342
- end
343
-
344
- def is_weave?(name)
345
- !!weave_slugs[name]
346
- end
347
-
348
- def pattern_methods
349
- @pattern_methods || []
350
- end
351
-
352
- def pattern_description(name)
353
- @pattern_descriptions[name]
354
- end
355
-
356
- def pattern_method(name)
357
- raise UnknownPatternMethod, name unless @pattern_method_map[name]
358
- instance_method name
416
+ def patterns
417
+ @pattern_map.values
359
418
  end
360
419
 
361
420
  def hooks
362
- @hooks || []
421
+ @hooks
363
422
  end
364
423
 
365
424
  def facts
366
- @facts || {}
425
+ @fact_map
367
426
  end
368
427
 
369
428
  def let_map
370
- @let_map || {}
429
+ @let_map
371
430
  end
372
431
 
373
432
  private
374
- def define_pattern_internal(name, &loom_file_block)
375
- @pattern_methods ||= []
376
- @pattern_method_map ||= {}
377
- @pattern_descriptions ||= {}
378
-
379
- method_name = name.to_sym
433
+ # TODO: Let mods introduce new pattern handlers. A pattern is effectively a
434
+ # named wrapper around a pattern execution block. This would be an advanced
435
+ # usage when before and after blocks aren't scalable. It could also provided
436
+ # additional filtering for pattern selection at weave time.
437
+ def define_pattern_internal(name, kind: :pattern, **kwargs, &loom_file_block)
438
+ unless block_given?
439
+ raise PatternDefinitionError, "missing block for pattern[#{name}]"
440
+ end
441
+ unless Pattern::KINDS[kind]
442
+ raise "unknown pattern kind: #{kind}"
443
+ end
380
444
 
381
- @pattern_methods << method_name
382
- @pattern_method_map[method_name] = true
383
- @pattern_descriptions[method_name] = @next_description
445
+ desc = @next_description
446
+ unless desc.is_a?(String) || desc.nil?
447
+ raise PatternDefinitionError, "description must be a string: #{desc.class}"
448
+ end
384
449
  @next_description = nil
450
+ name = name.intern
385
451
 
386
- ##
387
- # ```ruby
388
- # pattern :xyz do |loom, facts|
389
- # loom.x :dostuff
390
- # end
391
- # ```
392
- # Patterns declared in the .loom file are defined here:
393
- define_method method_name do |loom, facts|
394
- Loom.log.debug2(self) { "calling .loom file pattern: #{name}" }
395
- self.instance_exec(loom, facts, &loom_file_block)
396
- end
452
+ Loom.log.debug(self) { "defined .loom pattern[kind:#{kind}]: #{name}" }
453
+ @pattern_map[name] = Pattern.new(
454
+ name: name, description: desc, kind: kind, **kwargs, &loom_file_block)
397
455
  end
398
456
 
399
457
  def hook(scope, &block)
400
- @hooks ||= []
401
458
  @hooks << Hook.new(scope, &block)
402
459
  end
403
460
  end # DSL