travis-yaml 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -8
  3. data/Gemfile +3 -6
  4. data/Gemfile.lock +20 -50
  5. data/README.md +97 -2
  6. data/SPEC.md +116 -33
  7. data/lib/travis/yaml.rb +6 -3
  8. data/lib/travis/yaml/matrix.rb +12 -17
  9. data/lib/travis/yaml/nodes.rb +4 -0
  10. data/lib/travis/yaml/nodes/addons.rb +44 -0
  11. data/lib/travis/yaml/nodes/android.rb +7 -0
  12. data/lib/travis/yaml/nodes/cache.rb +5 -3
  13. data/lib/travis/yaml/nodes/deploy_conditions.rb +2 -1
  14. data/lib/travis/yaml/nodes/deploy_entry.rb +46 -0
  15. data/lib/travis/yaml/nodes/dist.rb +6 -0
  16. data/lib/travis/yaml/nodes/env.rb +1 -1
  17. data/lib/travis/yaml/nodes/group.rb +6 -0
  18. data/lib/travis/yaml/nodes/language.rb +3 -2
  19. data/lib/travis/yaml/nodes/language_specific.rb +2 -2
  20. data/lib/travis/yaml/nodes/mapping.rb +39 -10
  21. data/lib/travis/yaml/nodes/node.rb +58 -1
  22. data/lib/travis/yaml/nodes/notifications.rb +10 -6
  23. data/lib/travis/yaml/nodes/open_mapping.rb +1 -1
  24. data/lib/travis/yaml/nodes/os.rb +1 -1
  25. data/lib/travis/yaml/nodes/os_entry.rb +7 -5
  26. data/lib/travis/yaml/nodes/root.rb +28 -4
  27. data/lib/travis/yaml/nodes/scalar.rb +16 -1
  28. data/lib/travis/yaml/nodes/sequence.rb +38 -0
  29. data/lib/travis/yaml/nodes/version.rb +13 -0
  30. data/lib/travis/yaml/nodes/version_list.rb +4 -0
  31. data/lib/travis/yaml/parser/psych.rb +11 -5
  32. data/lib/travis/yaml/secure_string.rb +35 -2
  33. data/lib/travis/yaml/serializer.rb +17 -0
  34. data/lib/travis/yaml/serializer/generic.rb +114 -0
  35. data/lib/travis/yaml/serializer/json.rb +72 -0
  36. data/lib/travis/yaml/serializer/legacy.rb +32 -0
  37. data/lib/travis/yaml/serializer/ruby.rb +13 -0
  38. data/lib/travis/yaml/serializer/yaml.rb +41 -0
  39. data/lib/travis/yaml/version.rb +1 -1
  40. data/play/spec.rb +24 -17
  41. data/spec/matrix_spec.rb +57 -0
  42. data/spec/nodes/addons_spec.rb +63 -0
  43. data/spec/nodes/cache_spec.rb +4 -4
  44. data/spec/nodes/deploy_spec.rb +12 -0
  45. data/spec/nodes/dist_spec.rb +11 -0
  46. data/spec/nodes/env_spec.rb +48 -0
  47. data/spec/nodes/git_spec.rb +1 -1
  48. data/spec/nodes/group_spec.rb +11 -0
  49. data/spec/nodes/node_js_spec.rb +14 -0
  50. data/spec/nodes/notifications_spec.rb +7 -0
  51. data/spec/nodes/os_spec.rb +13 -0
  52. data/spec/nodes/root_spec.rb +36 -0
  53. data/spec/nodes/secure_spec.rb +145 -0
  54. data/spec/parser/psych_spec.rb +6 -0
  55. data/spec/parser/ruby_spec.rb +1 -1
  56. data/spec/serializer/json_spec.rb +30 -0
  57. data/spec/serializer/legacy_spec.rb +47 -0
  58. data/spec/serializer/ruby_spec.rb +21 -0
  59. data/spec/serializer/yaml_spec.rb +47 -0
  60. data/spec/support/coverage.rb +9 -9
  61. data/spec/yaml_spec.rb +23 -0
  62. data/travis-yaml.gemspec +2 -3
  63. metadata +42 -22
  64. data/config.ru +0 -2
  65. data/play/weblint.rb +0 -296
@@ -11,7 +11,7 @@ module Travis::Yaml
11
11
  end
12
12
 
13
13
  def accept_key?(key)
14
- true
14
+ key != 'secure'
15
15
  end
16
16
  end
17
17
  end
@@ -5,7 +5,7 @@ module Travis::Yaml
5
5
 
6
6
  def verify_language(language)
7
7
  children.delete_if do |os|
8
- next false if os.supports_language? language
8
+ next false if os.supports_language? language.to_s
9
9
  warning "dropping %p, does not support %p", os, language
10
10
  true
11
11
  end
@@ -1,7 +1,11 @@
1
1
  module Travis::Yaml
2
2
  module Nodes
3
3
  class OSEntry < FixedValue
4
- OSX = %w[objective-c ruby c cpp]
4
+ MISSING = {
5
+ # see https://github.com/travis-ci/travis-ci/issues/2320
6
+ 'osx' => %w[node_js python php perl erlang groovy clojure scala go haskell],
7
+ 'linux' => ['objective-c']
8
+ }
5
9
 
6
10
  ignore_case
7
11
  default :linux
@@ -9,10 +13,8 @@ module Travis::Yaml
9
13
  value :osx, mac: :osx, macos: :osx
10
14
 
11
15
  def supports_language?(language)
12
- case value
13
- when 'linux' then language != 'objective-c'
14
- when 'osx' then OSX.include? language
15
- end
16
+ return false unless missing = MISSING[value]
17
+ !missing.include?(language)
16
18
  end
17
19
  end
18
20
  end
@@ -4,9 +4,12 @@ module Travis::Yaml
4
4
  include LanguageSpecific
5
5
 
6
6
  map :language, required: true
7
+ map :sudo, to: Scalar[:bool], required: false
7
8
  map :bundler_args, to: BundlerArgs
8
- map :deploy, :ruby, :os, :compiler, :git, :jdk, :virtualenv, :matrix, :env, :notifications, :branches, :cache
9
- map :lein, :otp_release, :go, :ghc, :node_js, :xcode_sdk, :xcode_scheme, :perl, :php, :python, :services, :gemfile, to: VersionList
9
+ map :deploy, :ruby, :os, :compiler, :git, :jdk, :virtualenv, :matrix, :env, :notifications, :branches, :cache, :addons, :android
10
+ map :lein, :otp_release, :go, :ghc, :xcode_sdk, :xcode_scheme, :perl, :php, :python, :services, :gemfile, to: VersionList
11
+ map :podfile, to: Version
12
+ map :node_js, to: VersionList[/^\d+\.\d+(\.\d+)?$/]
10
13
  map :rvm, to: :ruby
11
14
  map :otp, to: :otp_release
12
15
  map :node, to: :node_js
@@ -14,9 +17,12 @@ module Travis::Yaml
14
17
  map :osx_image, to: Version, experimental: true
15
18
  map :gobuild_args, :xcode_project, :xcode_workspace, :xctool_args, :composer_args, :npm_args, to: Scalar[:str]
16
19
  map :source_key, to: Scalar[:str, :secure]
17
- map :sdk_components, to: Sequence
18
20
  map :before_install, :install, :before_script, :script, :after_result, :after_script,
19
21
  :after_success, :after_failure, :before_deploy, :after_deploy, to: Stage
22
+ map :dist, to: Dist
23
+ map :group, to: Group
24
+
25
+ FEATURE_KEYS = [:dist, :group]
20
26
 
21
27
  def initialize
22
28
  super(nil)
@@ -26,10 +32,24 @@ module Travis::Yaml
26
32
  super
27
33
  verify_os
28
34
  verify_language(language)
35
+ FEATURE_KEYS.each {|feature| warn_on_feature feature}
29
36
  end
30
37
 
31
38
  def verify_os
32
39
  self.os = language.default_os unless include? :os
40
+ warning 'your repository must be feature flagged for the "os" setting to be used' if os and os != language.default_os
41
+
42
+ if os.include? 'osx' and jdk
43
+ # https://github.com/travis-ci/travis-ci/issues/2317
44
+ warning 'dropping "jdk" section: currently not supported on "osx"'
45
+ @mapping.delete('jdk')
46
+ end
47
+ end
48
+
49
+ def warn_on_feature(feature)
50
+ if include? feature
51
+ warning 'your repository must be feature flagged for the "%s" setting to be used', feature
52
+ end
33
53
  end
34
54
 
35
55
  def nested_warnings(*)
@@ -39,6 +59,10 @@ module Travis::Yaml
39
59
  def inspect
40
60
  "#<Travis::Yaml:#{super}>"
41
61
  end
62
+
63
+ def to_matrix
64
+ Travis::Yaml.matrix(self)
65
+ end
42
66
  end
43
67
  end
44
- end
68
+ end
@@ -82,7 +82,7 @@ module Travis::Yaml
82
82
  def cast(visitor, type, value)
83
83
  visitor.cast(type, value)
84
84
  rescue ArgumentError => error
85
- error "failed to parse %p - %s", type.to_s, error.message
85
+ error "failed to parse %p - %s", type.to_s, error.message.sub("():", ":")
86
86
  end
87
87
 
88
88
  def cast?(type)
@@ -92,6 +92,21 @@ module Travis::Yaml
92
92
  def !@
93
93
  !value
94
94
  end
95
+
96
+ def with_value(value)
97
+ return value.dup if value.is_a? self.class
98
+ value = value.value while value.is_a? Scalar
99
+ super(value)
100
+ end
101
+
102
+ def with_value!(value)
103
+ self.value = value
104
+ end
105
+
106
+ def each_scalar(type = nil, &block)
107
+ return enum_for(:each_scalar, type) unless block
108
+ yield value if type.nil? or type === value
109
+ end
95
110
  end
96
111
  end
97
112
  end
@@ -4,6 +4,11 @@ module Travis::Yaml
4
4
  attr_reader :children
5
5
  alias_method :__getobj__, :children
6
6
 
7
+ def self.[](node_type)
8
+ node_type = Scalar[node_type] unless node_type.is_a? Node
9
+ Class.new(self) { type(node_type) }
10
+ end
11
+
7
12
  def self.type(identifier = nil)
8
13
  @type = Nodes[identifier] if identifier
9
14
  @type ||= superclass.respond_to?(:type) ? superclass.type : Scalar
@@ -79,6 +84,39 @@ module Travis::Yaml
79
84
  @children.each(&:deep_verify)
80
85
  super
81
86
  end
87
+
88
+ def each_scalar(type = nil, &block)
89
+ return enum_for(:each_scalar, type) unless block
90
+ @children.each { |c| c.each_scalar(type, &block) }
91
+ end
92
+
93
+ def with_value(value)
94
+ return value.dup if value.is_a? self.class
95
+ value = value.children if value.is_a? Sequence
96
+ value = value.value while value.is_a? Scalar
97
+ Parser::Ruby.new(Array(value)).parse self.class.new(parent)
98
+ end
99
+
100
+ def with_value!(value)
101
+ children.replace with_value(value).children
102
+ end
103
+
104
+ def add_value(value)
105
+ added = with_value(self)
106
+ added.add_value!(value)
107
+ added
108
+ end
109
+
110
+ def add_value!(value)
111
+ children.concat(with_value(value).children)
112
+ end
113
+
114
+ protected
115
+
116
+ def dup_values
117
+ @children = @children.map { |child| child.dup }
118
+ self
119
+ end
82
120
  end
83
121
  end
84
122
  end
@@ -1,6 +1,19 @@
1
1
  module Travis::Yaml
2
2
  module Nodes
3
3
  class Version < Scalar
4
+ def self.[](expression)
5
+ Class.new(self) { format(expression) }
6
+ end
7
+
8
+ def self.format(expression = nil)
9
+ @format = expression if expression
10
+ @format ||= superclass.respond_to?(:format) ? superclass.format : //
11
+ end
12
+
13
+ def value=(value)
14
+ return super unless value and value.to_s !~ self.class.format
15
+ error "value %p is not a valid version", value.to_s
16
+ end
4
17
  end
5
18
  end
6
19
  end
@@ -1,6 +1,10 @@
1
1
  module Travis::Yaml
2
2
  module Nodes
3
3
  class VersionList < Sequence
4
+ def self.[](expression)
5
+ Class.new(self) { type Version[expression] }
6
+ end
7
+
4
8
  type Version
5
9
  end
6
10
  end
@@ -119,7 +119,13 @@ module Travis::Yaml
119
119
  when SEQ then node.visit_sequence self, value
120
120
  when nil
121
121
  if value.children.size == 2 and value.children.first.value == 'secure'
122
- node.visit_scalar(self, :secure, value.children.last)
122
+ secret_value = value.children.last
123
+ if secret_value.is_a? ::Psych::Nodes::Scalar
124
+ secret_value.tag ||= '!secure'
125
+ node.visit_scalar(self, :secure, secret_value, false)
126
+ else
127
+ node.visit_unexpected(self, value, "secret value needs to be a string")
128
+ end
123
129
  else
124
130
  node.visit_mapping(self, value)
125
131
  end
@@ -205,12 +211,12 @@ module Travis::Yaml
205
211
  end
206
212
 
207
213
  def generate_key(node, value)
208
- unless value.respond_to? :value and (value.tag.nil? || value.tag == STR)
214
+ if value.respond_to? :value and (value.tag.nil? || value.tag == STR)
215
+ value = value.value.to_s
216
+ value.start_with?(?:) ? value[1..-1] : value
217
+ else
209
218
  node.visit_unexpected(self, value, "expected string as key")
210
219
  end
211
-
212
- value = value.value.to_s
213
- value.start_with?(?:) ? value[1..-1] : value
214
220
  end
215
221
  end
216
222
  end
@@ -2,11 +2,44 @@ module Travis::Yaml
2
2
  class SecureString
3
3
  attr_accessor :encrypted_string, :decrypted_string
4
4
  def initialize(string, encrypted = true)
5
+ unless string.respond_to? :to_str
6
+ raise ArgumentError, "secure string needs to be a string, not a %p" % string.class.name.downcase
7
+ end
5
8
  if encrypted
6
- @encryped_string = string
9
+ @encrypted_string = string.to_str
7
10
  else
8
- @decrypted_string = string
11
+ @decrypted_string = string.to_str
9
12
  end
10
13
  end
14
+
15
+ def encrypted?
16
+ !!encrypted_string
17
+ end
18
+
19
+ def decrypted?
20
+ !!decrypted_string
21
+ end
22
+
23
+ def decrypt
24
+ return unless encrypted?
25
+ @decrypted_string = yield(encrypted_string)
26
+ end
27
+
28
+ def encrypt
29
+ return unless decrypted?
30
+ @encrypted_string = yield(decrypted_string)
31
+ end
32
+
33
+ def inspect
34
+ "[SECURE]".freeze
35
+ end
36
+
37
+ def ==(other)
38
+ other.encrypted_string == encrypted_string and other.decrypted_string == decrypted_string if other.is_a? SecureString
39
+ end
40
+
41
+ def hash
42
+ encrypted_string.hash | decrypted_string.hash
43
+ end
11
44
  end
12
45
  end
@@ -0,0 +1,17 @@
1
+ module Travis::Yaml
2
+ module Serializer
3
+ NotSupportedError ||= Class.new(ArgumentError)
4
+ require 'travis/yaml/serializer/generic'
5
+ require 'travis/yaml/serializer/ruby'
6
+ require 'travis/yaml/serializer/legacy'
7
+ require 'travis/yaml/serializer/json'
8
+ require 'travis/yaml/serializer/yaml'
9
+
10
+ def self.[](key)
11
+ return key if key.respond_to? :serialize
12
+ name = constants.detect { |c| c.downcase == key }
13
+ raise ArgumentError, "unknown serializer %p" % key unless name
14
+ const_get(name)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,114 @@
1
+ module Travis::Yaml
2
+ module Serializer
3
+ class Generic
4
+ attr_reader :options
5
+
6
+ def self.serialize(node, options = nil)
7
+ new(options).serialize(node)
8
+ end
9
+
10
+ def initialize(options = nil)
11
+ @options = options || {}
12
+ end
13
+
14
+ def symbol_keys?
15
+ !!options[:symbol_keys]
16
+ end
17
+
18
+ def serialize(node)
19
+ case node
20
+ when Nodes::Root then serialize_root(node)
21
+ when Nodes::Scalar then serialize_scalar(node)
22
+ when Nodes::Mapping then serialize_mapping(node)
23
+ when Nodes::Sequence then serialize_sequence(node)
24
+ else raise NotSupportedError, 'do not know how to serialize %p' % node.class
25
+ end
26
+ end
27
+
28
+ def serialize_scalar(node)
29
+ case value = node.value
30
+ when true, false then serialize_bool(value)
31
+ when Float then serialize_float(value)
32
+ when Integer then serialize_integer(value)
33
+ when Time then serialize_time(value)
34
+ when SecureString then serialize_secure(value)
35
+ when Regexp then serialize_regexp(value)
36
+ when String
37
+ value.encoding == Encoding::BINARY ? serialize_binary(value) : serialize_str(value)
38
+ else
39
+ serialize_value(node)
40
+ end
41
+ end
42
+
43
+ def serialize_bool(value)
44
+ serialize_value(value)
45
+ end
46
+
47
+ def serialize_float(value)
48
+ serialize_value(value)
49
+ end
50
+
51
+ def serialize_time(value)
52
+ serialize_value(value)
53
+ end
54
+
55
+ def serialize_secure(value)
56
+ case options[:secure]
57
+ when :decrypted
58
+ raise ArgumentError, 'secure option is set decrypted, but a secure value is not decrypted' unless value.decrypted?
59
+ serialize_decrypted(value)
60
+ when :encrypted
61
+ raise ArgumentError, 'secure option is set encrypted, but a secure value is not encrypted' unless value.encrypted?
62
+ serialize_encrypted(value)
63
+ else
64
+ raise ArgumentError, 'unexpected value for secure option: %p' % options[:secure] if options[:secure]
65
+ value.encrypted? ? serialize_encrypted(value) : serialize_decrypted(value)
66
+ end
67
+ end
68
+
69
+ def serialize_encrypted(value)
70
+ serialize_value(value)
71
+ end
72
+
73
+ def serialize_decrypted(value)
74
+ serialize_value(value)
75
+ end
76
+
77
+ def serialize_regexp(value)
78
+ serialize_value(value)
79
+ end
80
+
81
+ def serialize_str(value)
82
+ serialize_value(value)
83
+ end
84
+
85
+ def serialize_binary(value)
86
+ serialize_str(value)
87
+ end
88
+
89
+ def serialize_bool(value)
90
+ serialize_value(value)
91
+ end
92
+
93
+ def serialize_value(value)
94
+ raise NotSupportedError, 'cannot serialize %p with %p' % [node.class, self.class]
95
+ end
96
+
97
+ def serialize_root(node)
98
+ serialize_mapping(node)
99
+ end
100
+
101
+ def serialize_mapping(node)
102
+ node.map { |key, value| [serialize_key(key), serialize(value)] }
103
+ end
104
+
105
+ def serialize_sequence(node)
106
+ node.map { |value| serialize(value) }
107
+ end
108
+
109
+ def serialize_key(value)
110
+ symbol_keys? ? value.to_sym : value.to_s
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,72 @@
1
+ module Travis::Yaml
2
+ module Serializer
3
+ class Json < Generic
4
+ MAP = { # mapping stolen from json gem
5
+ "\x0" => '\u0000', "\x1" => '\u0001', "\x2" => '\u0002', "\x3" => '\u0003', "\x4" => '\u0004', "\x5" => '\u0005',
6
+ "\x6" => '\u0006', "\x7" => '\u0007', "\b" => '\b', "\t" => '\t', "\n" => '\n', "\xb" => '\u000b',
7
+ "\f" => '\f', "\r" => '\r', "\xe" => '\u000e', "\xf" => '\u000f', "\x10" => '\u0010', "\x11" => '\u0011',
8
+ "\x12" => '\u0012', "\x13" => '\u0013', "\x14" => '\u0014', "\x15" => '\u0015', "\x16" => '\u0016', "\x17" => '\u0017',
9
+ "\x18" => '\u0018', "\x19" => '\u0019', "\x1a" => '\u001a', "\x1b" => '\u001b', "\x1c" => '\u001c', "\x1d" => '\u001d',
10
+ "\x1e" => '\u001e', "\x1f" => '\u001f', '"' => '\"', '\\' => '\\\\'
11
+ }
12
+
13
+ def pretty?
14
+ !!options[:pretty]
15
+ end
16
+
17
+ def serialize_float(value)
18
+ raise NotSupportedError, 'cannot serialize infinity as JSON' if value.infinite?
19
+ "#{value}"
20
+ end
21
+
22
+ def serialize_encrypted(value)
23
+ key_value("secure", serialize_str(value.encrypted_string), "{%s}")
24
+ end
25
+
26
+ def serialize_decrypted(value)
27
+ serialize_str(value.decrypted_string)
28
+ end
29
+
30
+ def serialize_str(value)
31
+ string = value.encode('utf-8')
32
+ string.force_encoding('binary')
33
+ string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
34
+ string.force_encoding('utf-8')
35
+ "\"#{string}\""
36
+ end
37
+
38
+ def serialize_binary(value)
39
+ raise NotSupportedError, 'cannot serialize binary data as JSON'
40
+ end
41
+
42
+ def serialize_bool(value)
43
+ value ? "true" : "false"
44
+ end
45
+
46
+ def serialize_mapping(node)
47
+ lines('{%s}', super.map { |key, value| key_value(key, value) })
48
+ end
49
+
50
+ def serialize_sequence(node)
51
+ lines('[%s]', super)
52
+ end
53
+
54
+ def key_value(key, value, wrapper = "%s")
55
+ space = pretty? ? " " : ""
56
+ wrapper % "#{serialize_str(key)}:#{space}#{value}"
57
+ end
58
+
59
+ def lines(wrapper, lines)
60
+ return wrapper % lines.join(',') unless pretty?
61
+ return wrapper % "" if lines.empty?
62
+ return wrapper % " #{lines.first} " unless lines.size > 1 or lines.first.include?("\n") or lines.first.size > 50
63
+ lines = "\n " + lines.join(",\n").strip.gsub("\n", "\n ") + "\n"
64
+ wrapper % lines
65
+ end
66
+
67
+ def serialize_key(value)
68
+ value.to_s
69
+ end
70
+ end
71
+ end
72
+ end