polytrix 0.1.2 → 0.1.3

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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop-todo.yml +14 -5
  3. data/Gemfile +2 -1
  4. data/README.md +139 -177
  5. data/Rakefile +5 -12
  6. data/bin/polytrix +0 -1
  7. data/features/bootstrapping.feature +0 -3
  8. data/features/cloning.feature +0 -3
  9. data/features/show.feature +38 -0
  10. data/features/states.feature +12 -13
  11. data/features/step_definitions/sdk_steps.rb +0 -4
  12. data/lib/polytrix/challenge.rb +135 -53
  13. data/lib/polytrix/challenge_result.rb +0 -2
  14. data/lib/polytrix/challenge_runner.rb +28 -18
  15. data/lib/polytrix/cli.rb +53 -69
  16. data/lib/polytrix/color.rb +2 -2
  17. data/lib/polytrix/command/action.rb +4 -3
  18. data/lib/polytrix/command/list.rb +39 -28
  19. data/lib/polytrix/command/report.rb +9 -86
  20. data/lib/polytrix/command/reports/code2doc.rb +72 -0
  21. data/lib/polytrix/command/reports/dashboard.rb +125 -0
  22. data/lib/polytrix/command/show.rb +148 -0
  23. data/lib/polytrix/command.rb +37 -104
  24. data/lib/polytrix/configuration.rb +14 -18
  25. data/lib/polytrix/{core/hashie.rb → dash.rb} +4 -3
  26. data/lib/polytrix/documentation/code_segmenter.rb +8 -8
  27. data/lib/polytrix/documentation/comment_styles.rb +1 -1
  28. data/lib/polytrix/documentation/helpers/code_helper.rb +9 -0
  29. data/lib/polytrix/documentation_generator.rb +11 -14
  30. data/lib/polytrix/error.rb +104 -97
  31. data/lib/polytrix/executor.rb +33 -0
  32. data/lib/polytrix/{runners → executors}/buff_shellout_executor.rb +1 -1
  33. data/lib/polytrix/executors/linux_challenge_executor.rb +29 -0
  34. data/lib/polytrix/executors/mixlib_shellout_executor.rb +55 -0
  35. data/lib/polytrix/{runners/windows_challenge_runner.rb → executors/windows_challenge_executor.rb} +4 -11
  36. data/lib/polytrix/{core/implementor.rb → implementor.rb} +10 -6
  37. data/lib/polytrix/manifest.rb +2 -31
  38. data/lib/polytrix/{reports → reporters}/hash_reporter.rb +6 -2
  39. data/lib/polytrix/{reports → reporters}/json_reporter.rb +2 -2
  40. data/lib/polytrix/{reports → reporters}/markdown_reporter.rb +7 -2
  41. data/lib/polytrix/{reports → reporters}/yaml_reporter.rb +2 -2
  42. data/lib/polytrix/reporters.rb +27 -0
  43. data/lib/polytrix/result.rb +6 -5
  44. data/lib/polytrix/spies/file_system_spy.rb +15 -0
  45. data/lib/polytrix/spies.rb +61 -0
  46. data/lib/polytrix/state_file.rb +1 -20
  47. data/lib/polytrix/util.rb +157 -62
  48. data/lib/polytrix/validation.rb +41 -2
  49. data/lib/polytrix/validator.rb +9 -4
  50. data/lib/polytrix/version.rb +1 -1
  51. data/lib/polytrix.rb +110 -105
  52. data/polytrix.gemspec +7 -2
  53. data/polytrix.yml +16 -13
  54. data/resources/assets/pygments/autumn.css +58 -0
  55. data/resources/assets/pygments/borland.css +46 -0
  56. data/resources/assets/pygments/bw.css +34 -0
  57. data/resources/assets/pygments/colorful.css +61 -0
  58. data/resources/assets/pygments/default.css +62 -0
  59. data/resources/assets/pygments/emacs.css +61 -0
  60. data/resources/assets/pygments/friendly.css +61 -0
  61. data/resources/assets/pygments/fruity.css +69 -0
  62. data/resources/assets/pygments/github.css +61 -0
  63. data/resources/assets/pygments/manni.css +61 -0
  64. data/resources/assets/pygments/monokai.css +64 -0
  65. data/resources/assets/pygments/murphy.css +61 -0
  66. data/resources/assets/pygments/native.css +69 -0
  67. data/resources/assets/pygments/pastie.css +60 -0
  68. data/resources/assets/pygments/perldoc.css +58 -0
  69. data/resources/assets/pygments/tango.css +69 -0
  70. data/resources/assets/pygments/trac.css +59 -0
  71. data/resources/assets/pygments/vim.css +69 -0
  72. data/resources/assets/pygments/vs.css +33 -0
  73. data/resources/assets/pygments/zenburn.css +1 -0
  74. data/resources/assets/style.css +41 -0
  75. data/resources/templates/dashboard/files/dashboard.html.tt +82 -0
  76. data/resources/templates/dashboard/templates/_test_report.html.tt +87 -0
  77. data/samples/bootstrap.sh +2 -0
  78. data/samples/clone.sh +2 -0
  79. data/samples/code2doc.sh +4 -0
  80. data/samples/docs/samples/code2doc/java/katas-hello_world-java.md +17 -0
  81. data/samples/docs/samples/code2doc/java/katas-quine-java.md +35 -0
  82. data/samples/docs/samples/code2doc/python/katas-hello_world-python.md +5 -0
  83. data/samples/docs/samples/code2doc/python/katas-quine-python.md +6 -0
  84. data/samples/docs/samples/code2doc/ruby/katas-hello_world-ruby.md +11 -0
  85. data/samples/exec.sh +2 -0
  86. data/samples/polytrix.rb +2 -2
  87. data/samples/polytrix.yml +5 -2
  88. data/samples/show.sh +4 -0
  89. data/samples/test.sh +2 -0
  90. data/samples/tests/polytrix/validators.rb +2 -2
  91. data/samples/verify.sh +3 -0
  92. data/scripts/wrapper +4 -7
  93. data/spec/fabricators/challenge_fabricator.rb +2 -9
  94. data/spec/fabricators/implementor_fabricator.rb +0 -8
  95. data/spec/fabricators/manifest_fabricator.rb +2 -9
  96. data/spec/fabricators/validator_fabricator.rb +2 -4
  97. data/spec/polytrix/challenge_runner_spec.rb +20 -0
  98. data/spec/polytrix/documentation/helpers/code_helper_spec.rb +7 -7
  99. data/spec/polytrix/file_finder_spec.rb +5 -5
  100. data/spec/polytrix/manifest_spec.rb +0 -21
  101. data/spec/polytrix/result_spec.rb +14 -14
  102. data/spec/polytrix/validator_registry_spec.rb +4 -4
  103. data/spec/polytrix/validator_spec.rb +9 -9
  104. data/spec/polytrix_spec.rb +1 -25
  105. data/spec/spec_helper.rb +8 -1
  106. metadata +130 -38
  107. data/features/execution.feature +0 -53
  108. data/features/fixtures/spec/polytrix_spec.rb +0 -7
  109. data/lib/polytrix/cli/report.rb +0 -84
  110. data/lib/polytrix/command/rundoc.rb +0 -27
  111. data/lib/polytrix/core/file_system_helper.rb +0 -75
  112. data/lib/polytrix/core/manifest_section.rb +0 -4
  113. data/lib/polytrix/core/string_helpers.rb +0 -15
  114. data/lib/polytrix/documentation/view_helper.rb +0 -21
  115. data/lib/polytrix/rspec/documentation_formatter.rb +0 -66
  116. data/lib/polytrix/rspec/yaml_report.rb +0 -51
  117. data/lib/polytrix/rspec.rb +0 -56
  118. data/lib/polytrix/runners/executor.rb +0 -34
  119. data/lib/polytrix/runners/linux_challenge_runner.rb +0 -23
  120. data/lib/polytrix/runners/middleware/change_directory.rb +0 -20
  121. data/lib/polytrix/runners/middleware/feature_executor.rb +0 -24
  122. data/lib/polytrix/runners/middleware/setup_env_vars.rb +0 -42
  123. data/lib/polytrix/runners/mixlib_shellout_executor.rb +0 -83
  124. data/lib/polytrix/validations.rb +0 -23
  125. data/samples/scripts/wrapper +0 -7
  126. data/spec/polytrix/middleware/feature_executor_spec.rb +0 -48
  127. data/spec/polytrix/validations_spec.rb +0 -16
@@ -2,13 +2,10 @@ require 'thread'
2
2
 
3
3
  module Polytrix
4
4
  module Command
5
- class Base
5
+ class Base # rubocop:disable ClassLength
6
6
  include Polytrix::DefaultLogger
7
7
  include Polytrix::Logging
8
- include Polytrix::Core::FileSystemHelper
9
-
10
- # Need standard executor...
11
- SUPPORTED_EXTENSIONS = %w(py rb js)
8
+ include Polytrix::Util::FileSystem
12
9
 
13
10
  # Contstructs a new Command object.
14
11
  #
@@ -28,9 +25,9 @@ module Polytrix
28
25
  @action = options.fetch(:action, nil)
29
26
  @help = options.fetch(:help, -> { 'No help provided' })
30
27
  @manifest_file = options.fetch('manifest', nil)
31
- @test_dir = options.fetch('test_dir', nil)
32
28
  @loader = options.fetch(:loader, nil)
33
29
  @shell = options.fetch(:shell)
30
+ @queue = Queue.new
34
31
  end
35
32
 
36
33
  private
@@ -47,10 +44,6 @@ module Polytrix
47
44
  # @api private
48
45
  attr_reader :help
49
46
 
50
- # @return [Config] a Config object
51
- # @api private
52
- attr_reader :test_dir
53
-
54
47
  # @return [Thor::Shell] a Thor shell object
55
48
  # @api private
56
49
  attr_reader :shell
@@ -60,50 +53,7 @@ module Polytrix
60
53
  attr_reader :action
61
54
 
62
55
  def setup
63
- manifest_file = File.expand_path @manifest_file
64
- if File.exists? manifest_file
65
- logger.debug "Loading manifest file: #{manifest_file}"
66
- Polytrix.configuration.manifest = @manifest_file
67
- elsif @options.solo
68
- solo_setup
69
- else
70
- fail StandardError, "No manifest found at #{manifest_file} and not using --solo mode"
71
- end
72
-
73
- Polytrix.configuration.documentation_dir = options[:target_dir]
74
- Polytrix.configuration.documentation_format = options[:format]
75
-
76
- manifest.build_challenges
77
-
78
- test_dir = @test_dir.nil? ? nil : File.expand_path(@test_dir)
79
- if test_dir && File.directory?(test_dir)
80
- $LOAD_PATH.unshift test_dir
81
- Dir["#{test_dir}/**/*.rb"].each do | file_to_require |
82
- require relativize(file_to_require, test_dir).to_s.gsub('.rb', '')
83
- end
84
- end
85
- end
86
-
87
- def solo_setup
88
- suites = {}
89
- solo_basedir = @options.solo
90
- solo_glob = @options.fetch('solo_glob', "**/*.{#{SUPPORTED_EXTENSIONS.join(',')}}")
91
- Dir[File.join(solo_basedir, solo_glob)].sort.each do | code_sample |
92
- code_sample = Pathname.new(code_sample)
93
- suite_name = relativize(code_sample.dirname, solo_basedir).to_s
94
- suite_name = solo_basedir if suite_name == '.'
95
- scenario_name = code_sample.basename(code_sample.extname).to_s
96
- suite = suites[suite_name] ||= Polytrix::Manifest::Suite.new(samples: [])
97
- suite.samples << scenario_name
98
- end
99
- @manifest = Polytrix.configuration.manifest = Polytrix::Manifest.new(
100
- implementors: {
101
- File.basename(solo_basedir) => {
102
- basedir: solo_basedir
103
- }
104
- },
105
- suites: suites
106
- )
56
+ Polytrix.setup(options, @manifest_file)
107
57
  end
108
58
 
109
59
  def manifest
@@ -124,44 +74,6 @@ module Polytrix
124
74
  exit 1
125
75
  end
126
76
 
127
- # @return [Array<Scenario>] an array of scenarios
128
- # @raise [SystemExit] if no scenario are returned
129
- # @api private
130
- def all_scenarios
131
- result = manifest.challenges.values
132
-
133
- if result.empty?
134
- die 'No scenarios defined'
135
- else
136
- result
137
- end
138
- end
139
-
140
- # Return an array on scenarios whos name matches the regular expression.
141
- #
142
- # @param regexp [Regexp] a regular expression matching on instance names
143
- # @return [Array<Instance>] an array of scenarios
144
- # @raise [SystemExit] if no scenarios are returned or the regular
145
- # expression is invalid
146
- # @api private
147
- def filtered_scenarios(regexp)
148
- result = begin
149
- manifest.challenges.get(regexp) ||
150
- manifest.challenges.get_all(/#{regexp}/)
151
- rescue RegexpError => e
152
- die "Invalid Ruby regular expression, " \
153
- "you may need to single quote the argument. " \
154
- "Please try again or consult http://rubular.com/ (#{e.message})"
155
- end
156
- result = [result] unless result.is_a? Array
157
-
158
- if result.empty?
159
- die "No scenarios for regex `#{regexp}', try running `polytrix list'"
160
- else
161
- result
162
- end
163
- end
164
-
165
77
  # Return an array on scenarios whos name matches the regular expression,
166
78
  # the full instance name, or the `"all"` literal.
167
79
  #
@@ -169,9 +81,14 @@ module Polytrix
169
81
  # `"all"`, or `nil`
170
82
  # @return [Array<Instance>] an array of scenarios
171
83
  # @api private
172
- def parse_subcommand(arg = nil)
173
- arg ||= 'all'
174
- arg == 'all' ? all_scenarios : filtered_scenarios(arg)
84
+ def parse_subcommand(regexp = nil)
85
+ scenarios = Polytrix.filter_scenarios(regexp, options)
86
+ die "No scenarios for regex `#{regexp}', try running `polytrix list'" if scenarios.empty?
87
+ scenarios
88
+ rescue RegexpError => e
89
+ die 'Invalid Ruby regular expression, ' \
90
+ 'you may need to single quote the argument. ' \
91
+ "Please try again or consult http://rubular.com/ (#{e.message})"
175
92
  end
176
93
  end
177
94
 
@@ -184,26 +101,42 @@ module Polytrix
184
101
  #
185
102
  # @param action [String] action to perform
186
103
  # @param scenarios [Array<Instance>] an array of scenarios
187
- def run_action(action, scenarios, *args)
104
+ def run_action(_action, scenarios, *_args)
188
105
  concurrency = 1
189
106
  if options[:concurrency]
190
107
  concurrency = options[:concurrency] || scenarios.size
191
108
  concurrency = scenarios.size if concurrency > scenarios.size
192
109
  end
193
110
 
194
- queue = Queue.new
195
- scenarios.each { |i| queue << i }
196
- concurrency.times { queue << nil }
111
+ scenarios.each { |i| @queue << i }
112
+ concurrency.times { @queue << nil }
113
+
114
+ threads = concurrency.times.map { |i| spawn(i) }
115
+ threads.map do |t|
116
+ begin
117
+ t.join
118
+ rescue Polytrix::ExecutionError, Polytrix::ChallengeFailure
119
+ # respawn thread
120
+ t.kill
121
+ threads.delete(t)
122
+ threads.push(spawn)
123
+ end
124
+ end while threads.any?(&:alive?)
125
+ end
126
+
127
+ private
197
128
 
198
- threads = []
199
- concurrency.times do
200
- threads << Thread.new do
201
- while (instance = queue.pop)
129
+ def spawn(i)
130
+ Thread.new(i) do |test_env_number|
131
+ Thread.current[:test_env_number] = test_env_number
132
+ while (instance = @queue.pop)
133
+ begin
202
134
  instance.public_send(action, *args)
135
+ rescue Polytrix::ExecutionError, Polytrix::ChallengeFailure => e
136
+ logger.error(e)
203
137
  end
204
138
  end
205
139
  end
206
- threads.map { |i| i.join }
207
140
  end
208
141
  end
209
142
  end
@@ -1,27 +1,14 @@
1
- require 'middleware'
1
+
2
+ require 'rspec/support'
3
+ require 'rspec/expectations'
2
4
 
3
5
  module Polytrix
4
6
  RESOURCES_DIR = File.expand_path '../../../resources', __FILE__
5
- # Autoload pool
6
- module Runners
7
- module Middleware
8
- autoload :FeatureExecutor, 'polytrix/runners/middleware/feature_executor'
9
- autoload :SetupEnvVars, 'polytrix/runners/middleware/setup_env_vars'
10
- autoload :ChangeDirectory, 'polytrix/runners/middleware/change_directory'
11
-
12
- STANDARD_MIDDLEWARE = ::Middleware::Builder.new do
13
- use Polytrix::Runners::Middleware::ChangeDirectory
14
- use Polytrix::Runners::Middleware::SetupEnvVars
15
- use Polytrix::Runners::Middleware::FeatureExecutor
16
- end
17
- end
18
- end
19
7
 
20
8
  class Configuration < Polytrix::ManifestSection
21
9
  property :dry_run, default: false
22
10
  property :log_root, default: '.polytrix/logs'
23
11
  property :log_level, default: :info
24
- property :middleware, default: Polytrix::Runners::Middleware::STANDARD_MIDDLEWARE
25
12
  property :implementors, default: []
26
13
  # coerce_key :implementors, Polytrix::Implementor
27
14
  property :suppress_output, default: false
@@ -29,8 +16,13 @@ module Polytrix
29
16
  property :template_dir, default: "#{RESOURCES_DIR}"
30
17
  property :documentation_dir, default: 'docs/'
31
18
  property :documentation_format, default: 'md'
32
- # Extra options for rspec
33
- property :rspec_options, default: ''
19
+
20
+ # TODO: This should probably be configurable, or tied to Thor color options.
21
+ if RSpec.respond_to?(:configuration)
22
+ RSpec.configuration.color = true
23
+ else
24
+ RSpec::Expectations.configuration.color = true
25
+ end
34
26
 
35
27
  def default_logger
36
28
  @default_logger ||= Logger.new(stdout: $stdout, level: env_log)
@@ -66,6 +58,10 @@ module Polytrix
66
58
 
67
59
  attr_writer :default_validator_callback
68
60
 
61
+ def register_spy(spy)
62
+ Polytrix::Spies.register_spy(spy)
63
+ end
64
+
69
65
  private
70
66
 
71
67
  # Determine the default log level from an environment variable, if it is
@@ -1,5 +1,4 @@
1
1
  require 'hashie/dash'
2
- require 'hashie/mash'
3
2
  require 'hashie/extensions/coercion'
4
3
 
5
4
  module Polytrix
@@ -7,8 +6,10 @@ module Polytrix
7
6
  include Hashie::Extensions::Coercion
8
7
 
9
8
  def initialize(hash = {})
10
- mash = Hashie::Mash.new(hash)
11
- super mash.to_hash(symbolize_keys: true)
9
+ super Polytrix::Util.symbolized_hash(hash)
12
10
  end
13
11
  end
12
+
13
+ class ManifestSection < Polytrix::Dash
14
+ end
14
15
  end
@@ -5,7 +5,7 @@ module Polytrix
5
5
  # This class was extracted from the [Rocco](http://rtomayko.github.com/rocco/) project
6
6
  # which was in turn based on the [Docco](http://jashkenas.github.com/docco/).
7
7
  class CodeSegmenter # rubocop:disable all
8
- # Cops are disabled because the code is from Rocco
8
+ # Cops are disabled because the code is from Rocco
9
9
  include CommentStyles
10
10
 
11
11
  DEFAULT_OPTIONS = {
@@ -23,7 +23,7 @@ module Polytrix
23
23
  # Parse the raw file source_code into a list of two-tuples. Each tuple has the
24
24
  # form `[docs, code]` where both elements are arrays containing the
25
25
  # raw lines parsed from the input file, comment characters stripped.
26
- def segment(source_code)
26
+ def segment(source_code) # rubocop:disable all
27
27
  sections, docs, code = [], [], []
28
28
  lines = source_code.split("\n")
29
29
 
@@ -42,20 +42,20 @@ module Polytrix
42
42
  in_heredoc = false
43
43
  single_line_comment, block_comment_start, block_comment_mid, block_comment_end =
44
44
  nil, nil, nil, nil
45
- if not @options[:comment_chars][:single].nil?
45
+ unless @options[:comment_chars][:single].nil?
46
46
  single_line_comment = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:single])}\\s?")
47
47
  end
48
- if not @options[:comment_chars][:multi].nil?
48
+ unless @options[:comment_chars][:multi].nil?
49
49
  block_comment_start = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*$")
50
50
  block_comment_end = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
51
- block_comment_one_liner = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*(.*?)\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
51
+ block_comment_one_liner = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*(.*?)\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$") # rubocop:disable Metrics/LineLength
52
52
  block_comment_start_with = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*(.*?)$")
53
53
  block_comment_end_with = Regexp.new("\\s*(.*?)\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
54
54
  if @options[:comment_chars][:multi][:middle]
55
55
  block_comment_mid = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:middle])}\\s?")
56
56
  end
57
57
  end
58
- if not @options[:comment_chars][:heredoc].nil?
58
+ unless @options[:comment_chars][:heredoc].nil?
59
59
  heredoc_start = Regexp.new("#{Regexp.escape(@options[:comment_chars][:heredoc])}(\\S+)$")
60
60
  end
61
61
  lines.each do |line|
@@ -67,8 +67,8 @@ module Polytrix
67
67
  in_comment_block = false
68
68
  elsif block_comment_end_with && line.match(block_comment_end_with)
69
69
  in_comment_block = false
70
- docs << line.match(block_comment_end_with).captures.first.
71
- sub(block_comment_mid || '', '')
70
+ docs << line.match(block_comment_end_with).captures.first
71
+ .sub(block_comment_mid || '', '')
72
72
  else
73
73
  docs << line.sub(block_comment_mid || '', '')
74
74
  end
@@ -15,7 +15,7 @@ module Polytrix
15
15
  extension.tr! '.', ''
16
16
  return extension, COMMENT_STYLES[extension] if COMMENT_STYLES.key? extension
17
17
 
18
- COMMENT_STYLES.each do | style_name, style |
18
+ COMMENT_STYLES.each do | _style_name, style |
19
19
  return extension, style if style[:extensions].include? extension
20
20
  end
21
21
 
@@ -1,4 +1,5 @@
1
1
  require 'polytrix/documentation/code_segmenter'
2
+ require 'rouge'
2
3
 
3
4
  module Polytrix
4
5
  module Documentation
@@ -35,6 +36,14 @@ module Polytrix
35
36
  File.read absolute_source_file
36
37
  end
37
38
 
39
+ def source?
40
+ !absolute_source_file.nil?
41
+ end
42
+
43
+ def highlighted_code(formatter = 'terminal256')
44
+ highlight(source, language: implementor.language, filename: absolute_source_file, formatter: formatter)
45
+ end
46
+
38
47
  def code_block(source_code, language, opts = { format: :markdown })
39
48
  case opts[:format]
40
49
  when :rst
@@ -1,4 +1,5 @@
1
- require 'tilt' # seems to be a bug where padrino-helpers should require tilt
1
+ require 'tilt' # padrino-helpers wants you to pre-require tilt/erubis
2
+ require 'erubis'
2
3
  require 'padrino-helpers'
3
4
 
4
5
  module Polytrix
@@ -20,23 +21,19 @@ module Polytrix
20
21
  end
21
22
 
22
23
  def process(challenges)
24
+ return nil unless File.readable? @template_file
25
+
23
26
  @challenges = challenges
24
- if File.readable? @template_file
25
- # @template_file ||= find_file @search_path, scenario, ""
26
- erb = ERB.new File.read(@template_file)
27
- @result = erb.result(binding) || ''
28
- end
27
+ erb = ERB.new File.read(@template_file)
28
+ @result = erb.result(binding) || ''
29
29
  end
30
30
 
31
31
  def save(target_file)
32
- fail 'No results to write, please call process before save' if @result.nil?
33
- if @result.empty?
34
- # Warn: skip creating empty file
35
- else
36
- FileUtils.mkdir_p File.dirname(target_file)
37
- File.open(target_file, 'wb') do |f|
38
- f.write @result
39
- end
32
+ fail 'No results to write, please call process before save' if @result.nil? || @result.empty?
33
+
34
+ FileUtils.mkdir_p File.dirname(target_file)
35
+ File.open(target_file, 'wb') do |f|
36
+ f.write @result
40
37
  end
41
38
  end
42
39
 
@@ -96,114 +96,121 @@ module Polytrix
96
96
  # Exception class capturing what caused an challenge to die.
97
97
  class ChallengeFailure < TransientFailure; end
98
98
 
99
+ # Exception class capturing what caused a validation to fail.
100
+ class ValidationFailure < TransientFailure; end
101
+
99
102
  class ExecutionError < TransientFailure
100
103
  attr_accessor :execution_result
101
104
  end
102
105
 
103
- # Yields to a code block in order to consistently emit a useful crash/error
104
- # message and exit appropriately. There are two primary failure conditions:
105
- # an expected challenge failure, and any other unexpected failures.
106
- #
107
- # **Note** This method may call `Kernel.exit` so may not return if the
108
- # yielded code block raises an exception.
109
- #
110
- # ## Challenge Failure
111
- #
112
- # This is an expected failure scenario which could happen if an challenge
113
- # couldn't be created, a Chef run didn't successfully converge, a
114
- # post-convergence test suite failed, etc. In other words, you can count on
115
- # encountering these failures all the time--this is Polytrix's worldview:
116
- # crash early and often. In this case a cleanly formatted exception is
117
- # written to `STDERR` and the exception message is written to
118
- # the common Polytrix file logger.
119
- #
120
- # ## Unexpected Failure
121
- #
122
- # All other forms of `Polytrix::Error` exceptions are considered unexpected
123
- # or unplanned exceptions, typically from user configuration errors, driver
124
- # or provisioner coding issues or bugs, or internal code issues. Given
125
- # a stable release of Polytrix and a solid set of drivers and provisioners,
126
- # the most likely cause of this is user configuration error originating in
127
- # the `.polytrix.yml` setup. For this reason, the exception is written to
128
- # `STDERR`, a full formatted exception trace is written to the common
129
- # Polytrix file logger, and a message is displayed on `STDERR` to the user
130
- # informing them to check the log files and check their configuration with
131
- # the `polytrix diagnose` subcommand.
132
- #
133
- # @raise [SystemExit] if an exception is raised in the yielded block
134
- def self.with_friendly_errors
135
- yield
136
- rescue Polytrix::ChallengeFailure => e
137
- Polytrix.mutex.synchronize do
138
- handle_challenge_failure(e)
139
- end
140
- exit 10
141
- rescue Polytrix::Error => e
142
- Polytrix.mutex.synchronize do
143
- handle_error(e)
106
+ class << self
107
+ # Yields to a code block in order to consistently emit a useful crash/error
108
+ # message and exit appropriately. There are two primary failure conditions:
109
+ # an expected challenge failure, and any other unexpected failures.
110
+ #
111
+ # **Note** This method may call `Kernel.exit` so may not return if the
112
+ # yielded code block raises an exception.
113
+ #
114
+ # ## Challenge Failure
115
+ #
116
+ # This is an expected failure scenario which could happen if an challenge
117
+ # couldn't be created, a Chef run didn't successfully converge, a
118
+ # post-convergence test suite failed, etc. In other words, you can count on
119
+ # encountering these failures all the time--this is Polytrix's worldview:
120
+ # crash early and often. In this case a cleanly formatted exception is
121
+ # written to `STDERR` and the exception message is written to
122
+ # the common Polytrix file logger.
123
+ #
124
+ # ## Unexpected Failure
125
+ #
126
+ # All other forms of `Polytrix::Error` exceptions are considered unexpected
127
+ # or unplanned exceptions, typically from user configuration errors, driver
128
+ # or provisioner coding issues or bugs, or internal code issues. Given
129
+ # a stable release of Polytrix and a solid set of drivers and provisioners,
130
+ # the most likely cause of this is user configuration error originating in
131
+ # the `.polytrix.yml` setup. For this reason, the exception is written to
132
+ # `STDERR`, a full formatted exception trace is written to the common
133
+ # Polytrix file logger, and a message is displayed on `STDERR` to the user
134
+ # informing them to check the log files and check their configuration with
135
+ # the `polytrix diagnose` subcommand.
136
+ #
137
+ # @raise [SystemExit] if an exception is raised in the yielded block
138
+ def with_friendly_errors
139
+ yield
140
+ rescue Polytrix::ChallengeFailure => e
141
+ Polytrix.mutex.synchronize do
142
+ handle_challenge_failure(e)
143
+ end
144
+ exit 10
145
+ rescue Polytrix::Error => e
146
+ Polytrix.mutex.synchronize do
147
+ handle_error(e)
148
+ end
149
+ exit 20
144
150
  end
145
- exit 20
146
- end
147
151
 
148
- private
149
-
150
- # Writes an array of lines to the common Polytrix logger's file device at the
151
- # given severity level. If the Polytrix logger is set to debug severity, then
152
- # the array of lines will also be written to the console output.
153
- #
154
- # @param level [Symbol,String] the desired log level
155
- # @param lines [Array<String>] an array of strings to log
156
- # @api private
157
- def self.file_log(level, lines)
158
- Array(lines).each do |line|
159
- if Polytrix.logger.debug?
160
- Polytrix.logger.debug(line)
161
- else
162
- Polytrix.logger.logdev && Polytrix.logger.logdev.public_send(level, line)
163
- end
152
+ # Handles an challenge failure exception.
153
+ #
154
+ # @param e [StandardError] an exception to handle
155
+ # @see Polytrix.with_friendly_errors
156
+ # @api private
157
+ def handle_challenge_failure(e)
158
+ stderr_log(e.message.split(/\s{2,}/))
159
+ stderr_log(Error.formatted_exception(e.original))
160
+ file_log(:error, e.message.split(/\s{2,}/).first)
161
+ debug_log(Error.formatted_trace(e))
164
162
  end
165
- end
166
163
 
167
- # Writes an array of lines to the `STDERR` device.
168
- #
169
- # @param lines [Array<String>] an array of strings to log
170
- # @api private
171
- def self.stderr_log(lines)
172
- Array(lines).each do |line|
173
- $stderr.puts(Color.colorize(">>>>>> #{line}", :red))
164
+ alias_method :handle_validation_failure, :handle_challenge_failure
165
+
166
+ # Handles an unexpected failure exception.
167
+ #
168
+ # @param e [StandardError] an exception to handle
169
+ # @see Polytrix.with_friendly_errors
170
+ # @api private
171
+ def handle_error(e)
172
+ stderr_log(Error.formatted_exception(e))
173
+ stderr_log('Please see .polytrix/logs/polytrix.log for more details')
174
+ # stderr_log("Also try running `polytrix diagnose --all` for configuration\n")
175
+ file_log(:error, Error.formatted_trace(e))
174
176
  end
175
- end
176
177
 
177
- # Writes an array of lines to the common Polytrix debugger with debug
178
- # severity.
179
- #
180
- # @param lines [Array<String>] an array of strings to log
181
- # @api private
182
- def self.debug_log(lines)
183
- Array(lines).each { |line| Polytrix.logger.debug(line) }
184
- end
178
+ private
185
179
 
186
- # Handles an challenge failure exception.
187
- #
188
- # @param e [StandardError] an exception to handle
189
- # @see Polytrix.with_friendly_errors
190
- # @api private
191
- def self.handle_challenge_failure(e)
192
- stderr_log(e.message.split(/\s{2,}/))
193
- stderr_log(Error.formatted_exception(e.original))
194
- file_log(:error, e.message.split(/\s{2,}/).first)
195
- debug_log(Error.formatted_trace(e))
196
- end
180
+ # Writes an array of lines to the common Polytrix logger's file device at the
181
+ # given severity level. If the Polytrix logger is set to debug severity, then
182
+ # the array of lines will also be written to the console output.
183
+ #
184
+ # @param level [Symbol,String] the desired log level
185
+ # @param lines [Array<String>] an array of strings to log
186
+ # @api private
187
+ def file_log(level, lines)
188
+ Array(lines).each do |line|
189
+ if Polytrix.logger.debug?
190
+ Polytrix.logger.debug(line)
191
+ else
192
+ Polytrix.logger.logdev && Polytrix.logger.logdev.public_send(level, line)
193
+ end
194
+ end
195
+ end
197
196
 
198
- # Handles an unexpected failure exception.
199
- #
200
- # @param e [StandardError] an exception to handle
201
- # @see Polytrix.with_friendly_errors
202
- # @api private
203
- def self.handle_error(e)
204
- stderr_log(Error.formatted_exception(e))
205
- stderr_log('Please see .polytrix/logs/polytrix.log for more details')
206
- # stderr_log("Also try running `polytrix diagnose --all` for configuration\n")
207
- file_log(:error, Error.formatted_trace(e))
197
+ # Writes an array of lines to the `STDERR` device.
198
+ #
199
+ # @param lines [Array<String>] an array of strings to log
200
+ # @api private
201
+ def stderr_log(lines)
202
+ Array(lines).each do |line|
203
+ $stderr.puts(Color.colorize(">>>>>> #{line}", :red))
204
+ end
205
+ end
206
+
207
+ # Writes an array of lines to the common Polytrix debugger with debug
208
+ # severity.
209
+ #
210
+ # @param lines [Array<String>] an array of strings to log
211
+ # @api private
212
+ def debug_log(lines)
213
+ Array(lines).each { |line| Polytrix.logger.debug(line) }
214
+ end
208
215
  end
209
216
  end
@@ -0,0 +1,33 @@
1
+ module Polytrix
2
+ module Executors
3
+ autoload :BuffShellOutExecutor, 'polytrix/executors/buff_shellout_executor'
4
+ autoload :MixlibShellOutExecutor, 'polytrix/executors/mixlib_shellout_executor'
5
+
6
+ class ExecutionResult < Polytrix::Dash
7
+ property :exitstatus, require: true
8
+ property :stdout, required: true
9
+ property :stderr, required: true
10
+ coerce_value String, ->(v) { v.force_encoding('utf-8') }
11
+ end
12
+ end
13
+
14
+ module Executor
15
+ attr_writer :executor
16
+ attr_accessor :env
17
+
18
+ def executor
19
+ @executor ||= if RUBY_PLATFORM == 'java'
20
+ # TODO: Display warning that JRuby support is experimental
21
+ # (because executor may not be equivalent)
22
+ Polytrix::Executors::BuffShellOutExecutor.new
23
+ else
24
+ Polytrix::Executors::MixlibShellOutExecutor.new
25
+ end
26
+ end
27
+
28
+ def execute(command, opts = {})
29
+ opts[:env] = env unless env.nil?
30
+ executor.execute(command, opts)
31
+ end
32
+ end
33
+ end