travis-yaml 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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