polytrix 0.0.1 → 0.1.0.pre

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 (238) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -2
  3. data/.rspec +1 -6
  4. data/.rubocop-todo.yml +19 -0
  5. data/.rubocop.yml +10 -0
  6. data/.travis.yml +11 -0
  7. data/Gemfile +0 -16
  8. data/README.md +119 -28
  9. data/Rakefile +18 -123
  10. data/bin/polytrix +5 -0
  11. data/doc-src/_markdown.md +5 -0
  12. data/doc-src/default_bootstrap.md +13 -0
  13. data/docs/influences.md +28 -0
  14. data/docs/samples/code2doc/java/HelloWorld.md +13 -0
  15. data/docs/samples/code2doc/java/Quine.md +33 -0
  16. data/docs/samples/code2doc/python/hello_world.md +3 -0
  17. data/docs/samples/code2doc/python/quine.md +4 -0
  18. data/docs/samples/code2doc/ruby/hello_world.md +7 -0
  19. data/features/execution.feature +67 -0
  20. data/features/fixtures/configs/empty.yml +12 -0
  21. data/features/fixtures/configs/hello_world.yml +10 -0
  22. data/features/fixtures/spec/polytrix_merge.rb +5 -0
  23. data/features/fixtures/spec/polytrix_spec.rb +10 -0
  24. data/features/reporting.feature +140 -0
  25. data/features/step_definitions/sdk_steps.rb +12 -0
  26. data/features/support/env.rb +8 -0
  27. data/lib/polytrix/challenge.rb +20 -7
  28. data/lib/polytrix/challenge_runner.rb +9 -44
  29. data/lib/polytrix/cli/add.rb +67 -0
  30. data/lib/polytrix/cli/report.rb +88 -0
  31. data/lib/polytrix/cli/reports/hash_reporter.rb +30 -0
  32. data/lib/polytrix/cli/reports/json_reporter.rb +14 -0
  33. data/lib/polytrix/cli/reports/markdown_reporter.rb +23 -0
  34. data/lib/polytrix/cli/reports/yaml_reporter.rb +14 -0
  35. data/lib/polytrix/cli.rb +158 -0
  36. data/lib/polytrix/configuration.rb +65 -4
  37. data/lib/polytrix/core/file_system_helper.rb +75 -0
  38. data/lib/polytrix/core/implementor.rb +31 -3
  39. data/lib/polytrix/documentation/code_segmenter.rb +168 -0
  40. data/lib/polytrix/documentation/comment_styles.rb +87 -0
  41. data/lib/polytrix/documentation/helpers/code_helper.rb +85 -0
  42. data/lib/polytrix/documentation/view_helper.rb +21 -0
  43. data/lib/polytrix/documentation_generator.rb +59 -10
  44. data/lib/polytrix/executor.rb +89 -0
  45. data/lib/polytrix/logger.rb +17 -0
  46. data/lib/polytrix/manifest.rb +64 -7
  47. data/lib/polytrix/result.rb +16 -2
  48. data/lib/polytrix/rspec/documentation_formatter.rb +41 -16
  49. data/lib/polytrix/rspec/yaml_report.rb +51 -0
  50. data/lib/polytrix/rspec.rb +32 -53
  51. data/lib/polytrix/runners/middleware/feature_executor.rb +4 -3
  52. data/lib/polytrix/runners/middleware/setup_env_vars.rb +6 -4
  53. data/lib/polytrix/validation.rb +20 -0
  54. data/lib/polytrix/validations.rb +23 -0
  55. data/lib/polytrix/validator.rb +20 -0
  56. data/lib/polytrix/validator_registry.rb +34 -0
  57. data/lib/polytrix/version.rb +1 -1
  58. data/lib/polytrix.rb +125 -22
  59. data/polytrix.gemspec +7 -2
  60. data/polytrix.rb +6 -0
  61. data/polytrix_tests.yml +20 -0
  62. data/resources/code_sample.tt +2 -0
  63. data/samples/.gitignore +2 -0
  64. data/samples/_markdown.md +5 -0
  65. data/samples/default_bootstrap.rb +14 -0
  66. data/samples/polytrix.rb +28 -0
  67. data/samples/polytrix_cli.sh +7 -0
  68. data/samples/polytrix_tests.yml +10 -0
  69. data/{sdks/fog → samples}/scripts/bootstrap +0 -2
  70. data/samples/scripts/wrapper +7 -0
  71. data/samples/sdks/custom/polytrix.yml +2 -0
  72. data/samples/sdks/java/.gitignore +2 -0
  73. data/samples/sdks/java/build.gradle +14 -0
  74. data/samples/sdks/java/challenges/HelloWorld.java +10 -0
  75. data/samples/sdks/java/challenges/Quine.java +31 -0
  76. data/samples/sdks/java/code_sample.tt +11 -0
  77. data/samples/sdks/java/scripts/bootstrap +2 -0
  78. data/samples/sdks/java/scripts/wrapper +8 -0
  79. data/samples/sdks/python/challenges/hello_world.py +2 -0
  80. data/samples/sdks/python/challenges/quine.py +2 -0
  81. data/{sdks/pkgcloud → samples/sdks/python}/scripts/wrapper +1 -1
  82. data/samples/sdks/ruby/challenges/hello_world.rb +4 -0
  83. data/scripts/bootstrap +1 -9
  84. data/scripts/wrapper +7 -0
  85. data/spec/fabricators/challenge_fabricator.rb +17 -0
  86. data/spec/fabricators/manifest_fabricator.rb +50 -0
  87. data/spec/fabricators/validator_fabricator.rb +12 -0
  88. data/spec/fixtures/{polytrix.yml → polytrix_tests.yml} +0 -0
  89. data/spec/fixtures/src-doc/_scenario.md.erb +1 -0
  90. data/spec/polytrix/challenge_runner_spec.rb +3 -3
  91. data/spec/polytrix/challenge_spec.rb +3 -4
  92. data/spec/polytrix/cli_spec.rb +39 -0
  93. data/spec/polytrix/configuration_spec.rb +45 -1
  94. data/spec/polytrix/documentation/helpers/code_helper_spec.rb +120 -0
  95. data/spec/polytrix/documentation_generator_spec.rb +41 -20
  96. data/spec/polytrix/file_finder_spec.rb +4 -3
  97. data/spec/polytrix/implementor_spec.rb +33 -0
  98. data/spec/polytrix/manifest_spec.rb +32 -14
  99. data/spec/polytrix/middleware/feature_executor_spec.rb +1 -1
  100. data/spec/polytrix/result_spec.rb +49 -0
  101. data/spec/polytrix/validations_spec.rb +16 -0
  102. data/spec/polytrix/validator_registry_spec.rb +39 -0
  103. data/spec/polytrix/validator_spec.rb +63 -0
  104. data/spec/polytrix_spec.rb +33 -7
  105. data/spec/spec_helper.rb +14 -1
  106. data/spec/thor_spy.rb +64 -0
  107. metadata +177 -160
  108. data/.rspec_parallel +0 -10
  109. data/Vagrantfile +0 -41
  110. data/features/0_identity_spec.rb +0 -40
  111. data/features/1_cloud_files_spec.rb +0 -48
  112. data/features/2_servers_spec.rb +0 -19
  113. data/features/features_helper.rb +0 -46
  114. data/features/helpers/cloudfiles_helper.rb +0 -31
  115. data/features/helpers/pacto_helper.rb +0 -33
  116. data/features/helpers/teardown_helper.rb +0 -49
  117. data/features/pacto/extensions/loaders/api_blueprint_loader.rb +0 -63
  118. data/features/pacto/extensions/loaders/simple_loader.rb +0 -55
  119. data/features/pacto/extensions/loaders/yaml_or_json_loader.rb +0 -17
  120. data/features/pacto/extensions/matchers.rb +0 -38
  121. data/features/phase2/feature_coverage_report.rb +0 -109
  122. data/features/phase2/run_all_features.rb +0 -14
  123. data/features/static_site/fixtures/index.html +0 -6
  124. data/lib/polytrix/challenge_builder.rb +0 -16
  125. data/lib/polytrix/core/file_finder.rb +0 -43
  126. data/lib/polytrix/core/result_tracker.rb +0 -25
  127. data/lib/polytrix/runners/middleware/pacto.rb +0 -59
  128. data/packer/.gitignore +0 -3
  129. data/packer/Berksfile +0 -15
  130. data/packer/Gemfile +0 -5
  131. data/packer/Vagrantfile +0 -128
  132. data/packer/cookbooks/drg/metadata.rb +0 -27
  133. data/packer/cookbooks/drg/recipes/admins.rb +0 -22
  134. data/packer/cookbooks/drg/recipes/default.rb +0 -9
  135. data/packer/cookbooks/drg/recipes/dotnet.rb +0 -4
  136. data/packer/cookbooks/drg/recipes/golang.rb +0 -4
  137. data/packer/cookbooks/drg/recipes/java.rb +0 -5
  138. data/packer/cookbooks/drg/recipes/php.rb +0 -10
  139. data/packer/cookbooks/drg/recipes/ruby.rb +0 -29
  140. data/packer/cookbooks/drg/recipes/system.rb +0 -13
  141. data/packer/create_box.sh +0 -10
  142. data/packer/http/preseed.cfg +0 -87
  143. data/packer/packer.json +0 -91
  144. data/packer/scripts/root_setup.sh +0 -37
  145. data/packer/scripts/setup.sh +0 -32
  146. data/pacto/config/pacto_server.rb +0 -40
  147. data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/extensions.json +0 -64
  148. data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/flavors/id.json +0 -100
  149. data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/images/id.json +0 -176
  150. data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/servers/id.json +0 -189
  151. data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/servers.json +0 -63
  152. data/pacto/contracts/dns.api.rackspacecloud.com/v1.0/_tenant_id/domains.json +0 -62
  153. data/pacto/contracts/identity.api.rackspacecloud.com/v2.0/tokens.json +0 -192
  154. data/pacto/contracts/monitoring.api.rackspacecloud.com/v1.0/_tenant_id/account.json +0 -39
  155. data/pacto/contracts/ord.autoscale.api.rackspacecloud.com/v1.0/_tenant_id/groups.json +0 -38
  156. data/pacto/contracts/ord.blockstorage.api.rackspacecloud.com/v1/_tenant_id/volumes.json +0 -30
  157. data/pacto/contracts/ord.databases.api.rackspacecloud.com/v1.0/_tenant_id/instances.json +0 -30
  158. data/pacto/contracts/ord.loadbalancers.api.rackspacecloud.com/v1.0/_tenant_id/loadbalancers.json +0 -114
  159. data/pacto/contracts/ord.queues.api.rackspacecloud.com/v1/_tenant_id/queues.json +0 -13
  160. data/pacto/contracts/ord.servers.api.rackspacecloud.com/v2/_tenant_id/os-networksv2.json +0 -46
  161. data/pacto/contracts/ord.servers.api.rackspacecloud.com/v2/_tenant_id/servers/detail.json +0 -230
  162. data/pacto/contracts/storage101.dfw1.clouddrive.com/v1/mosso_account/container/object.json +0 -15
  163. data/pacto/contracts/storage101.dfw1.clouddrive.com/v1/mosso_account.json +0 -43
  164. data/pacto/contracts/storage101.ord1.clouddrive.com/v1/_mosso_id.json +0 -44
  165. data/pacto/pacto_server.rb +0 -100
  166. data/pacto/rackspace_uri_map.yaml +0 -229
  167. data/scripts/cibuild +0 -4
  168. data/sdks/fog/.gitignore +0 -1
  169. data/sdks/fog/Gemfile +0 -5
  170. data/sdks/fog/challenges/all_connections.rb +0 -45
  171. data/sdks/fog/challenges/authenticate_token.rb +0 -15
  172. data/sdks/fog/challenges/cdn_enable_container.rb +0 -20
  173. data/sdks/fog/challenges/create_a_container.rb +0 -17
  174. data/sdks/fog/challenges/create_server.rb +0 -36
  175. data/sdks/fog/challenges/get_object_metadata.rb +0 -13
  176. data/sdks/fog/challenges/list_containers.rb +0 -10
  177. data/sdks/fog/challenges/provision_scalable_webapp.rb +0 -30
  178. data/sdks/fog/challenges/upload_folder.rb +0 -25
  179. data/sdks/fog/scripts/bootstrap.ps1 +0 -1
  180. data/sdks/fog/scripts/wrapper +0 -2
  181. data/sdks/fog/scripts/wrapper.ps1 +0 -1
  182. data/sdks/gophercloud/.gitignore +0 -2
  183. data/sdks/gophercloud/challenges/authenticate_token.go +0 -23
  184. data/sdks/gophercloud/scripts/bootstrap +0 -6
  185. data/sdks/gophercloud/scripts/wrapper +0 -10
  186. data/sdks/jclouds/.gitignore +0 -1
  187. data/sdks/jclouds/challenges/AuthenticateToken.java +0 -115
  188. data/sdks/jclouds/pom.xml +0 -34
  189. data/sdks/jclouds/scripts/bootstrap +0 -3
  190. data/sdks/jclouds/scripts/wrapper +0 -7
  191. data/sdks/openstack.net/.gitignore +0 -4
  192. data/sdks/openstack.net/.nuget/Microsoft.Build.dll +0 -0
  193. data/sdks/openstack.net/.nuget/NuGet.Config +0 -6
  194. data/sdks/openstack.net/.nuget/NuGet.exe +0 -0
  195. data/sdks/openstack.net/.nuget/NuGet.targets +0 -136
  196. data/sdks/openstack.net/Challenge.cs +0 -10
  197. data/sdks/openstack.net/RunChallenge.cs +0 -19
  198. data/sdks/openstack.net/challenges/AuthenticateToken.cs +0 -24
  199. data/sdks/openstack.net/challenges/Weird.cs +0 -133
  200. data/sdks/openstack.net/openstack.net.csproj +0 -58
  201. data/sdks/openstack.net/openstack.net.sln +0 -27
  202. data/sdks/openstack.net/openstack.net.userprefs +0 -8
  203. data/sdks/openstack.net/packages.config +0 -6
  204. data/sdks/openstack.net/scripts/bootstrap +0 -2
  205. data/sdks/openstack.net/scripts/bootstrap.ps1 +0 -2
  206. data/sdks/openstack.net/scripts/wrapper +0 -7
  207. data/sdks/openstack.net/scripts/wrapper.ps1 +0 -1
  208. data/sdks/php-opencloud/.gitignore +0 -4
  209. data/sdks/php-opencloud/challenges/all_connections.php +0 -64
  210. data/sdks/php-opencloud/challenges/authenticate_token.php +0 -14
  211. data/sdks/php-opencloud/challenges/create_server.php +0 -39
  212. data/sdks/php-opencloud/challenges/get_object_metadata.php +0 -19
  213. data/sdks/php-opencloud/composer.json +0 -5
  214. data/sdks/php-opencloud/scripts/bootstrap +0 -4
  215. data/sdks/php-opencloud/scripts/bootstrap.ps1 +0 -2
  216. data/sdks/php-opencloud/scripts/wrapper +0 -2
  217. data/sdks/php-opencloud/scripts/wrapper.ps1 +0 -1
  218. data/sdks/pkgcloud/.gitignore +0 -1
  219. data/sdks/pkgcloud/challenges/authenticate_token.js +0 -17
  220. data/sdks/pkgcloud/challenges/get_object_metadata.js +0 -18
  221. data/sdks/pkgcloud/scripts/bootstrap +0 -2
  222. data/sdks/pkgcloud/scripts/bootstrap.ps1 +0 -1
  223. data/sdks/pkgcloud/scripts/wrapper.ps1 +0 -1
  224. data/sdks/pyrax/.gitignore +0 -2
  225. data/sdks/pyrax/challenges/all_connections.py +0 -61
  226. data/sdks/pyrax/challenges/authenticate_token.py +0 -17
  227. data/sdks/pyrax/challenges/cdn_enable_container.py +0 -22
  228. data/sdks/pyrax/challenges/create_a_container.py +0 -21
  229. data/sdks/pyrax/challenges/create_server.py +0 -35
  230. data/sdks/pyrax/challenges/get_object_metadata.py +0 -17
  231. data/sdks/pyrax/challenges/upload_folder.py +0 -32
  232. data/sdks/pyrax/requirements.txt +0 -21
  233. data/sdks/pyrax/scripts/bootstrap +0 -9
  234. data/sdks/pyrax/scripts/bootstrap.ps1 +0 -7
  235. data/sdks/pyrax/scripts/wrapper +0 -3
  236. data/sdks/pyrax/scripts/wrapper.ps1 +0 -2
  237. data/spec/polytrix/challenge_builder_spec.rb +0 -16
  238. data/spec/rspec_spec.rb +0 -17
@@ -0,0 +1,158 @@
1
+ require 'polytrix'
2
+ require 'thor'
3
+
4
+ module Polytrix
5
+ module CLI
6
+ autoload :Add, 'polytrix/cli/add'
7
+ autoload :Report, 'polytrix/cli/report'
8
+
9
+ class Base < Thor
10
+ include Polytrix::Core::FileSystemHelper
11
+
12
+ def self.config_options
13
+ # I had trouble with class_option and subclasses...
14
+ method_option :manifest, type: 'string', default: 'polytrix_tests.yml', desc: 'The Polytrix test manifest file'
15
+ method_option :config, type: 'string', default: 'polytrix.rb', desc: 'The Polytrix config file'
16
+ end
17
+
18
+ def self.log_options
19
+ method_option :quiet, type: :boolean, default: false, desc: 'Do not print log messages'
20
+ end
21
+
22
+ def self.doc_options
23
+ method_option :target_dir, type: :string, default: 'docs'
24
+ method_option :lang, enum: Polytrix::Documentation::CommentStyles::COMMENT_STYLES.keys, desc: 'Source language (auto-detected if not specified)'
25
+ method_option :format, enum: %w(md rst), default: 'md'
26
+ end
27
+
28
+ def self.sdk_options
29
+ method_option :sdk, type: 'string', desc: 'An implementor name or directory', default: '.'
30
+ end
31
+
32
+ protected
33
+
34
+ def find_sdks(sdks)
35
+ sdks.map do |sdk|
36
+ implementor = Polytrix.implementors.find { |i| i.name == sdk }
37
+ abort "SDK #{sdk} not found" if implementor.nil?
38
+ implementor
39
+ end
40
+ end
41
+
42
+ def pick_implementor(sdk)
43
+ Polytrix.implementors.find { |i| i.name == sdk } || Polytrix.configuration.implementor(sdk)
44
+ end
45
+
46
+ def debug(msg)
47
+ say("polytrix::debug: #{msg}", :cyan) if debugging?
48
+ end
49
+
50
+ def debugging?
51
+ !ENV['POLYTRIX_DEBUG'].nil?
52
+ end
53
+
54
+ def setup
55
+ manifest_file = File.expand_path options[:manifest]
56
+ config_file = File.expand_path options[:config]
57
+ if File.exists? manifest_file
58
+ debug "Loading manifest file: #{manifest_file}"
59
+ Polytrix.configuration.test_manifest = manifest_file if File.exists? manifest_file
60
+ end
61
+ if File.exists? config_file
62
+ debug "Loading Polytrix config: #{config_file}"
63
+ require_relative config_file
64
+ end
65
+ end
66
+ end
67
+
68
+ class Main < Base
69
+ include Polytrix::Documentation::Helpers::CodeHelper
70
+
71
+ # register Add, :add, 'add', 'Add implementors or code samples'
72
+ # register Report, :report, 'report', 'Generate test reports'
73
+ desc 'add', 'Add implementors or code samples'
74
+ subcommand 'add', Add
75
+
76
+ desc 'report', 'Generate test reports'
77
+ subcommand 'report', Report
78
+
79
+ desc 'code2doc FILES', 'Converts annotated code to Markdown or reStructuredText'
80
+ doc_options
81
+ def code2doc(*files)
82
+ if files.empty?
83
+ help('code2doc')
84
+ abort 'No FILES were specified, check usage above'
85
+ end
86
+
87
+ files.each do |file|
88
+ target_file_name = File.basename(file, File.extname(file)) + ".#{options[:format]}"
89
+ target_file = File.join(options[:target_dir], target_file_name)
90
+ say_status 'polytrix:code2doc', "Converting #{file} to #{target_file}", !quiet?
91
+ doc = Polytrix::DocumentationGenerator.new.code2doc(file, options[:lang])
92
+ FileUtils.mkdir_p File.dirname(target_file)
93
+ File.write(target_file, doc)
94
+ end
95
+ rescue Polytrix::Documentation::CommentStyles::UnknownStyleError => e
96
+ abort "Unknown file extension: #{e.extension}, please use --lang to set the language manually"
97
+ end
98
+
99
+ desc 'exec', 'Executes code sample(s), using the SDK settings if provided'
100
+ method_option :code2doc, type: :boolean, desc: 'Convert successfully executed code samples to documentation using the code2doc command'
101
+ doc_options
102
+ sdk_options
103
+ config_options
104
+ def exec(*files)
105
+ setup
106
+ if files.empty?
107
+ help('exec')
108
+ abort 'No FILES were specified, check usage above'
109
+ end
110
+
111
+ exec_options = {
112
+ # default_implementor: pick_implementor(options[:sdk])
113
+ }
114
+
115
+ files.each do | file |
116
+ say_status 'polytrix:exec', "Running #{file}..."
117
+ results = Polytrix.exec(file, exec_options)
118
+ display_results results
119
+ code2doc(file) if options[:code2doc]
120
+ end
121
+ end
122
+
123
+ desc 'bootstrap [SDKs]', 'Bootstraps the SDK by installing dependencies'
124
+ config_options
125
+ def bootstrap(*sdks)
126
+ setup
127
+ Polytrix.bootstrap(*sdks)
128
+ rescue ArgumentError => e
129
+ abort e.message
130
+ end
131
+
132
+ desc 'test [SDKs]', 'Runs and tests the code samples'
133
+ method_option :rspec_options, format: 'string', desc: 'Extra options to pass to rspec'
134
+ config_options
135
+ def test(*sdks)
136
+ setup
137
+ implementors = find_sdks(sdks)
138
+ Polytrix.configuration.rspec_options = options[:rspec_options]
139
+ Polytrix.run_tests(implementors)
140
+ end
141
+
142
+ protected
143
+
144
+ def quiet?
145
+ options[:quiet] || false
146
+ end
147
+
148
+ def display_results(challenge)
149
+ short_name = challenge.name
150
+ exit_code = challenge.result.execution_result.exitstatus
151
+ color = exit_code == 0 ? :green : :red
152
+ stderr = challenge.result.execution_result.stderr
153
+ say_status "polytrix:exec[#{short_name}][stderr]", stderr, !quiet? unless stderr.empty?
154
+ say_status "polytrix:exec[#{short_name}]", "Finished with exec code: #{challenge.result.execution_result.exitstatus}", color unless quiet?
155
+ end
156
+ end
157
+ end
158
+ end
@@ -4,6 +4,7 @@ require 'hashie/dash'
4
4
  require 'hashie/extensions/coercion'
5
5
 
6
6
  module Polytrix
7
+ RESOURCES_DIR = File.expand_path '../../../resources', __FILE__
7
8
  # Autoload pool
8
9
  module Runners
9
10
  module Middleware
@@ -22,10 +23,70 @@ module Polytrix
22
23
  class Configuration < Hashie::Dash
23
24
  include Hashie::Extensions::Coercion
24
25
 
25
- property :logger, :default => Logger.new($stdout)
26
- property :middleware, :default => Polytrix::Runners::Middleware::STANDARD_MIDDLEWARE
27
- property :implementors
28
- coerce_key :implementors, Polytrix::Implementor
26
+ property :dry_run, default: false
27
+ property :log_level, default: 'info'
28
+ property :middleware, default: Polytrix::Runners::Middleware::STANDARD_MIDDLEWARE
29
+ property :implementors, default: []
30
+ # coerce_key :implementors, Polytrix::Implementor
31
+ property :suppress_output, default: false
32
+ property :default_doc_template
33
+ property :template_dir, default: "#{RESOURCES_DIR}"
34
+ # Extra options for rspec
35
+ property :rspec_options, default: ''
29
36
 
37
+ def logger
38
+ @logger ||= ::Logger.new($stdout).tap do |logger|
39
+ levels = {
40
+ 'fatal' => ::Logger::FATAL,
41
+ 'error' => ::Logger::ERROR,
42
+ 'warn' => ::Logger::WARN,
43
+ 'info' => ::Logger::INFO,
44
+ 'debug' => ::Logger::DEBUG
45
+ }
46
+ fail "Unknown log level: #{log_level}" unless levels.keys.include? log_level
47
+ logger.level = levels[log_level]
48
+ end
49
+ end
50
+
51
+ def test_manifest
52
+ @test_manifest ||= Manifest.from_yaml 'polytrix_tests.yml'
53
+ end
54
+
55
+ def test_manifest=(yaml_file)
56
+ @test_manifest = Manifest.from_yaml yaml_file
57
+ end
58
+
59
+ def implementor(metadata)
60
+ if metadata.is_a? Hash # load from data
61
+ Implementor.new(metadata).tap do |implementor|
62
+ implementors << implementor
63
+ end
64
+ else # load from filesystem
65
+ folder = metadata
66
+ fail ArgumentError, "#{folder} is not a directory" unless File.directory? folder
67
+ settings_file = File.expand_path('polytrix.yml', folder)
68
+ if File.exist? settings_file
69
+ settings = YAML.load(File.read(settings_file))
70
+ Polytrix.configuration.implementor(settings.merge(basedir: folder))
71
+ else
72
+ Polytrix.configuration.implementor name: File.basename(folder), basedir: folder
73
+ end
74
+ end
75
+ end
76
+
77
+ # The callback used to validate code samples that
78
+ # don't have a custom validator. The default
79
+ # checks that the sample code runs successfully.
80
+ def default_validator_callback
81
+ @default_validator_callback ||= proc do |challenge|
82
+ expect(challenge[:result].execution_result.exitstatus).to eq(0)
83
+ end
84
+ end
85
+
86
+ def default_validator
87
+ @default_validator ||= Validator.new(suite: //, scenario: //, &default_validator_callback)
88
+ end
89
+
90
+ attr_writer :default_validator_callback
30
91
  end
31
92
  end
@@ -0,0 +1,75 @@
1
+ module Polytrix
2
+ module Core
3
+ module FileSystemHelper
4
+ include Polytrix::Logger
5
+ class FileNotFound < StandardError; end
6
+
7
+ # Finds a file by loosely matching the file name to a scenario name
8
+ def find_file(search_path, scenario_name, ignored_patterns = read_gitignore(search_path))
9
+ glob_string = "#{search_path}/**/*#{slugify(scenario_name)}.*"
10
+ potential_files = Dir.glob(glob_string, File::FNM_CASEFOLD)
11
+ potential_files.concat Dir.glob(glob_string.gsub('_', '-'), File::FNM_CASEFOLD)
12
+ potential_files.concat Dir.glob(glob_string.gsub('_', ''), File::FNM_CASEFOLD)
13
+
14
+ # Find the first file, not including generated files
15
+ file = potential_files.find do |f|
16
+ !ignored? ignored_patterns, search_path, f
17
+ end
18
+
19
+ fail FileNotFound, "No file was found for #{scenario_name} within #{search_path}" if file.nil?
20
+ Pathname.new file
21
+ end
22
+
23
+ def slugify(path)
24
+ path.downcase.gsub(' ', '_')
25
+ end
26
+
27
+ def recursive_parent_search(path, file_name = nil, &block)
28
+ if block_given?
29
+ obj = yield path
30
+ return obj if obj
31
+ elsif file_name
32
+ file = File.expand_path(file_name, path)
33
+ logger.debug "Checking for #{file}"
34
+ found = File.exists? file
35
+ else
36
+ fail ArgumentError, 'Provide either a file_name to search for, or a block to check directories'
37
+ end
38
+
39
+ parent_dir = File.dirname(path)
40
+ return path if found
41
+ return nil if parent_dir == path # we've reached the top
42
+ recursive_parent_search(parent_dir, file_name, &block)
43
+ end
44
+
45
+ private
46
+
47
+ def read_gitignore(dir)
48
+ gitignore_file = "#{dir}/.gitignore"
49
+ File.read(gitignore_file)
50
+ rescue
51
+ ''
52
+ end
53
+
54
+ def ignored?(ignored_patterns, base_path, target_file)
55
+ # Trying to match the git ignore rules but there's some discrepencies.
56
+ ignored_patterns.split.find do |pattern|
57
+ # if git ignores a folder, we should ignore all files it contains
58
+ pattern = "#{pattern}**" if pattern[-1] == '/'
59
+ started_with_slash = pattern.start_with? '/'
60
+
61
+ pattern.gsub!(/\A\//, '') # remove leading slashes since we're searching from root
62
+ file = relativize(target_file, base_path)
63
+ ignored = file.fnmatch? pattern
64
+ ignored || (file.fnmatch? "**/#{pattern}" unless started_with_slash)
65
+ end
66
+ end
67
+
68
+ def relativize(file, base_path)
69
+ absolute_file = File.absolute_path(file)
70
+ absolute_base_path = File.absolute_path(base_path)
71
+ Pathname.new(absolute_file).relative_path_from Pathname.new(absolute_base_path)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -2,16 +2,44 @@ require 'hashie/dash'
2
2
  require 'hashie/extensions/coercion'
3
3
 
4
4
  module Polytrix
5
+ class FeatureNotImplementedError < StandardError
6
+ def initialize(feature)
7
+ super "Feature #{feature} is not implemented"
8
+ end
9
+ end
5
10
  class Implementor < Hashie::Dash
11
+ include Polytrix::Logger
12
+ include Polytrix::Core::FileSystemHelper
6
13
  include Hashie::Extensions::Coercion
14
+ include Polytrix::Executor
7
15
  property :name
8
- property :basedir
16
+ property :basedir, required: true
9
17
  property :language
10
18
  coerce_key :basedir, Pathname
11
19
 
12
20
  def initialize(data)
13
- data[:basedir] ||= "sdks/#{data[:name]}"
21
+ data = Hashie::Mash.new data
22
+ data[:name] ||= File.basename data[:basedir]
23
+ data[:basedir] = File.absolute_path(data[:basedir])
14
24
  super(data)
15
25
  end
26
+
27
+ def bootstrap
28
+ execute('./scripts/bootstrap', cwd: basedir, prefix: name)
29
+ rescue Errno::ENOENT
30
+ logger.warn "Skipping bootstrapping for #{name}, no script/bootstrap exists"
31
+ end
32
+
33
+ def build_challenge(challenge_data)
34
+ challenge_data[:source_file] ||= find_file basedir, challenge_data[:name]
35
+ challenge_data[:basedir] ||= basedir
36
+ challenge_data[:source_file] = relativize(challenge_data[:source_file], challenge_data[:basedir])
37
+ challenge_data[:implementor] ||= self
38
+ challenge_data[:suite] ||= ''
39
+ fail FeatureNotImplementedError, "#{name} is not setup" unless File.directory? challenge_data[:basedir]
40
+ Challenge.new challenge_data
41
+ rescue Polytrix::Core::FileSystemHelper::FileNotFound
42
+ raise FeatureNotImplementedError, challenge_data[:name]
43
+ end
16
44
  end
17
- end
45
+ end
@@ -0,0 +1,168 @@
1
+ require 'polytrix/documentation/comment_styles'
2
+
3
+ module Polytrix
4
+ module Documentation
5
+ # This class was extracted from the [Rocco](http://rtomayko.github.com/rocco/) project
6
+ # which was in turn based on the [Docco](http://jashkenas.github.com/docco/).
7
+ class CodeSegmenter # rubocop:disable all
8
+ # Cops are disabled because the code is from Rocco
9
+ include CommentStyles
10
+
11
+ DEFAULT_OPTIONS = {
12
+ language: 'rb',
13
+ comment_chars: '#'
14
+ }
15
+
16
+ def initialize(options = {})
17
+ @options = DEFAULT_OPTIONS.merge options
18
+ @options[:comment_chars] = generate_comment_chars if @options[:comment_chars].is_a? String
19
+ end
20
+ # Internal Parsing and Highlighting
21
+ # ---------------------------------
22
+
23
+ # Parse the raw file source_code into a list of two-tuples. Each tuple has the
24
+ # form `[docs, code]` where both elements are arrays containing the
25
+ # raw lines parsed from the input file, comment characters stripped.
26
+ def segment(source_code)
27
+ sections, docs, code = [], [], []
28
+ lines = source_code.split("\n")
29
+
30
+ # The first line is ignored if it is a shebang line. We also ignore the
31
+ # PEP 263 encoding information in python sourcefiles, and the similar ruby
32
+ # 1.9 syntax.
33
+ lines.shift if lines[0] =~ /^\#\!/
34
+ lines.shift if lines[0] =~ /coding[:=]\s*[-\w.]+/ &&
35
+ %w(python rb).include?(@options[:language])
36
+
37
+ # To detect both block comments and single-line comments, we'll set
38
+ # up a tiny state machine, and loop through each line of the file.
39
+ # This requires an `in_comment_block` boolean, and a few regular
40
+ # expressions for line tests. We'll do the same for fake heredoc parsing.
41
+ in_comment_block = false
42
+ in_heredoc = false
43
+ single_line_comment, block_comment_start, block_comment_mid, block_comment_end =
44
+ nil, nil, nil, nil
45
+ if not @options[:comment_chars][:single].nil?
46
+ single_line_comment = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:single])}\\s?")
47
+ end
48
+ if not @options[:comment_chars][:multi].nil?
49
+ block_comment_start = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*$")
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*$")
52
+ block_comment_start_with = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*(.*?)$")
53
+ block_comment_end_with = Regexp.new("\\s*(.*?)\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
54
+ if @options[:comment_chars][:multi][:middle]
55
+ block_comment_mid = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:middle])}\\s?")
56
+ end
57
+ end
58
+ if not @options[:comment_chars][:heredoc].nil?
59
+ heredoc_start = Regexp.new("#{Regexp.escape(@options[:comment_chars][:heredoc])}(\\S+)$")
60
+ end
61
+ lines.each do |line|
62
+ # If we're currently in a comment block, check whether the line matches
63
+ # the _end_ of a comment block or the _end_ of a comment block with a
64
+ # comment.
65
+ if in_comment_block
66
+ if block_comment_end && line.match(block_comment_end)
67
+ in_comment_block = false
68
+ elsif block_comment_end_with && line.match(block_comment_end_with)
69
+ in_comment_block = false
70
+ docs << line.match(block_comment_end_with).captures.first.
71
+ sub(block_comment_mid || '', '')
72
+ else
73
+ docs << line.sub(block_comment_mid || '', '')
74
+ end
75
+ # If we're currently in a heredoc, we're looking for the end of the
76
+ # heredoc, and everything it contains is code.
77
+ elsif in_heredoc
78
+ if line.match(Regexp.new("^#{Regexp.escape(in_heredoc)}$"))
79
+ in_heredoc = false
80
+ end
81
+ code << line
82
+ # Otherwise, check whether the line starts a heredoc. If so, note the end
83
+ # pattern, and the line is code. Otherwise check whether the line matches
84
+ # the beginning of a block, or a single-line comment all on it's lonesome.
85
+ # In either case, if there's code, start a new section.
86
+ else
87
+ if heredoc_start && line.match(heredoc_start)
88
+ in_heredoc = Regexp.last_match[1]
89
+ code << line
90
+ elsif block_comment_one_liner && line.match(block_comment_one_liner)
91
+ if code.any?
92
+ sections << [docs, code]
93
+ docs, code = [], []
94
+ end
95
+ docs << line.match(block_comment_one_liner).captures.first
96
+ elsif block_comment_start && line.match(block_comment_start)
97
+ in_comment_block = true
98
+ if code.any?
99
+ sections << [docs, code]
100
+ docs, code = [], []
101
+ end
102
+ elsif block_comment_start_with && line.match(block_comment_start_with)
103
+ in_comment_block = true
104
+ if code.any?
105
+ sections << [docs, code]
106
+ docs, code = [], []
107
+ end
108
+ docs << line.match(block_comment_start_with).captures.first
109
+ elsif single_line_comment && line.match(single_line_comment)
110
+ if code.any?
111
+ sections << [docs, code]
112
+ docs, code = [], []
113
+ end
114
+ docs << line.sub(single_line_comment || '', '')
115
+ else
116
+ code << line
117
+ end
118
+ end
119
+ end
120
+ sections << [docs, code] if docs.any? || code.any?
121
+ normalize_leading_spaces(sections)
122
+ end
123
+
124
+ # Normalizes documentation whitespace by checking for leading whitespace,
125
+ # removing it, and then removing the same amount of whitespace from each
126
+ # succeeding line. That is:
127
+ #
128
+ # def func():
129
+ # """
130
+ # Comment 1
131
+ # Comment 2
132
+ # """
133
+ # print "omg!"
134
+ #
135
+ # should yield a comment block of `Comment 1\nComment 2` and code of
136
+ # `def func():\n print "omg!"`
137
+ def normalize_leading_spaces(sections)
138
+ sections.map do |section|
139
+ if section.any? && section[0].any?
140
+ leading_space = section[0][0].match("^\s+")
141
+ if leading_space
142
+ section[0] =
143
+ section[0].map { |line| line.sub(/^#{leading_space.to_s}/, '') }
144
+ end
145
+ end
146
+ section
147
+ end
148
+ end
149
+
150
+ def comment(lines)
151
+ lines.map do | line |
152
+ "#{@options[:comment_chars][:single]} #{line}"
153
+ end.join "\n"
154
+ end
155
+
156
+ private
157
+
158
+ def generate_comment_chars
159
+ @_commentchar ||=
160
+ if COMMENT_STYLES[@options[:language]]
161
+ COMMENT_STYLES[@options[:language]]
162
+ else
163
+ { single: @options[:comment_chars], multi: nil, heredoc: nil }
164
+ end
165
+ end
166
+ end # rubocop:enable all
167
+ end
168
+ end
@@ -0,0 +1,87 @@
1
+ module Polytrix
2
+ module Documentation
3
+ # This class was extracted from the [Rocco](http://rtomayko.github.com/rocco/) project
4
+ # which was in turn based on the [Docco](http://jashkenas.github.com/docco/).
5
+ module CommentStyles
6
+ class UnknownStyleError < StandardError
7
+ attr_accessor :extension
8
+
9
+ def initialize(extension)
10
+ @extension = extension
11
+ end
12
+ end
13
+
14
+ def self.infer(extension)
15
+ extension.tr! '.', ''
16
+ return extension, COMMENT_STYLES[extension] if COMMENT_STYLES.key? extension
17
+
18
+ COMMENT_STYLES.each do | style_name, style |
19
+ return extension, style if style[:extensions].include? extension
20
+ end
21
+
22
+ fail UnknownStyleError, extension
23
+ end
24
+
25
+ C_STYLE_COMMENTS = {
26
+ single: '//',
27
+ multi: { start: '/**', middle: '*', end: '*/' },
28
+ heredoc: nil,
29
+ extensions: %w(c cpp cs java js php scala go)
30
+ }
31
+
32
+ COMMENT_STYLES = {
33
+ 'bash' => { single: '#', multi: nil, extensions: %w(sh) },
34
+ 'c' => C_STYLE_COMMENTS,
35
+ 'coffee-script' => {
36
+ single: '#',
37
+ multi: { start: '###', middle: nil, end: '###' },
38
+ heredoc: nil,
39
+ extensions: %w(coffee)
40
+ },
41
+ 'cpp' => C_STYLE_COMMENTS,
42
+ 'csharp' => C_STYLE_COMMENTS,
43
+ 'css' => {
44
+ single: nil,
45
+ multi: { start: '/**', middle: '*', end: '*/' },
46
+ heredoc: nil,
47
+ extensions: %w(css scss sass)
48
+ },
49
+ 'html' => {
50
+ single: nil,
51
+ multi: { start: '<!--', middle: nil, end: '-->' },
52
+ heredoc: nil,
53
+ extensions: %w(html htm)
54
+ },
55
+ 'java' => C_STYLE_COMMENTS,
56
+ 'js' => C_STYLE_COMMENTS,
57
+ 'lua' => {
58
+ single: '--',
59
+ multi: nil,
60
+ heredoc: nil,
61
+ extensions: %w(lua)
62
+ },
63
+ 'php' => C_STYLE_COMMENTS,
64
+ 'python' => {
65
+ single: '#',
66
+ multi: { start: '"""', middle: nil, end: '"""' },
67
+ heredoc: nil,
68
+ extensions: %w(py)
69
+ },
70
+ 'rb' => {
71
+ single: '#',
72
+ multi: { start: '=begin', middle: nil, end: '=end', idiomatic: false },
73
+ heredoc: '<<-',
74
+ extensions: %w(rb)
75
+ },
76
+ 'scala' => C_STYLE_COMMENTS,
77
+ 'scheme' => { single: ';;', multi: nil, heredoc: nil, extensions: %w(schema) },
78
+ 'xml' => {
79
+ single: nil,
80
+ multi: { start: '<!--', middle: nil, end: '-->' },
81
+ heredoc: nil,
82
+ extensions: %w(xml xsl xslt)
83
+ }
84
+ }
85
+ end
86
+ end
87
+ end