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
@@ -1,16 +1,98 @@
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
24
+ end
25
+
26
+ def expand_slugs
27
+ slug_matchers = @reference_slugs.map do |s|
28
+ Matcher.get_matcher(s)
29
+ end
30
+
31
+ # O(MN) :(
32
+ expanded_slugs = @reference_slugs.flat_map do |s|
33
+ @reference_set.slugs.select { |s| slug_matchers.any? { |m| m.match?(s) } }
34
+ end.uniq
35
+ Loom.log.debug3(self) { "Loom::Pattern::ExpandingReference@reference_slugs+: #{@reference_slugs.join(",")}"}
36
+ Loom.log.debug3(self) { "Loom::Pattern::ExpandingReference+expanded_slugs+: #{expanded_slugs.join(",")}"}
37
+
38
+ expanded_refs = expanded_slugs.map { |s| @reference_set[s] }
39
+ expanded_refs.each do |r|
40
+ if r.is_a? ExpandingReference
41
+ Loom.log.error "recursive expansion for pattern[#{r.slug}] in weave[#{@slug}], i.e. only patterns are allowed in weaves"
42
+ raise RecursiveExpansionError, @slug
43
+ end
44
+ end
45
+
46
+ Loom.log.info { "expanded slug[#{@slug}] => #{expanded_slugs.join(",")}"}
47
+ expanded_slugs
10
48
  end
11
49
 
12
- def is_expanding?
13
- true
50
+ private
51
+ # TODO: This can be made common to some utility directory if one emerges.
52
+ class Matcher
53
+
54
+ def self.get_matcher(slug)
55
+ matcher_module = [
56
+ GlobMatcher,
57
+ EqualityMatcher
58
+ ].first { |m| m.handles_pattern? slug }
59
+
60
+ Class.new(Matcher).include(matcher_module).new(slug)
61
+ end
62
+
63
+ def initialize(loom_pattern_slug)
64
+ @my_slug = loom_pattern_slug
65
+ end
66
+
67
+ private
68
+ module GlobMatcher
69
+ MATCH_P = /(\*)$/
70
+
71
+ def self.handles_pattern?(p)
72
+ p.match? MATCH_P
73
+ end
74
+
75
+ def match?(your_pattern)
76
+ # TODO: This can be made RE2 compliant later.
77
+ unless GlobMatcher.handles_pattern?(@my_slug)
78
+ raise 'WTF? invalid pattern, must end in "*": %s' % @my_slug
79
+ end
80
+
81
+ prefix = @my_slug.to_s.gsub(MATCH_P, "")
82
+ Loom.log.debug2(self) { "GlobMatcher+match?+ #{@my_slug} #{your_pattern}, prefix: #{prefix}"}
83
+ your_pattern.to_s.start_with? prefix
84
+ end
85
+ end
86
+
87
+ module EqualityMatcher
88
+ def self.handles_pattern?(p)
89
+ true
90
+ end
91
+
92
+ def match?(your_pattern)
93
+ @my_slug == your_pattern
94
+ end
95
+ end
14
96
  end
15
97
  end
16
98
  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
@@ -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
@@ -1,22 +1,25 @@
1
1
  module Loom::Pattern
2
+ # See Loom::Pattern::Pattern for the difference between refs and patterns.
2
3
  class Reference
3
4
 
4
- attr_reader :slug, :source_file, :desc
5
+ attr_reader :slug, :source_file, :desc, :pattern
5
6
 
6
- def initialize(slug, unbound_method, source_file, definition_ctx, description)
7
+ def initialize(slug, pattern, source_file, definition_ctx)
7
8
  @slug = slug
8
- @unbound_method = unbound_method
9
9
  @source_file = source_file
10
10
  @definition_ctx = definition_ctx
11
- @desc = description
11
+ @desc = pattern.description
12
+ @pattern = pattern
12
13
  end
13
14
 
14
- def is_expanding?
15
- false
15
+ # Used by Loom::Pattern::Loader to expand weaves. A Pattern::Reference it
16
+ # just expands to itself.
17
+ def expand_slugs
18
+ @slug
16
19
  end
17
20
 
18
21
  def call(shell_api, host_fact_set)
19
- run_context = RunContext.new @unbound_method, @definition_ctx
22
+ run_context = RunContext.new @pattern, @definition_ctx
20
23
 
21
24
  fact_set = @definition_ctx.fact_set host_fact_set
22
25
  Loom.log.debug5(self) {
@@ -30,7 +33,7 @@ module Loom::Pattern
30
33
  begin
31
34
  run_context.run shell_api, fact_set
32
35
  rescue => e
33
- error_msg = "error executing '#{slug}' in #{source_file} => #{e} \n%s"
36
+ error_msg = "error executing '#{slug}' in #{source_file} =>\n\t#{e}\n%s"
34
37
  Loom.log.error(error_msg % e.backtrace.join("\n\t"))
35
38
  raise
36
39
  end
@@ -42,8 +45,8 @@ module Loom::Pattern
42
45
  # A small class to bind the unbound_method to and provide context
43
46
  # in the case of errors.
44
47
  class RunContext
45
- def initialize(unbound_method, definition_ctx)
46
- @bound_method = unbound_method.bind self
48
+ def initialize(pattern, definition_ctx)
49
+ @pattern = pattern
47
50
  @definition_ctx = definition_ctx
48
51
  end
49
52
 
@@ -55,7 +58,7 @@ module Loom::Pattern
55
58
  Loom.log.debug1(self) { "before hooks => #{before_hooks}"}
56
59
  before_hooks.each do |hook|
57
60
  Loom.log.debug2(self) { "executing before hook => #{hook}"}
58
- self.instance_exec shell_api, fact_set, &hook.block
61
+ instance_exec shell_api, fact_set, &hook.block
59
62
  end
60
63
 
61
64
  # This is the entry point into calling patterns.
@@ -64,14 +67,15 @@ module Loom::Pattern
64
67
  Loom.log.debug1(self) { "after hooks => #{after_hooks}" }
65
68
  after_hooks.each do |hook|
66
69
  Loom.log.debug2(self) { "executing after hook => #{hook}"}
67
- self.instance_exec shell_api, fact_set, &hook.block
70
+ instance_exec shell_api, fact_set, &hook.block
68
71
  end
69
72
  end
70
73
  end
71
74
 
72
75
  private
76
+
73
77
  def apply_pattern(*args)
74
- @bound_method.call *args
78
+ instance_exec(*args, &@pattern.pattern_block)
75
79
  end
76
80
  end
77
81
 
@@ -3,10 +3,13 @@
3
3
  # into a reference set. There's also a lot of dark magic w/ generating
4
4
  # stacktraces to get the correct .loom file line (but I forget if that's done
5
5
  # here or in pattern/reference.rb maybe?)
6
+
7
+ # NB: The use of the word "mod" or "module" in this file probably means a
8
+ # ::Module, not a Loom::Mods::Module
6
9
  module Loom::Pattern
7
10
 
8
11
  DuplicatePatternRef = Class.new Loom::LoomError
9
- UnknownPatternMethod = Class.new Loom::LoomError
12
+ NoReferenceForSlug = Class.new Loom::LoomError
10
13
  InvalidPatternNamespace = Class.new Loom::LoomError
11
14
 
12
15
  ##
@@ -16,7 +19,11 @@ module Loom::Pattern
16
19
  class << self
17
20
  def load_from_file(path)
18
21
  Loom.log.debug1(self) { "loading patterns from file => #{path}" }
19
- Builder.create File.read(path), path
22
+ builder(File.read(path), path).build
23
+ end
24
+
25
+ def builder(file_src, file_path)
26
+ Builder.create(file_src, file_path)
20
27
  end
21
28
  end
22
29
 
@@ -34,21 +41,20 @@ module Loom::Pattern
34
41
 
35
42
  def get_pattern_ref(slug)
36
43
  ref = @slug_to_ref_map[slug]
37
- raise UnknownPatternMethod, slug unless ref
44
+ raise NoReferenceForSlug, slug unless ref
38
45
  ref
39
46
  end
40
47
  alias_method :[], :get_pattern_ref
41
48
 
42
49
  def merge!(ref_set)
43
- self.add_pattern_refs(ref_set.pattern_refs)
50
+ add_pattern_refs(ref_set.pattern_refs)
44
51
  end
45
52
 
46
53
  def add_pattern_refs(refs)
47
- map = @slug_to_ref_map
48
54
  refs.each do |ref|
49
55
  Loom.log.debug2(self) { "adding ref to set => #{ref.slug}" }
50
- raise DuplicatePatternRef, ref.slug if map[ref.slug]
51
- map[ref.slug] = ref
56
+ raise DuplicatePatternRef, ref.slug if @slug_to_ref_map[ref.slug]
57
+ @slug_to_ref_map[ref.slug] = ref
52
58
  end
53
59
  end
54
60
 
@@ -57,83 +63,95 @@ module Loom::Pattern
57
63
 
58
64
  class << self
59
65
  def create(ruby_code, source_file)
66
+ # Creates an anonymous parent module in which to evaluate the .loom
67
+ # file src. This module acts as a global context for the .loom file.
68
+ # TODO: How should this be hardened?
60
69
  shell_module = Module.new
61
70
  shell_module.include Loom::Pattern
62
71
  # TODO: I think this is my black magic for capturing stacktrace
63
72
  # info... I forget the details. Add documentation.
73
+ # TODO: This is where I would need to hack into to auto-include
74
+ # Loom::Pattern in .loom file modules
64
75
  shell_module.module_eval ruby_code, source_file, 1
65
76
  shell_module.namespace ""
66
77
 
67
- self.new(shell_module, source_file).build
78
+ self.new shell_module, source_file
68
79
  end
69
80
  end
70
81
 
71
82
  def initialize(shell_module, source_file)
72
83
  @shell_module = shell_module
73
- @pattern_mod_specs = pattern_mod_specs
74
84
  @source_file = source_file
75
85
  end
76
86
 
77
87
  def build
78
88
  ref_set = ReferenceSet.new
89
+
90
+ dsl_specs = create_dsl_specs
91
+ pattern_refs = create_pattern_refs dsl_specs, ref_set
92
+
79
93
  ref_set.add_pattern_refs pattern_refs
80
94
  ref_set
81
95
  end
82
96
 
83
97
  private
84
- def pattern_refs
85
- @pattern_mod_specs.map { |mod_spec| refs_for_mod_spec mod_spec }.flatten
98
+ # TODO: I don't like passing in the ref set build target here.... but
99
+ # ExpandingReference needs it... Can I do this w/o an instance variable
100
+ # and w/o plumbing the param through methods?
101
+ def create_pattern_refs(dsl_specs, builder_target_ref_set)
102
+ dsl_specs.flat_map do |dsl_spec|
103
+ create_refs_for_dsl_spec dsl_spec, builder_target_ref_set
104
+ end
86
105
  end
87
106
 
88
- def refs_for_mod_spec(mod_spec)
89
- mod = mod_spec[:module]
90
- context = context_for_mod_spec mod_spec
107
+ def create_refs_for_dsl_spec(dsl_spec, builder_target_ref_set)
108
+ context = create_defn_context_for_dsl_spec dsl_spec
91
109
 
92
- mod_spec[:pattern_methods].map do |m|
93
- slug = compute_slug mod_spec[:namespace_list], m
110
+ dsl_spec[:dsl_builder].patterns.map do |pattern|
111
+ slug = compute_slug dsl_spec[:namespace_list], pattern.name
94
112
 
95
- if mod.is_weave?(m)
113
+ case pattern.kind
114
+ when :weave
96
115
  Loom.log.debug2(self) { "adding ExpandingReference for weave: #{slug}" }
97
- build_expanding_reference(m, slug, mod)
116
+ create_expanding_reference(pattern, slug, builder_target_ref_set)
98
117
  else
99
118
  Loom.log.debug2(self) { "adding Reference for pattern: #{slug}" }
100
- build_pattern_reference(m, slug, context, mod)
119
+ create_pattern_reference(pattern, slug, context)
101
120
  end
102
121
  end
103
122
  end
104
123
 
105
- def build_expanding_reference(weave_name, slug, mod)
106
- desc = mod.pattern_description weave_name
124
+ def create_expanding_reference(pattern, slug, ref_set)
125
+ desc = pattern.description
107
126
  Loom.log.warn "no descripiton for weave => #{slug}" unless desc
108
127
 
109
- ExpandingReference.new slug, mod.weave_slugs[weave_name], @source_file, desc
128
+ ExpandingReference.new slug, pattern, ref_set
110
129
  end
111
130
 
112
- def build_pattern_reference(pattern_name, slug, context, mod)
113
- method = mod.pattern_method pattern_name
114
- desc = mod.pattern_description pattern_name
131
+ def create_pattern_reference(pattern, slug, context)
132
+ desc = pattern.description
115
133
  Loom.log.warn "no descripiton for pattern => #{slug}" unless desc
116
134
 
117
- Reference.new slug, method, @source_file, context, desc
135
+ Reference.new slug, pattern, @source_file, context
118
136
  end
119
137
 
120
- def context_for_mod_spec(mod_spec)
121
- parents = mod_spec[:parent_modules].find_all do |mod|
122
- is_pattern_module mod
138
+ # Creates a DefinitionContext for dsl_module by flattening and mapping
139
+ # parent ::Modules to a contextualized DefinitionContext.
140
+ def create_defn_context_for_dsl_spec(dsl_spec)
141
+ parents = dsl_spec[:parent_modules].find_all do |mod|
142
+ dsl_module? mod
123
143
  end
124
144
  parent_context = parents.reduce(nil) do |parent_ctx, parent_mod|
125
- DefinitionContext.new parent_mod, parent_ctx
145
+ DefinitionContext.new parent_mod.dsl_builder, parent_ctx
126
146
  end
127
-
128
- mod = mod_spec[:module]
129
- DefinitionContext.new mod, parent_context
147
+ DefinitionContext.new dsl_spec[:dsl_builder], parent_context
130
148
  end
131
149
 
132
150
  def compute_slug(namespace_list, pattern_method_name)
133
151
  namespace_list.dup.push(pattern_method_name).join ":"
134
152
  end
135
153
 
136
- def mod_namespace_list(pattern, parent_modules)
154
+ def create_namespace_list(pattern, parent_modules)
137
155
  mods = parent_modules.dup << pattern
138
156
  mods.reduce([]) do |memo, mod|
139
157
  mod_name = if mod.respond_to?(:namespace) && mod.namespace
@@ -149,32 +167,35 @@ module Loom::Pattern
149
167
  end
150
168
  end
151
169
 
152
- def pattern_mod_specs
153
- pattern_mods = []
154
- traverse_pattern_modules @shell_module do |pattern_mod, parent_modules|
155
- Loom.log.debug2(self) { "found pattern module => #{pattern_mod}" }
156
- pattern_methods = pattern_mod.pattern_methods
170
+ def create_dsl_specs
171
+ dsl_mods = []
172
+ traverse_dsl_modules @shell_module do |dsl_mod, parent_modules|
173
+ Loom.log.debug2(self) { "found pattern module => #{dsl_mod}" }
174
+
175
+ dsl_builder = dsl_mod.dsl_builder
176
+ next if dsl_builder.patterns.empty?
157
177
 
158
- next if pattern_methods.empty?
159
- pattern_mods << {
160
- :namespace_list => mod_namespace_list(pattern_mod, parent_modules),
161
- :pattern_methods => pattern_methods,
162
- :module => pattern_mod,
163
- :parent_modules => parent_modules.dup
178
+ dsl_mods << {
179
+ namespace_list: create_namespace_list(dsl_mod, parent_modules),
180
+ dsl_builder: dsl_builder,
181
+ dsl_module: dsl_mod,
182
+ parent_modules: parent_modules
164
183
  }
165
184
  end
166
- pattern_mods
185
+ dsl_mods
167
186
  end
168
187
 
169
- def is_pattern_module(mod)
188
+ def dsl_module?(mod)
170
189
  mod.included_modules.include? Loom::Pattern
171
190
  end
172
191
 
173
- def traverse_pattern_modules(mod, pattern_parents=[], visited={}, &block)
192
+ # Recursive method to walk the tree of dsl_builders representative of the
193
+ # .loom file pattern modules
194
+ def traverse_dsl_modules(mod, pattern_parents=[], visited={}, &block)
174
195
  return if visited[mod.name] # prevent cycles
175
196
  visited[mod.name] = true
176
197
 
177
- yield mod, pattern_parents.dup if is_pattern_module(mod)
198
+ yield mod, pattern_parents.dup if dsl_module?(mod)
178
199
 
179
200
  # Traverse all sub modules, even ones that aren't
180
201
  # Loom::Pattern[s], since they might contain more sub modules
@@ -185,7 +206,7 @@ module Loom::Pattern
185
206
 
186
207
  pattern_parents << mod
187
208
  sub_modules.each do |sub_mod|
188
- traverse_pattern_modules sub_mod, pattern_parents.dup, visited, &block
209
+ traverse_dsl_modules sub_mod, pattern_parents.dup, visited, &block
189
210
  end
190
211
  end
191
212
  end