loom-core 0.0.6 → 0.0.9

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +31 -0
  3. data/Gemfile.lock +108 -56
  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 +2 -2
  10. data/lib/loom/method_signature.rb +1 -1
  11. data/lib/loom/mods/action_proxy.rb +2 -2
  12. data/lib/loom/mods/mod_loader.rb +10 -5
  13. data/lib/loom/mods/module.rb +6 -5
  14. data/lib/loom/pattern/all.rb +1 -0
  15. data/lib/loom/pattern/definition_context.rb +7 -5
  16. data/lib/loom/pattern/dsl.rb +184 -119
  17. data/lib/loom/pattern/expanding_reference.rb +82 -6
  18. data/lib/loom/pattern/loader.rb +2 -18
  19. data/lib/loom/pattern/pattern.rb +52 -0
  20. data/lib/loom/pattern/reference.rb +17 -13
  21. data/lib/loom/pattern/reference_set.rb +71 -50
  22. data/lib/loom/runner/all.rb +2 -0
  23. data/lib/loom/runner/execution_context.rb +9 -0
  24. data/lib/loom/{dsl.rb → runner/sshkit_connector.rb} +5 -7
  25. data/lib/loom/runner.rb +46 -33
  26. data/lib/loom/shell/api.rb +3 -3
  27. data/lib/loom/shell/cmd_wrapper.rb +5 -2
  28. data/lib/loom/shell/core.rb +16 -16
  29. data/lib/loom/shell.rb +4 -2
  30. data/lib/loom/version.rb +1 -1
  31. data/lib/loomext/coremods/exec.rb +1 -0
  32. data/lib/loomext/coremods/systemd/all.rb +1 -0
  33. data/lib/loomext/coremods/systemd/hostname.rb +10 -0
  34. data/loom.TAGS +797 -0
  35. data/loom.gemspec +4 -3
  36. data/spec/.loom/error_handling.loom +1 -0
  37. data/spec/.loom/fail.loom +27 -13
  38. data/spec/.loom/files.loom +1 -0
  39. data/spec/.loom/inventory.yml +3 -0
  40. data/spec/.loom/net.loom +1 -0
  41. data/spec/.loom/pattern_context.loom +1 -0
  42. data/spec/.loom/pkg.loom +1 -0
  43. data/spec/.loom/regression.loom +23 -0
  44. data/spec/.loom/shell.loom +1 -0
  45. data/spec/.loom/test.loom +21 -4
  46. data/spec/.loom/user.loom +1 -0
  47. data/spec/.loom/vms.loom +1 -0
  48. data/spec/loom/host_spec_spec.rb +1 -1
  49. data/spec/loom/pattern/dsl_spec.rb +3 -2
  50. data/spec/shared/loom_internals_helper.rb +1 -1
  51. data/spec/test_loom_spec.rb +102 -42
  52. data/test +15 -0
  53. metadata +40 -17
@@ -1,13 +1,59 @@
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
+
21
+ # [master]
22
+ # * ... ongoing ... ways to test and +verify+ pattern execution
23
+ # - IDEA: launch a container on host before executing a pattern, w/ an overlay
24
+ # fs of "/". Use this to canary a command sequence before executing on the
25
+ # actual host. This way no rollback, or idempotence checks, or state
26
+ # management, or history needs to be kept. Only the volatile container image
27
+ # needs to be torn down.
28
+
1
29
  # 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.
30
+ # - More Mods! .... ondeck:
31
+ # * bkblz
32
+ # * cassandra
33
+ # * apache (nginx?)
34
+ # * digital ocean
35
+ # * systemd-nspawj
36
+
37
+ # - Models Future:
38
+ # Notice each ondeck module above (bkblz, cassanadra, apache, digital ocean,
39
+ # system-nspawn): covers a unique infrastructure area, i.e.: bkblz:object and
40
+ # cold storage, cassandra:hyper-scale data storage, apache/nginx:content
41
+ # serving, digital-ocean/aws/gcp:remote virtual hosting, containers:local
42
+ # virtual hosting
43
+ # I could generalize each of the above infrastructure area into a high level
44
+ # mod. Should I? Maybe not.
45
+
46
+ # - auto mod documentation in the CLI
47
+
4
48
  # - Pattern+non_idempotent+ marks a pattern as explicitly not idempotent, this
5
49
  # let's additional warnings and checks to be added
50
+
6
51
  # - A history module, store a log of each executed command, a hash of the .loom
7
52
  # file, and the requisite facts (the let declarations) for each executed pattern
8
53
  # on the host it executes. /var/log/loom/history? Create this log on startup.
9
54
  # -- add a new set of history commands through the CLI and a history
10
55
  # FactProvider exposing host/loom/pattern_slug execution stats.
56
+
11
57
  # - Provide automatic command reversion support with a =Module= DSL that ties in
12
58
  # with local revision history.
13
59
  # -- allow Module actions/mods to define an "undo" command of itself given the
@@ -24,6 +70,7 @@
24
70
  # accesses to fact_set be done in let expressions (enforce this maybe?)
25
71
  # -- Later... before/after hooks can ensure the entire loom execution sequence
26
72
  # was "revertable"
73
+
27
74
  # - A mechanism to allow mods to register CLI flags and actions. Using an action predicate
28
75
  # mechanisms can add flags at the global or action levels. All CLI flags set config values.
29
76
  # -- Mods can also register for action namespaces similar to git. This is consistent with mod
@@ -31,33 +78,22 @@
31
78
  # -- Best way is to migrate loom/mods/module and loom/mods/action_proxy into base classes of
32
79
  # themselves. The isolate the shell specific behavior into a subclass of each to preserve the
33
80
  # 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.
81
+ #
82
+ # - Add a "groups" flag to `loom i` to display inventory groupings
48
83
 
49
84
  =begin
50
85
 
51
86
  ## .loom File DSL
52
87
 
53
88
  See:
54
- * spec/test.loom for a valid .loom file.
89
+ * spec/.loom/*.loom for a lots of valid .loom files.
90
+ * run these specs with `rspec spec/test_loom_spec.rb`
55
91
  * spec/loom/pattern/dsl_spec.rb for other examples
56
92
 
57
93
 
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.
94
+ I've tried to take inspriation from several ruby DSLs, including RSpec, Thor,
95
+ Commander, Sinatra... so hopefully it feels comfortable. Thank you to all of
96
+ those projects.
61
97
 
62
98
  Loom::Pattern::DSL is the mixin that defines the declarative API for all .loom
63
99
  file defined modules. It is included into Loom::Pattern by default. The outer
@@ -83,15 +119,19 @@ end
83
119
 
84
120
  It declares a reference set with slugs:
85
121
 
86
- * top_level
122
+ * cmd
87
123
  * outer:first
88
124
  * outer:inner:second
89
125
 
90
- Defining the same pattern slug twice raises a DuplicatPatternRef error.
126
+ Defining the same pattern slug twice raises a `DuplicatePatternRef` error.
91
127
 
92
128
  #### Code Details
93
129
 
94
- To follow the code path for .loom file loading see:
130
+ Loom::DSL is a facade available to all .loom file ::Modules that include
131
+ Loom::Pattern. It provides Module singleton methods listed at
132
+ Loom::DSL::DSL_METHODS.
133
+
134
+ Code path for .loom file loading:
95
135
 
96
136
  Loom::Runner#load
97
137
  -> Loom::Pattern::Loader.load
@@ -103,12 +143,48 @@ file. A ReferenseSet being a collection of references with uniquely named
103
143
  slugs. The slug of a reference is computed from the module namespace and
104
144
  instance method name.
105
145
 
146
+ ### `weave`
147
+
148
+ The `weave` creates a specialized pattern, that allows aliasing a sequence of
149
+ pattern slugs as a single pattern name. Pattern execution will be flattened and
150
+ run sequentially before or after any other patterns in the `$ loom` invocation.
151
+
152
+ ``` ~ruby
153
+ pattern :step_1 { ... }
154
+ pattern :step_2 { ... }
155
+
156
+ module OtherTasks
157
+ ...
158
+ end
159
+
160
+ weave :do_it, [ :step_1, :step_2, :other_tasks:* ]
161
+ ```
162
+
163
+ This creates pattern :do_it, which when run `$ loom do_it` will run :step_1,
164
+ :step_2, and all slugs that match /other_tasks.*/. Recursive expansion is
165
+ explicitly disallowed, only pattern names (not weaves), are allowed in the list
166
+ of weave pattern slugs.
167
+
168
+ #### Code Details
169
+
170
+ Weave expansion to pattern slugs is accomplished by creating a
171
+ Loom::Pattern::ExpandingReference via the Loom::Pattern::Loader+load+ path
172
+ invoked via Loom::Runner+load+. Expansion happens on read via
173
+ Loom::Pattern::Loader+patterns+, thus the list of patterns is constant
174
+ throughout all phases of pattern execution.
175
+
106
176
  ### `report`
107
177
 
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.
178
+ Use `report` to create another specialized pattern that prints a fact, value,
179
+ or result of a block to yaml, json, or any other format to STDOUT.
110
180
 
111
- ### `let`, `before`, and `after`
181
+ ### `let`, `before`, and `after`: for examples spec/.loom/parent_context.loom
182
+
183
+ `let`, does the same as in RSpec (including the context details). It creates an
184
+ alias for a value, available in all patterns.
185
+
186
+ `before` and `after` are similar to the same in RSpec. Each before/after block
187
+ is run before/after respectively to EACH pattern.
112
188
 
113
189
  Module::Inner, from above, inherits all +let+ declarations from its outer
114
190
  contexts, both :: (root) and ::Outer. +before+ hooks are run from a top-down
@@ -179,31 +255,6 @@ Loom::Facts::FactSet as parameters.
179
255
  See Loom::Pattern::DefinitionContext for evaluation of `let` blocks and
180
256
  before/after context ordering.
181
257
 
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
258
  #### Pattern Execution Sequence
208
259
 
209
260
  Once hosts and patterns are identified in earlier Loom::Runner phases,
@@ -250,14 +301,55 @@ module Loom::Pattern
250
301
 
251
302
  PatternDefinitionError = Class.new Loom::LoomError
252
303
 
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.
304
+ DSL_METHODS = [
305
+ :desc,
306
+ :description,
307
+
308
+ :pattern,
309
+ :weave,
310
+ :report,
311
+
312
+ :with_facts,
313
+ :let,
314
+ :before,
315
+ :after,
316
+
317
+ :namespace
318
+ ]
319
+
320
+ ##
321
+ # The Loom DSL definition. See documentation above.
259
322
  module DSL
323
+ DSL_METHODS.each do |m|
324
+ define_method m do |*args, **kwargs, &block|
325
+ Loom.log.debug1(self) { "delegating Pattern::DSL call to DSLBuilder+#{m}+" }
326
+ @dsl_builder.send(m, *args, **kwargs, &block)
327
+ end
328
+ end
329
+
330
+ attr_reader :dsl_builder
331
+
332
+ class << self
333
+ def extended(receiving_mod)
334
+ # NB: Using Forwardable was awkward here due to the scope of extended, and
335
+ # the scope of where the fordwardable instance variable would live.
336
+ dsl_builder = PatternBuilder.new
337
+ receiving_mod.instance_variable_set :@dsl_builder, dsl_builder
338
+ end
339
+ end
340
+ end
341
+
342
+ class DSL::PatternBuilder
343
+
344
+ def initialize
345
+ @pattern_map = {}
346
+ @fact_map = {}
347
+ @let_map = {}
348
+ @hooks = []
349
+ @next_description = nil
350
+ end
260
351
 
352
+ # BEGIN DSL Implementation
261
353
  loom_accessor :namespace
262
354
 
263
355
  def description(description)
@@ -266,37 +358,33 @@ module Loom::Pattern
266
358
  alias_method :desc, :description
267
359
 
268
360
  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
361
+ @fact_map.merge! new_facts
362
+ yield_result = yield @fact_map if block_given?
363
+ @fact_map = yield_result if yield_result.is_a? Hash
273
364
  end
274
365
 
275
366
  def let(name, default: nil, &block)
276
367
  raise "malformed let expression: missing block" unless block_given?
277
-
278
- @let_map ||= {}
279
368
  @let_map[name.to_sym] = LetMapEntry.new default, &block
280
369
  end
281
370
 
282
371
  def pattern(name, &block)
283
- Loom.log.debug1(self) { "defining pattern => #{name}" }
284
- define_pattern_internal(name, &block)
372
+ define_pattern_internal(name, kind: :pattern, &block)
285
373
  end
286
374
 
287
375
  ##
288
376
  # @param format[:yaml|:json|:raw] default is :yaml
289
377
  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.
378
+ define_pattern_internal(name, kind: :report) do |loom, facts|
379
+ # TODO: I don't like all of this logic here. It feels like it belongs in
380
+ # a mod.
293
381
  result = if block_given?
294
382
  Loom.log.debug(self) { "report[#{name}] from block" }
295
- self.instance_exec(loom, facts, &block)
383
+ instance_exec(loom, facts, &block)
296
384
  elsif !Loom::Facts.is_empty?(facts[name])
297
385
  Loom.log.debug(self) { "report[#{name}] from facts[#{name}]" }
298
386
  facts[name]
299
- elsif self.respond_to?(name) && !self.send(name).nil?
387
+ elsif respond_to?(name) && !self.send(name).nil?
300
388
  Loom.log.debug(self) { "report[#{name}] from let{#{name}}" }
301
389
  self.send name
302
390
  else
@@ -318,15 +406,10 @@ module Loom::Pattern
318
406
  end
319
407
 
320
408
  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
409
  unless @next_description
326
410
  @next_description = "Weave runs patterns: %s" % pattern_slugs.join(", ")
327
411
  end
328
-
329
- define_pattern_internal(name) { |_, _| true }
412
+ define_pattern_internal(name, kind: :weave, expanded_slugs: pattern_slugs) { true }
330
413
  end
331
414
 
332
415
  def before(&block)
@@ -336,68 +419,50 @@ module Loom::Pattern
336
419
  def after(&block)
337
420
  hook :after, &block
338
421
  end
422
+ # END DSL Implementation
339
423
 
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
424
+ def patterns
425
+ @pattern_map.values
359
426
  end
360
427
 
361
428
  def hooks
362
- @hooks || []
429
+ @hooks
363
430
  end
364
431
 
365
432
  def facts
366
- @facts || {}
433
+ @fact_map
367
434
  end
368
435
 
369
436
  def let_map
370
- @let_map || {}
437
+ @let_map
371
438
  end
372
439
 
373
440
  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
441
+ # TODO: Let mods introduce new pattern handlers. A pattern is effectively a
442
+ # named wrapper around a pattern execution block. This would be an advanced
443
+ # usage when before and after blocks aren't scalable. It could also provided
444
+ # additional filtering for pattern selection at weave time.
445
+ def define_pattern_internal(name, kind: :pattern, **kwargs, &loom_file_block)
446
+ unless block_given?
447
+ raise PatternDefinitionError, "missing block for pattern[#{name}]"
448
+ end
449
+ unless Pattern::KINDS[kind]
450
+ raise "unknown pattern kind: #{kind}"
451
+ end
380
452
 
381
- @pattern_methods << method_name
382
- @pattern_method_map[method_name] = true
383
- @pattern_descriptions[method_name] = @next_description
453
+ desc = @next_description
454
+ unless desc.is_a?(String) || desc.nil?
455
+ raise PatternDefinitionError, "description must be a string: #{desc.class}"
456
+ end
384
457
  @next_description = nil
458
+ name = name.intern
385
459
 
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
460
+ Loom.log.debug(self) { "defined .loom pattern[kind:#{kind}]: #{name}" }
461
+ @pattern_map[name] = Pattern.new(
462
+ name: name, description: desc, kind: kind, **kwargs, &loom_file_block)
397
463
  end
398
464
 
399
465
  def hook(scope, &block)
400
- @hooks ||= []
401
466
  @hooks << Hook.new(scope, &block)
402
467
  end
403
468
  end # DSL
@@ -1,16 +1,92 @@
1
+ require "pry"
2
+
1
3
  module Loom::Pattern
2
4
  class ExpandingReference
3
5
 
4
- attr_reader :slug, :reference_slugs, :source_file, :desc
6
+ RecursiveExpansionError = Class.new Loom::LoomError
7
+
8
+ # TODO: Ensure ExpandingReference and Reference stay in sync. Maybe create
9
+ # an inheritance hierarchy.
10
+ attr_reader :slug, :reference_slugs, :source_file, :desc, :pattern
5
11
 
6
- def initialize(slug, reference_slugs, source_file, description)
12
+ ##
13
+ # @param slug [String]: flattened colon separated slug name
14
+ # @param pattern [Loom::Pattern::Pattern]: a pattern responding to +expanded_slugs+
15
+ def initialize(slug, pattern, reference_set)
7
16
  @slug = slug
8
- @reference_slugs = reference_slugs
9
- @desc = description
17
+ @reference_set = reference_set
18
+ # TODO: Hmm... I tried to abstract the "weave" keyword from the
19
+ # "ExpandingReference" concept... but it leaked through. Think the
20
+ # `pattern.kind` based method name over.
21
+ @reference_slugs = pattern.weave.expanded_slugs
22
+ @desc = pattern.description
23
+ @pattern
10
24
  end
11
25
 
12
- def is_expanding?
13
- true
26
+ def expand_slugs
27
+ # O(MN) :(
28
+ expanded_slugs = @reference_slugs.flat_map do |my_slug|
29
+ matcher = Matcher.get_matcher(my_slug)
30
+ @reference_set.slugs.select { |your_slug| matcher.match? your_slug }
31
+ end.uniq
32
+ Loom.log.debug3(self) { "Loom::Pattern::ExpandingReference@reference_slugs+: #{@reference_slugs.join(",")}"}
33
+ Loom.log.debug3(self) { "Loom::Pattern::ExpandingReference+expanded_slugs+: #{expanded_slugs.join(",")}"}
34
+
35
+ expanded_refs = expanded_slugs.map { |s| @reference_set[s] }
36
+ expanded_refs.each do |r|
37
+ if r.is_a? ExpandingReference
38
+ Loom.log.error "recursive expansion for pattern[#{r.slug}] in weave[#{@slug}], i.e. only patterns are allowed in weaves"
39
+ raise RecursiveExpansionError, @slug
40
+ end
41
+ end
42
+
43
+ Loom.log.info { "expanded slug[#{@slug}] => #{expanded_slugs.join(",")}"}
44
+ expanded_slugs
45
+ end
46
+
47
+ private
48
+ # TODO: This can be made common to some utility directory if one emerges.
49
+ class Matcher
50
+
51
+ def self.get_matcher(slug)
52
+ matcher_module = [
53
+ GlobMatcher,
54
+ EqualityMatcher
55
+ ].first { |m| m.handles_pattern? slug }
56
+
57
+ Class.new(Matcher).include(matcher_module).new(slug)
58
+ end
59
+
60
+ def initialize(loom_pattern_slug)
61
+ @my_slug = loom_pattern_slug
62
+ end
63
+
64
+ private
65
+ module GlobMatcher
66
+ MATCH_P = /(\*)$/
67
+
68
+ def self.handles_pattern?(my_slug)
69
+ res = my_slug.match? MATCH_P
70
+ Loom.log.debug2(self) { "#{p}.match? #{MATCH_P} = #{res}" }
71
+ res
72
+ end
73
+
74
+ def match?(your_pattern)
75
+ prefix = @my_slug.to_s.gsub(MATCH_P, "")
76
+ Loom.log.debug2(self) { "GlobMatcher+match?+ #{@my_slug} #{your_pattern}, prefix: #{prefix}"}
77
+ your_pattern.to_s.start_with? prefix
78
+ end
79
+ end
80
+
81
+ module EqualityMatcher
82
+ def self.handles_pattern?(p)
83
+ true
84
+ end
85
+
86
+ def match?(your_pattern)
87
+ @my_slug == your_pattern
88
+ end
89
+ end
14
90
  end
15
91
  end
16
92
  end
@@ -1,7 +1,6 @@
1
1
  module Loom::Pattern
2
2
 
3
3
  SiteFileNotFound = Class.new Loom::LoomError
4
- RecursiveExpansionError = Class.new Loom::LoomError
5
4
 
6
5
  class Loader
7
6
  class << self
@@ -37,7 +36,7 @@ module Loom::Pattern
37
36
 
38
37
  def load_patterns
39
38
  @loom_pattern_files.each do |f|
40
- raise SiteFileNotFound, f unless File.exists? f
39
+ raise SiteFileNotFound, f unless File.exist? f
41
40
  load_pattern_file f
42
41
  end
43
42
  end
@@ -48,22 +47,7 @@ module Loom::Pattern
48
47
  end
49
48
 
50
49
  def expand_refs(refs)
51
- refs.flat_map do |ref|
52
- if ref.is_expanding?
53
- expanded_refs = ref.reference_slugs.map { |s| get_pattern_ref(s) }
54
- expanded_refs.each do |exp_ref|
55
- if exp_ref.is_expanding?
56
- Loom.log.error "error expanding pattern[#{exp_ref.slug}] in weave[#{ref.slug}], i.e. only patterns are allowed in weaves"
57
- raise RecursiveExpansionError, ref.slug
58
- end
59
- end
60
- Loom.log.info(
61
- "expanded pattern #{ref.slug} to patterns: #{expanded_refs.map(&:slug).join(",")}")
62
- expanded_refs
63
- else
64
- ref
65
- end
66
- end
50
+ refs.flat_map { |r| r.expand_slugs }.map { |s| @reference_set[s] }
67
51
  end
68
52
  end
69
53
  end
@@ -0,0 +1,52 @@
1
+ module Loom::Pattern
2
+ # A value object represnting the .loom file pattern declarations. The
3
+ # difference between a Loom::Pattern::Pattern and Loom::Pattern::Reference is
4
+ # a pattern has no association to the context it should run in. It is simply a
5
+ # value object with pointers to its assigned values from the .loom
6
+ # file. However, a Reference includes it's DefinitionContext, including nested
7
+ # before/after/with_facts/let hooks.
8
+ class Pattern
9
+
10
+ KINDS = %i[pattern weave report]
11
+ .reduce({}) { |memo, k| memo.merge k => true }.freeze
12
+
13
+ # effectively, a list of `attr_readers` for the Pattern kind. but also used
14
+ # for validation
15
+ KIND_KWARGS = {
16
+ weave: [:expanded_slugs]
17
+ }.freeze
18
+
19
+ attr_reader :name, :description, :kind, :pattern_block
20
+
21
+ def initialize(name: nil, description: nil, kind: nil, **kind_kwargs, &block)
22
+ @name = name
23
+ @description = description
24
+ @kind = kind
25
+ @pattern_block = block
26
+
27
+ @valid_kwargs = KIND_KWARGS[kind]
28
+ kind_kwargs.each do |k, _|
29
+ raise "unknown kind_kwarg: #{k}" unless @valid_kwargs.include? k
30
+ end
31
+ @kind_properties_struct = OpenStruct.new kind_kwargs
32
+ end
33
+
34
+ # Adds methods:
35
+ # :is_weave?, is_pattern?, :is_reported?
36
+ # :weave, :patttern, :reported => returns the OpenStruct of KIND_KWARGS.
37
+ KINDS.keys.each do |k|
38
+ is_k_name = "is_#{k}?".intern
39
+ Loom.log.debug3(self) { "defining method Loom::Pattern::Pattern+#{is_k_name}+" }
40
+ define_method(is_k_name) { @kind == k }
41
+
42
+ k_name = k.intern
43
+ Loom.log.debug1(self) { "defining method Loom::Pattern::Pattern+#{k_name}+" }
44
+ define_method(k_name) do
45
+ if @kind != k_name
46
+ raise "invalid kwarg +#{k_name}+ for Pattern[#{@kind}]"
47
+ end
48
+ @kind_properties_struct.dup
49
+ end
50
+ end
51
+ end
52
+ end