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.
- checksums.yaml +4 -4
- data/.gitignore +2 -2
- data/.rspec +1 -6
- data/.rubocop-todo.yml +19 -0
- data/.rubocop.yml +10 -0
- data/.travis.yml +11 -0
- data/Gemfile +0 -16
- data/README.md +119 -28
- data/Rakefile +18 -123
- data/bin/polytrix +5 -0
- data/doc-src/_markdown.md +5 -0
- data/doc-src/default_bootstrap.md +13 -0
- data/docs/influences.md +28 -0
- data/docs/samples/code2doc/java/HelloWorld.md +13 -0
- data/docs/samples/code2doc/java/Quine.md +33 -0
- data/docs/samples/code2doc/python/hello_world.md +3 -0
- data/docs/samples/code2doc/python/quine.md +4 -0
- data/docs/samples/code2doc/ruby/hello_world.md +7 -0
- data/features/execution.feature +67 -0
- data/features/fixtures/configs/empty.yml +12 -0
- data/features/fixtures/configs/hello_world.yml +10 -0
- data/features/fixtures/spec/polytrix_merge.rb +5 -0
- data/features/fixtures/spec/polytrix_spec.rb +10 -0
- data/features/reporting.feature +140 -0
- data/features/step_definitions/sdk_steps.rb +12 -0
- data/features/support/env.rb +8 -0
- data/lib/polytrix/challenge.rb +20 -7
- data/lib/polytrix/challenge_runner.rb +9 -44
- data/lib/polytrix/cli/add.rb +67 -0
- data/lib/polytrix/cli/report.rb +88 -0
- data/lib/polytrix/cli/reports/hash_reporter.rb +30 -0
- data/lib/polytrix/cli/reports/json_reporter.rb +14 -0
- data/lib/polytrix/cli/reports/markdown_reporter.rb +23 -0
- data/lib/polytrix/cli/reports/yaml_reporter.rb +14 -0
- data/lib/polytrix/cli.rb +158 -0
- data/lib/polytrix/configuration.rb +65 -4
- data/lib/polytrix/core/file_system_helper.rb +75 -0
- data/lib/polytrix/core/implementor.rb +31 -3
- data/lib/polytrix/documentation/code_segmenter.rb +168 -0
- data/lib/polytrix/documentation/comment_styles.rb +87 -0
- data/lib/polytrix/documentation/helpers/code_helper.rb +85 -0
- data/lib/polytrix/documentation/view_helper.rb +21 -0
- data/lib/polytrix/documentation_generator.rb +59 -10
- data/lib/polytrix/executor.rb +89 -0
- data/lib/polytrix/logger.rb +17 -0
- data/lib/polytrix/manifest.rb +64 -7
- data/lib/polytrix/result.rb +16 -2
- data/lib/polytrix/rspec/documentation_formatter.rb +41 -16
- data/lib/polytrix/rspec/yaml_report.rb +51 -0
- data/lib/polytrix/rspec.rb +32 -53
- data/lib/polytrix/runners/middleware/feature_executor.rb +4 -3
- data/lib/polytrix/runners/middleware/setup_env_vars.rb +6 -4
- data/lib/polytrix/validation.rb +20 -0
- data/lib/polytrix/validations.rb +23 -0
- data/lib/polytrix/validator.rb +20 -0
- data/lib/polytrix/validator_registry.rb +34 -0
- data/lib/polytrix/version.rb +1 -1
- data/lib/polytrix.rb +125 -22
- data/polytrix.gemspec +7 -2
- data/polytrix.rb +6 -0
- data/polytrix_tests.yml +20 -0
- data/resources/code_sample.tt +2 -0
- data/samples/.gitignore +2 -0
- data/samples/_markdown.md +5 -0
- data/samples/default_bootstrap.rb +14 -0
- data/samples/polytrix.rb +28 -0
- data/samples/polytrix_cli.sh +7 -0
- data/samples/polytrix_tests.yml +10 -0
- data/{sdks/fog → samples}/scripts/bootstrap +0 -2
- data/samples/scripts/wrapper +7 -0
- data/samples/sdks/custom/polytrix.yml +2 -0
- data/samples/sdks/java/.gitignore +2 -0
- data/samples/sdks/java/build.gradle +14 -0
- data/samples/sdks/java/challenges/HelloWorld.java +10 -0
- data/samples/sdks/java/challenges/Quine.java +31 -0
- data/samples/sdks/java/code_sample.tt +11 -0
- data/samples/sdks/java/scripts/bootstrap +2 -0
- data/samples/sdks/java/scripts/wrapper +8 -0
- data/samples/sdks/python/challenges/hello_world.py +2 -0
- data/samples/sdks/python/challenges/quine.py +2 -0
- data/{sdks/pkgcloud → samples/sdks/python}/scripts/wrapper +1 -1
- data/samples/sdks/ruby/challenges/hello_world.rb +4 -0
- data/scripts/bootstrap +1 -9
- data/scripts/wrapper +7 -0
- data/spec/fabricators/challenge_fabricator.rb +17 -0
- data/spec/fabricators/manifest_fabricator.rb +50 -0
- data/spec/fabricators/validator_fabricator.rb +12 -0
- data/spec/fixtures/{polytrix.yml → polytrix_tests.yml} +0 -0
- data/spec/fixtures/src-doc/_scenario.md.erb +1 -0
- data/spec/polytrix/challenge_runner_spec.rb +3 -3
- data/spec/polytrix/challenge_spec.rb +3 -4
- data/spec/polytrix/cli_spec.rb +39 -0
- data/spec/polytrix/configuration_spec.rb +45 -1
- data/spec/polytrix/documentation/helpers/code_helper_spec.rb +120 -0
- data/spec/polytrix/documentation_generator_spec.rb +41 -20
- data/spec/polytrix/file_finder_spec.rb +4 -3
- data/spec/polytrix/implementor_spec.rb +33 -0
- data/spec/polytrix/manifest_spec.rb +32 -14
- data/spec/polytrix/middleware/feature_executor_spec.rb +1 -1
- data/spec/polytrix/result_spec.rb +49 -0
- data/spec/polytrix/validations_spec.rb +16 -0
- data/spec/polytrix/validator_registry_spec.rb +39 -0
- data/spec/polytrix/validator_spec.rb +63 -0
- data/spec/polytrix_spec.rb +33 -7
- data/spec/spec_helper.rb +14 -1
- data/spec/thor_spy.rb +64 -0
- metadata +177 -160
- data/.rspec_parallel +0 -10
- data/Vagrantfile +0 -41
- data/features/0_identity_spec.rb +0 -40
- data/features/1_cloud_files_spec.rb +0 -48
- data/features/2_servers_spec.rb +0 -19
- data/features/features_helper.rb +0 -46
- data/features/helpers/cloudfiles_helper.rb +0 -31
- data/features/helpers/pacto_helper.rb +0 -33
- data/features/helpers/teardown_helper.rb +0 -49
- data/features/pacto/extensions/loaders/api_blueprint_loader.rb +0 -63
- data/features/pacto/extensions/loaders/simple_loader.rb +0 -55
- data/features/pacto/extensions/loaders/yaml_or_json_loader.rb +0 -17
- data/features/pacto/extensions/matchers.rb +0 -38
- data/features/phase2/feature_coverage_report.rb +0 -109
- data/features/phase2/run_all_features.rb +0 -14
- data/features/static_site/fixtures/index.html +0 -6
- data/lib/polytrix/challenge_builder.rb +0 -16
- data/lib/polytrix/core/file_finder.rb +0 -43
- data/lib/polytrix/core/result_tracker.rb +0 -25
- data/lib/polytrix/runners/middleware/pacto.rb +0 -59
- data/packer/.gitignore +0 -3
- data/packer/Berksfile +0 -15
- data/packer/Gemfile +0 -5
- data/packer/Vagrantfile +0 -128
- data/packer/cookbooks/drg/metadata.rb +0 -27
- data/packer/cookbooks/drg/recipes/admins.rb +0 -22
- data/packer/cookbooks/drg/recipes/default.rb +0 -9
- data/packer/cookbooks/drg/recipes/dotnet.rb +0 -4
- data/packer/cookbooks/drg/recipes/golang.rb +0 -4
- data/packer/cookbooks/drg/recipes/java.rb +0 -5
- data/packer/cookbooks/drg/recipes/php.rb +0 -10
- data/packer/cookbooks/drg/recipes/ruby.rb +0 -29
- data/packer/cookbooks/drg/recipes/system.rb +0 -13
- data/packer/create_box.sh +0 -10
- data/packer/http/preseed.cfg +0 -87
- data/packer/packer.json +0 -91
- data/packer/scripts/root_setup.sh +0 -37
- data/packer/scripts/setup.sh +0 -32
- data/pacto/config/pacto_server.rb +0 -40
- data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/extensions.json +0 -64
- data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/flavors/id.json +0 -100
- data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/images/id.json +0 -176
- data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/servers/id.json +0 -189
- data/pacto/contracts/dfw.servers.api.rackspacecloud.com/v2/account_id/servers.json +0 -63
- data/pacto/contracts/dns.api.rackspacecloud.com/v1.0/_tenant_id/domains.json +0 -62
- data/pacto/contracts/identity.api.rackspacecloud.com/v2.0/tokens.json +0 -192
- data/pacto/contracts/monitoring.api.rackspacecloud.com/v1.0/_tenant_id/account.json +0 -39
- data/pacto/contracts/ord.autoscale.api.rackspacecloud.com/v1.0/_tenant_id/groups.json +0 -38
- data/pacto/contracts/ord.blockstorage.api.rackspacecloud.com/v1/_tenant_id/volumes.json +0 -30
- data/pacto/contracts/ord.databases.api.rackspacecloud.com/v1.0/_tenant_id/instances.json +0 -30
- data/pacto/contracts/ord.loadbalancers.api.rackspacecloud.com/v1.0/_tenant_id/loadbalancers.json +0 -114
- data/pacto/contracts/ord.queues.api.rackspacecloud.com/v1/_tenant_id/queues.json +0 -13
- data/pacto/contracts/ord.servers.api.rackspacecloud.com/v2/_tenant_id/os-networksv2.json +0 -46
- data/pacto/contracts/ord.servers.api.rackspacecloud.com/v2/_tenant_id/servers/detail.json +0 -230
- data/pacto/contracts/storage101.dfw1.clouddrive.com/v1/mosso_account/container/object.json +0 -15
- data/pacto/contracts/storage101.dfw1.clouddrive.com/v1/mosso_account.json +0 -43
- data/pacto/contracts/storage101.ord1.clouddrive.com/v1/_mosso_id.json +0 -44
- data/pacto/pacto_server.rb +0 -100
- data/pacto/rackspace_uri_map.yaml +0 -229
- data/scripts/cibuild +0 -4
- data/sdks/fog/.gitignore +0 -1
- data/sdks/fog/Gemfile +0 -5
- data/sdks/fog/challenges/all_connections.rb +0 -45
- data/sdks/fog/challenges/authenticate_token.rb +0 -15
- data/sdks/fog/challenges/cdn_enable_container.rb +0 -20
- data/sdks/fog/challenges/create_a_container.rb +0 -17
- data/sdks/fog/challenges/create_server.rb +0 -36
- data/sdks/fog/challenges/get_object_metadata.rb +0 -13
- data/sdks/fog/challenges/list_containers.rb +0 -10
- data/sdks/fog/challenges/provision_scalable_webapp.rb +0 -30
- data/sdks/fog/challenges/upload_folder.rb +0 -25
- data/sdks/fog/scripts/bootstrap.ps1 +0 -1
- data/sdks/fog/scripts/wrapper +0 -2
- data/sdks/fog/scripts/wrapper.ps1 +0 -1
- data/sdks/gophercloud/.gitignore +0 -2
- data/sdks/gophercloud/challenges/authenticate_token.go +0 -23
- data/sdks/gophercloud/scripts/bootstrap +0 -6
- data/sdks/gophercloud/scripts/wrapper +0 -10
- data/sdks/jclouds/.gitignore +0 -1
- data/sdks/jclouds/challenges/AuthenticateToken.java +0 -115
- data/sdks/jclouds/pom.xml +0 -34
- data/sdks/jclouds/scripts/bootstrap +0 -3
- data/sdks/jclouds/scripts/wrapper +0 -7
- data/sdks/openstack.net/.gitignore +0 -4
- data/sdks/openstack.net/.nuget/Microsoft.Build.dll +0 -0
- data/sdks/openstack.net/.nuget/NuGet.Config +0 -6
- data/sdks/openstack.net/.nuget/NuGet.exe +0 -0
- data/sdks/openstack.net/.nuget/NuGet.targets +0 -136
- data/sdks/openstack.net/Challenge.cs +0 -10
- data/sdks/openstack.net/RunChallenge.cs +0 -19
- data/sdks/openstack.net/challenges/AuthenticateToken.cs +0 -24
- data/sdks/openstack.net/challenges/Weird.cs +0 -133
- data/sdks/openstack.net/openstack.net.csproj +0 -58
- data/sdks/openstack.net/openstack.net.sln +0 -27
- data/sdks/openstack.net/openstack.net.userprefs +0 -8
- data/sdks/openstack.net/packages.config +0 -6
- data/sdks/openstack.net/scripts/bootstrap +0 -2
- data/sdks/openstack.net/scripts/bootstrap.ps1 +0 -2
- data/sdks/openstack.net/scripts/wrapper +0 -7
- data/sdks/openstack.net/scripts/wrapper.ps1 +0 -1
- data/sdks/php-opencloud/.gitignore +0 -4
- data/sdks/php-opencloud/challenges/all_connections.php +0 -64
- data/sdks/php-opencloud/challenges/authenticate_token.php +0 -14
- data/sdks/php-opencloud/challenges/create_server.php +0 -39
- data/sdks/php-opencloud/challenges/get_object_metadata.php +0 -19
- data/sdks/php-opencloud/composer.json +0 -5
- data/sdks/php-opencloud/scripts/bootstrap +0 -4
- data/sdks/php-opencloud/scripts/bootstrap.ps1 +0 -2
- data/sdks/php-opencloud/scripts/wrapper +0 -2
- data/sdks/php-opencloud/scripts/wrapper.ps1 +0 -1
- data/sdks/pkgcloud/.gitignore +0 -1
- data/sdks/pkgcloud/challenges/authenticate_token.js +0 -17
- data/sdks/pkgcloud/challenges/get_object_metadata.js +0 -18
- data/sdks/pkgcloud/scripts/bootstrap +0 -2
- data/sdks/pkgcloud/scripts/bootstrap.ps1 +0 -1
- data/sdks/pkgcloud/scripts/wrapper.ps1 +0 -1
- data/sdks/pyrax/.gitignore +0 -2
- data/sdks/pyrax/challenges/all_connections.py +0 -61
- data/sdks/pyrax/challenges/authenticate_token.py +0 -17
- data/sdks/pyrax/challenges/cdn_enable_container.py +0 -22
- data/sdks/pyrax/challenges/create_a_container.py +0 -21
- data/sdks/pyrax/challenges/create_server.py +0 -35
- data/sdks/pyrax/challenges/get_object_metadata.py +0 -17
- data/sdks/pyrax/challenges/upload_folder.py +0 -32
- data/sdks/pyrax/requirements.txt +0 -21
- data/sdks/pyrax/scripts/bootstrap +0 -9
- data/sdks/pyrax/scripts/bootstrap.ps1 +0 -7
- data/sdks/pyrax/scripts/wrapper +0 -3
- data/sdks/pyrax/scripts/wrapper.ps1 +0 -2
- data/spec/polytrix/challenge_builder_spec.rb +0 -16
- data/spec/rspec_spec.rb +0 -17
data/lib/polytrix/cli.rb
ADDED
|
@@ -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 :
|
|
26
|
-
property :
|
|
27
|
-
property :
|
|
28
|
-
|
|
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
|
|
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
|