loom-core 0.0.6 → 0.0.7

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.
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