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.
- checksums.yaml +4 -4
- data/.rubocop.yml +31 -0
- data/Gemfile.lock +108 -56
- data/Rakefile +2 -0
- data/bin/loom +3 -0
- data/docs/architecture.jpg +0 -0
- data/gentags.sh +2 -0
- data/lib/loom/all.rb +1 -1
- data/lib/loom/config.rb +2 -2
- data/lib/loom/method_signature.rb +1 -1
- data/lib/loom/mods/action_proxy.rb +2 -2
- data/lib/loom/mods/mod_loader.rb +10 -5
- data/lib/loom/mods/module.rb +6 -5
- data/lib/loom/pattern/all.rb +1 -0
- data/lib/loom/pattern/definition_context.rb +7 -5
- data/lib/loom/pattern/dsl.rb +184 -119
- data/lib/loom/pattern/expanding_reference.rb +82 -6
- data/lib/loom/pattern/loader.rb +2 -18
- data/lib/loom/pattern/pattern.rb +52 -0
- data/lib/loom/pattern/reference.rb +17 -13
- data/lib/loom/pattern/reference_set.rb +71 -50
- data/lib/loom/runner/all.rb +2 -0
- data/lib/loom/runner/execution_context.rb +9 -0
- data/lib/loom/{dsl.rb → runner/sshkit_connector.rb} +5 -7
- data/lib/loom/runner.rb +46 -33
- data/lib/loom/shell/api.rb +3 -3
- data/lib/loom/shell/cmd_wrapper.rb +5 -2
- data/lib/loom/shell/core.rb +16 -16
- data/lib/loom/shell.rb +4 -2
- data/lib/loom/version.rb +1 -1
- data/lib/loomext/coremods/exec.rb +1 -0
- data/lib/loomext/coremods/systemd/all.rb +1 -0
- data/lib/loomext/coremods/systemd/hostname.rb +10 -0
- data/loom.TAGS +797 -0
- data/loom.gemspec +4 -3
- data/spec/.loom/error_handling.loom +1 -0
- data/spec/.loom/fail.loom +27 -13
- data/spec/.loom/files.loom +1 -0
- data/spec/.loom/inventory.yml +3 -0
- data/spec/.loom/net.loom +1 -0
- data/spec/.loom/pattern_context.loom +1 -0
- data/spec/.loom/pkg.loom +1 -0
- data/spec/.loom/regression.loom +23 -0
- data/spec/.loom/shell.loom +1 -0
- data/spec/.loom/test.loom +21 -4
- data/spec/.loom/user.loom +1 -0
- data/spec/.loom/vms.loom +1 -0
- data/spec/loom/host_spec_spec.rb +1 -1
- data/spec/loom/pattern/dsl_spec.rb +3 -2
- data/spec/shared/loom_internals_helper.rb +1 -1
- data/spec/test_loom_spec.rb +102 -42
- data/test +15 -0
- metadata +40 -17
data/lib/loom/pattern/dsl.rb
CHANGED
@@ -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
|
-
# -
|
3
|
-
#
|
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
|
-
#
|
35
|
-
#
|
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
|
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
|
59
|
-
|
60
|
-
|
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
|
-
*
|
122
|
+
* cmd
|
87
123
|
* outer:first
|
88
124
|
* outer:inner:second
|
89
125
|
|
90
|
-
Defining the same pattern slug twice raises a
|
126
|
+
Defining the same pattern slug twice raises a `DuplicatePatternRef` error.
|
91
127
|
|
92
128
|
#### Code Details
|
93
129
|
|
94
|
-
|
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
|
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
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
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
|
-
@
|
270
|
-
@
|
271
|
-
|
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
|
-
|
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
|
-
|
291
|
-
|
292
|
-
#
|
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
|
-
|
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
|
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
|
341
|
-
@
|
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
|
-
@
|
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
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
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
|
-
|
382
|
-
|
383
|
-
|
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
|
-
|
388
|
-
|
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
|
-
|
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
|
-
|
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
|
-
@
|
9
|
-
|
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
|
13
|
-
|
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
|
data/lib/loom/pattern/loader.rb
CHANGED
@@ -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.
|
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
|
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
|