loom-core 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +31 -0
  3. data/Gemfile.lock +19 -1
  4. data/Rakefile +2 -0
  5. data/bin/loom +3 -0
  6. data/docs/architecture.jpg +0 -0
  7. data/gentags.sh +2 -0
  8. data/lib/loom/all.rb +1 -1
  9. data/lib/loom/config.rb +1 -1
  10. data/lib/loom/mods/mod_loader.rb +7 -2
  11. data/lib/loom/pattern/all.rb +1 -0
  12. data/lib/loom/pattern/definition_context.rb +4 -4
  13. data/lib/loom/pattern/dsl.rb +176 -119
  14. data/lib/loom/pattern/expanding_reference.rb +88 -6
  15. data/lib/loom/pattern/loader.rb +1 -17
  16. data/lib/loom/pattern/pattern.rb +52 -0
  17. data/lib/loom/pattern/reference.rb +17 -13
  18. data/lib/loom/pattern/reference_set.rb +71 -50
  19. data/lib/loom/runner.rb +46 -33
  20. data/lib/loom/runner/all.rb +2 -0
  21. data/lib/loom/runner/execution_context.rb +9 -0
  22. data/lib/loom/{dsl.rb → runner/sshkit_connector.rb} +5 -7
  23. data/lib/loom/shell.rb +4 -2
  24. data/lib/loom/shell/core.rb +16 -16
  25. data/lib/loom/version.rb +1 -1
  26. data/lib/loomext/coremods/exec.rb +1 -0
  27. data/loom.TAGS +797 -0
  28. data/loom.gemspec +1 -0
  29. data/spec/.loom/error_handling.loom +1 -0
  30. data/spec/.loom/fail.loom +27 -13
  31. data/spec/.loom/files.loom +1 -0
  32. data/spec/.loom/inventory.yml +3 -0
  33. data/spec/.loom/net.loom +1 -0
  34. data/spec/.loom/pattern_context.loom +1 -0
  35. data/spec/.loom/pkg.loom +1 -0
  36. data/spec/.loom/shell.loom +1 -0
  37. data/spec/.loom/test.loom +17 -4
  38. data/spec/.loom/user.loom +1 -0
  39. data/spec/.loom/vms.loom +1 -0
  40. data/spec/loom/pattern/dsl_spec.rb +3 -2
  41. data/spec/shared/loom_internals_helper.rb +1 -1
  42. data/spec/test_loom_spec.rb +102 -42
  43. data/test +15 -0
  44. metadata +25 -3
@@ -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