travis-yaml 0.1.0

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 (75) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +14 -0
  5. data/Gemfile +9 -0
  6. data/Gemfile.lock +73 -0
  7. data/LICENSE +22 -0
  8. data/README.md +232 -0
  9. data/Rakefile +3 -0
  10. data/SPEC.md +1018 -0
  11. data/bench/parser_bench.rb +54 -0
  12. data/config.ru +2 -0
  13. data/lib/travis/yaml.rb +43 -0
  14. data/lib/travis/yaml/matrix.rb +65 -0
  15. data/lib/travis/yaml/nodes.rb +40 -0
  16. data/lib/travis/yaml/nodes/branches.rb +12 -0
  17. data/lib/travis/yaml/nodes/bundler_args.rb +6 -0
  18. data/lib/travis/yaml/nodes/cache.rb +29 -0
  19. data/lib/travis/yaml/nodes/compiler.rb +7 -0
  20. data/lib/travis/yaml/nodes/compiler_entry.rb +9 -0
  21. data/lib/travis/yaml/nodes/deploy.rb +7 -0
  22. data/lib/travis/yaml/nodes/deploy_conditions.rb +12 -0
  23. data/lib/travis/yaml/nodes/deploy_entry.rb +10 -0
  24. data/lib/travis/yaml/nodes/env.rb +36 -0
  25. data/lib/travis/yaml/nodes/fixed_value.rb +60 -0
  26. data/lib/travis/yaml/nodes/git.rb +9 -0
  27. data/lib/travis/yaml/nodes/jdk.rb +11 -0
  28. data/lib/travis/yaml/nodes/language.rb +18 -0
  29. data/lib/travis/yaml/nodes/language_specific.rb +45 -0
  30. data/lib/travis/yaml/nodes/mapping.rb +204 -0
  31. data/lib/travis/yaml/nodes/matrix.rb +36 -0
  32. data/lib/travis/yaml/nodes/node.rb +102 -0
  33. data/lib/travis/yaml/nodes/notifications.rb +77 -0
  34. data/lib/travis/yaml/nodes/open_mapping.rb +18 -0
  35. data/lib/travis/yaml/nodes/os.rb +21 -0
  36. data/lib/travis/yaml/nodes/os_entry.rb +19 -0
  37. data/lib/travis/yaml/nodes/root.rb +44 -0
  38. data/lib/travis/yaml/nodes/ruby.rb +11 -0
  39. data/lib/travis/yaml/nodes/scalar.rb +97 -0
  40. data/lib/travis/yaml/nodes/sequence.rb +84 -0
  41. data/lib/travis/yaml/nodes/stage.rb +6 -0
  42. data/lib/travis/yaml/nodes/version.rb +6 -0
  43. data/lib/travis/yaml/nodes/version_list.rb +7 -0
  44. data/lib/travis/yaml/nodes/virtual_env.rb +7 -0
  45. data/lib/travis/yaml/parser.rb +31 -0
  46. data/lib/travis/yaml/parser/dummy.rb +13 -0
  47. data/lib/travis/yaml/parser/psych.rb +217 -0
  48. data/lib/travis/yaml/parser/ruby.rb +77 -0
  49. data/lib/travis/yaml/secure_string.rb +12 -0
  50. data/lib/travis/yaml/version.rb +5 -0
  51. data/play/lint.rb +8 -0
  52. data/play/spec.rb +183 -0
  53. data/play/weblint.rb +296 -0
  54. data/spec/nodes/.rb +0 -0
  55. data/spec/nodes/branches_spec.rb +45 -0
  56. data/spec/nodes/bundler_args_spec.rb +9 -0
  57. data/spec/nodes/cache_spec.rb +55 -0
  58. data/spec/nodes/compiler_spec.rb +14 -0
  59. data/spec/nodes/deploy_spec.rb +83 -0
  60. data/spec/nodes/git_spec.rb +55 -0
  61. data/spec/nodes/jdk_spec.rb +41 -0
  62. data/spec/nodes/language_spec.rb +79 -0
  63. data/spec/nodes/notifications_spec.rb +45 -0
  64. data/spec/nodes/os_spec.rb +28 -0
  65. data/spec/nodes/ruby_spec.rb +69 -0
  66. data/spec/nodes/stage_spec.rb +34 -0
  67. data/spec/nodes/virtual_env_spec.rb +9 -0
  68. data/spec/parser/dummy_spec.rb +7 -0
  69. data/spec/parser/ruby_spec.rb +41 -0
  70. data/spec/support.rb +3 -0
  71. data/spec/support/coverage.rb +11 -0
  72. data/spec/support/environment.rb +1 -0
  73. data/spec/yaml_spec.rb +26 -0
  74. data/travis-yaml.gemspec +24 -0
  75. metadata +207 -0
@@ -0,0 +1,45 @@
1
+ module Travis::Yaml
2
+ module Nodes
3
+ module LanguageSpecific
4
+ LANGUAGE_SPECIFIC = {
5
+ bundler_args: %w[ruby],
6
+ compiler: %w[c cpp],
7
+ lein: %w[clojure],
8
+ otp_release: %w[erlang],
9
+ gobuild_args: %w[go],
10
+ go: %w[go],
11
+ jdk: %w[clojure groovy java ruby scala android],
12
+ ghc: %w[haskell],
13
+ node_js: %w[node_js],
14
+ ruby: %w[ruby objective-c],
15
+ xcode_sdk: %w[objective-c],
16
+ xcode_scheme: %w[objective-c],
17
+ xcode_project: %w[objective-c],
18
+ xcode_workspace: %w[objective-c],
19
+ xctool_args: %w[objective-c],
20
+ perl: %w[perl],
21
+ php: %w[php],
22
+ python: %w[python],
23
+ virtualenv: %w[python],
24
+ gemfile: %w[ruby objective-c],
25
+ composer_args: %w[php],
26
+ npm_args: %w[node_js],
27
+ sdk_components: %w[android]
28
+ }
29
+
30
+ def verify_language(language)
31
+ LANGUAGE_SPECIFIC.each do |key, languages|
32
+ next unless include? key and not languages.include? language
33
+ mapping.delete mapped_key(key)
34
+ warning "specified %p, but setting is not relevant for %p", key.to_s, language
35
+ end
36
+
37
+ mapping.each_value do |value|
38
+ value.verify_language(language) if value.respond_to? :verify_language
39
+ end
40
+
41
+ verify_errors
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,204 @@
1
+ module Travis::Yaml
2
+ module Nodes
3
+ class Mapping < Node
4
+ def self.mapping
5
+ @mapping ||= superclass.respond_to?(:mapping) ? superclass.mapping.dup : {}
6
+ end
7
+
8
+ def self.required
9
+ @required ||= superclass.respond_to?(:required) ? superclass.required.dup : []
10
+ end
11
+
12
+ def self.experimental
13
+ @experimental ||= superclass.respond_to?(:experimental) ? superclass.experimental.dup : []
14
+ end
15
+
16
+ def self.drop_empty
17
+ @drop_empty ||= superclass.respond_to?(:drop_empty) ? superclass.drop_empty.dup : []
18
+ end
19
+
20
+ def self.aliases
21
+ @aliases ||= superclass.respond_to?(:aliases) ? superclass.aliases.dup : {}
22
+ end
23
+
24
+ def self.map(*list)
25
+ options = Hash === list.last ? list.pop : {}
26
+ list.each do |key|
27
+ drop_empty << key.to_s if options.fetch(:drop_empty, true)
28
+ required << key.to_s if options[:required]
29
+ experimental << key.to_s if options[:experimental]
30
+ define_map_accessor(key)
31
+ case options[:to]
32
+ when Symbol then aliases[key.to_s] = options[:to].to_s
33
+ when Module then mapping[key.to_s] = options[:to]
34
+ when nil then mapping[key.to_s] = Nodes[key]
35
+ else raise ArgumentError, 'unexpected value for to: %p' % options[:to]
36
+ end
37
+ end
38
+ end
39
+
40
+ def self.auto_prefix(key)
41
+ prefix_sequence(key)
42
+ prefix_scalar(key)
43
+ end
44
+
45
+ def self.prefix_sequence(key)
46
+ define_method(:visit_sequence) do |visitor, value|
47
+ visit_key_value(visitor, key, value)
48
+ end
49
+ end
50
+
51
+ def self.prefix_scalar(key, *types)
52
+ define_method(:visit_scalar) do |visitor, type, value, implicit = true|
53
+ return super(visitor, type, value, implicit = true) if types.any? and not types.include?(type)
54
+ visit_key_value(visitor, key, value)
55
+ end
56
+ end
57
+
58
+ def self.define_map_accessor(key)
59
+ define_method(key) { | | self[key] } unless method_defined? key
60
+ define_method("#{key}=") { |v| self[key] = v } unless method_defined? "#{key}="
61
+ define_method("#{key}?") { | | !!self[key] } unless method_defined? "#{key}?"
62
+ end
63
+
64
+ def self.subnode_for(key)
65
+ mapping[aliases.fetch(key.to_s, key.to_s)]
66
+ end
67
+
68
+ attr_reader :mapping
69
+ alias_method :__getobj__, :mapping
70
+
71
+ def prepare
72
+ @mapping = {}
73
+ super
74
+ end
75
+
76
+ def visit_mapping(visitor, value)
77
+ visitor.apply_mapping(self, value)
78
+ end
79
+
80
+ def visit_pair(visitor, key, value)
81
+ key = visitor.generate_key(self, key)
82
+ visit_key_value(visitor, key, value)
83
+ end
84
+
85
+ def visit_key_value(visitor, key, value)
86
+ return warning("unexpected key %p, dropping", key) unless node = subnode_for(key)
87
+ self[key] = node
88
+ visitor.accept(node, value)
89
+ end
90
+
91
+ def drop_empty?(key)
92
+ self.class.drop_empty.include? mapped_key(key)
93
+ end
94
+
95
+ def []=(key, value)
96
+ if key = mapped_key(key)
97
+ unless value.is_a? Node
98
+ node = subnode_for(key)
99
+ value = node if Parser::Ruby.new(value).parse(node)
100
+ end
101
+ @mapping[key] = value
102
+ else
103
+ warning("unexpected key %p, dropping", key)
104
+ end
105
+ end
106
+
107
+ def [](key)
108
+ @mapping[mapped_key(key)]
109
+ end
110
+
111
+ def include?(key)
112
+ @mapping.include? mapped_key(key)
113
+ end
114
+
115
+ def empty?
116
+ @mapping.empty?
117
+ end
118
+
119
+ def mapped_key(key)
120
+ key = self.class.aliases.fetch(key.to_s, key.to_s)
121
+ key if accept_key?(key)
122
+ end
123
+
124
+ def accept_key?(key)
125
+ self.class.mapping.include? key
126
+ end
127
+
128
+ def subnode_for(key)
129
+ type = self.class.subnode_for(key)
130
+ type.new(self) if type
131
+ end
132
+
133
+ def inspect
134
+ @mapping.inspect
135
+ end
136
+
137
+ def ==(other)
138
+ other = other.mapping if other.is_a? Mapping
139
+ if other.respond_to? :to_hash and other.to_hash.size == @mapping.size
140
+ other.to_hash.all? { |k, v| include?(k) and self[k] == v }
141
+ else
142
+ false
143
+ end
144
+ end
145
+
146
+ def verify
147
+ verify_experimental
148
+ verify_errors
149
+ verify_empty
150
+ verify_required
151
+ verify_errors
152
+ end
153
+
154
+ def verify_experimental
155
+ self.class.experimental.each do |key|
156
+ next unless @mapping.include? key
157
+ warning "%p is experimental and might be removed in the future", key
158
+ end
159
+ end
160
+
161
+ def verify_empty
162
+ @mapping.delete_if do |key, value|
163
+ next unless drop_empty? key and value.empty?
164
+ value.nested_warnings.each { |p, w| nested_warning(w, key, *p) }
165
+ warning('value for %p section is empty, dropping', key)
166
+ true
167
+ end
168
+ end
169
+
170
+ def verify_required
171
+ self.class.required.each do |key|
172
+ next if @mapping.include? key
173
+ type = self.class.subnode_for(key)
174
+ if type.has_default?
175
+ warning "missing key %p, defaulting to %p", key, type.default
176
+ @mapping[key] = type.new(self)
177
+ else
178
+ error "missing key %p", key
179
+ end
180
+ end
181
+ end
182
+
183
+ def verify_errors
184
+ @mapping.delete_if do |key, value|
185
+ if value.errors?
186
+ warning "dropping %p section: %s", key, value.errors.join(', ')
187
+ true
188
+ end
189
+ end
190
+ end
191
+
192
+ def deep_verify
193
+ @mapping.each_value(&:deep_verify)
194
+ super
195
+ end
196
+
197
+ def nested_warnings(*prefix)
198
+ @mapping.inject(super) do |list, (key, value)|
199
+ list = value.nested_warnings(*prefix, key) + list
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,36 @@
1
+ module Travis::Yaml
2
+ module Nodes
3
+ class Matrix < Mapping
4
+ class Matcher < Mapping
5
+ include LanguageSpecific
6
+
7
+ map :ruby, :jdk, :lein, :otp_release, :go, :ghc, :node_js, :xcode_sdk,
8
+ :xcode_scheme, :perl, :php, :python, :gemfile, to: Version
9
+ map :rvm, to: :ruby
10
+ map :otp, to: :otp_release
11
+ map :node, to: :node_js
12
+ map :env, to: Env::Variables
13
+ map :os, to: OSEntry
14
+ map :compiler, to: CompilerEntry
15
+ end
16
+
17
+ class MatcherList < Sequence
18
+ type Matcher
19
+
20
+ def verify_language(language)
21
+ children.each { |c| c.verify_language(language) }
22
+ verify_children
23
+ end
24
+ end
25
+
26
+ map :include, :exclude, :allow_failures, to: MatcherList
27
+ map :fast_finish, to: Scalar[:bool]
28
+
29
+ def verify_language(language)
30
+ values.each { |v| v.verify_language(language) }
31
+ verify_empty
32
+ verify_errors
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,102 @@
1
+ module Travis::Yaml
2
+ module Nodes
3
+ class Node
4
+ def self.has_default?
5
+ false
6
+ end
7
+
8
+ attr_accessor :partent
9
+ def initialize(parent)
10
+ @nested_warnings = []
11
+ @parent = parent
12
+ prepare
13
+ yield self if block_given?
14
+ end
15
+
16
+ def warngings?
17
+ warnings.any?
18
+ end
19
+
20
+ def errors?
21
+ errors.any?
22
+ end
23
+
24
+ def warnings
25
+ @warnings ||= []
26
+ end
27
+
28
+ def nested_warning(message, *prefix)
29
+ @nested_warnings << [prefix, message]
30
+ end
31
+
32
+ def nested_warnings(*prefix)
33
+ messages = errors + warnings
34
+ prefixed = messages.map { |warning| [prefix, warning] }
35
+ prefixed += @nested_warnings.map { |p, w| [prefix + p, w] }
36
+ prefixed
37
+ end
38
+
39
+ def errors
40
+ @errors ||= []
41
+ end
42
+
43
+ def warning(message, *params)
44
+ warnings << message % params
45
+ end
46
+
47
+ def error(message, *params)
48
+ errors << message % params
49
+ end
50
+
51
+ def prepare
52
+ end
53
+
54
+ def verify
55
+ end
56
+
57
+ def verify_language(language)
58
+ end
59
+
60
+ def deep_verify
61
+ verify
62
+ end
63
+
64
+ def visit_unexpected(visitor, value, message = nil)
65
+ error(message || "unexpected %p", value)
66
+ end
67
+
68
+ def visit_mapping(visitor, value)
69
+ error("unexpected mapping")
70
+ end
71
+
72
+ def visit_pair(visitor, key, value)
73
+ error("unexpected pair")
74
+ end
75
+
76
+ def visit_scalar(visitor, type, value, implicit = true)
77
+ error("unexpected scalar") unless type == :null
78
+ end
79
+
80
+ def visit_sequence(visitor, value)
81
+ error("unexpected sequence")
82
+ end
83
+
84
+ def visit_child(visitor, value)
85
+ error("unexpected child")
86
+ end
87
+
88
+ def respond_to_missing?(method, include_private = false)
89
+ __getobj__.respond_to?(method, false)
90
+ end
91
+
92
+ def method_missing(method, *args, &block)
93
+ return super unless __getobj__.respond_to?(method)
94
+ __getobj__.public_send(method, *args, &block)
95
+ end
96
+
97
+ def to_s
98
+ __getobj__.to_s
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,77 @@
1
+ module Travis::Yaml
2
+ module Nodes
3
+ class Notifications < Mapping
4
+ Callbacks ||= FixedValue[:always, :never, :change]
5
+
6
+ class Notification < Mapping
7
+ map :enabled, :disabled, to: Scalar[:bool]
8
+ map :on_success, :on_failure, :on_start, to: Callbacks
9
+
10
+ def self.[](name)
11
+ Class.new(self) { list(name) }
12
+ end
13
+
14
+ def self.list(name)
15
+ map name, to: Sequence
16
+ prefix_sequence name
17
+ prefix_scalar name, :str, :secure
18
+ end
19
+
20
+ def visit_scalar(visitor, type, value, implicit = true)
21
+ return super unless type == :bool
22
+ visit_key_value(visitor, :enabled, value)
23
+ end
24
+
25
+ def enabled?
26
+ @mapping.fetch('enabled', !@mapping['disabled'])
27
+ end
28
+
29
+ def disabled?
30
+ !enabled?
31
+ end
32
+ end
33
+
34
+ class Template < Sequence
35
+ VARIABLES = %w[repository_slug repository_name repository build_number branch commit author message duration compare_url build_url]
36
+
37
+ def verify
38
+ super
39
+ @children.each do |child|
40
+ child.to_s.scan(/%{([^}]+)}/) do |match|
41
+ next if VARIABLES.include? match.first
42
+ warning 'unknown "template" variable %p', match.first
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ class WithTemplate < Notification
49
+ map :template, to: Template
50
+ end
51
+
52
+ class IRC < WithTemplate
53
+ list :channels
54
+ map :channel_key, :password, :nickserv_password, :nick, to: Scalar[:str, :secure]
55
+ map :use_notice, :skip_join, to: Scalar[:bool]
56
+ end
57
+
58
+ class Hipchat < WithTemplate
59
+ map :format, to: FixedValue[:html, :text]
60
+ list :rooms
61
+ end
62
+
63
+ class Flowdoc < Notification
64
+ map :api_token, to: Scalar[:str, :secure]
65
+ prefix_scalar name, :str, :secure
66
+ end
67
+
68
+ map :webhooks, to: Notification[:urls]
69
+ map :email, to: Notification[:recipients]
70
+ map :sqwiggle, :slack, :campfire, to: WithTemplate[:rooms]
71
+ map :flowdoc, to: Flowdoc
72
+ map :hipchat, to: Hipchat
73
+ map :irc, to: IRC
74
+ map :webhook, to: :webhooks
75
+ end
76
+ end
77
+ end