saucelabs/travis-yaml

Name: travis-yaml

Owner: Sauce Labs

Description: parses, normalizes, validates and serializes your .travis.yml

Created: 2016-08-07 02:53:40.0

Updated: 2016-08-07 02:53:42.0

Pushed: 2016-08-07 05:41:00.0

Homepage: http://yaml.travis-ci.org/

Size: 229

Language: Ruby

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Travis Configuration Parser

What is this?

This project is a library for loading Travis CI build configuration.

It can create a configuration both from a normal Ruby Hash or a YAML string. These config objects generally behave like normal primitives (hashes, arrays, etc).

ire 'travis/yaml'

ig = Travis::Yaml.parse('language: ruby')
ig = Travis::Yaml.parse(language: 'ruby')

ig[:language] # ruby
ig.language   # ruby

ing parse! instead of parse will print out warnings
is::Yaml.parse! deploy: []
ravis.yml: missing key "language", defaulting to "ruby"
ravis.yml: value for "deploy" section is empty, dropping

ve it generate the build matrix for you
is::Yaml.matrix('rvm: [jruby, 2.0.0]').each do |matrix_entry|
ts matrix_entry.ruby

Why use it?
What is missing?
External API
Loading a config
ig = Travis::Yaml.parse('language: ruby') # parse from a yaml string
ig = Travis::Yaml.parse(language: "ruby") # parse from Ruby object

For Psych/YAML compatibility, parse is also aliased to load.

Convenience

Nodes generally behave like normal Ruby objects. Mappings accept both symbols and strings as keys. Known fields on mappings are exposed as methods.

 config.language
 config[:language]
 config['language']
Warnings and errors
is::Yaml.parse("foo: bar").nested_warnings.each do |key, warning|
ts "#{key.join('.')}: #{warning}"


ll print nested warnings to stderr, will raise on top level error
is::Yaml.parse! "foo: bar"
Secure Variables

Secure variables are stored as Travis::Yaml::SecureString internally. A secure string has at least an encrypted_string or a decrypted_string, or both.

You can use decrypt/encrypt with a block to generate the missing string:

et = Travis::Yaml::SecureString.new("foo")

et.encrypted_string # => "foo"
et.decrypted_string # => nil
et.encrypted?       # => true
et.decrypted?       # => false

et.decrypt { |string| string.upcase }

et.encrypted_string # => "foo"
et.decrypted_string # => "FOO"
et.encrypted?       # => true
et.decrypted?       # => true

To avoid having to walk the whole structure manually or hardcoding the values to decrypt, these methods are also exposed on any node:

ig = Travis::Yaml.load 'env: { secure: foo }'
ig.decrypted? # => false

ig.decrypt { |string| string.upcase }
ig.decrypted?                        # => true
ig.env.matrix.first.decrypted_string # => "FOO"

This can even be done right with the parse step:

ent = File.read('.travis.yml')
is::Yaml.parse! content do |config|
nfig.decrypt { |string| string.upcase }

Serialization

A travis-yaml document can be serialized to a few other formats via the serialize method:

 config.serialize(:ruby)
 config.serialize(:json, pretty: true)

Serializer | Descriptions | Options ———–|——————————————————————-|——— ruby | Corresponding Ruby objects, secure values will be SecureStrings | secure, symbol_keys legacy | Format compatible with Travis CI's old fetch_config service | secure, symbol_keys json | Serialize as JSON, parsable via Travis::Yaml.load | secure, pretty yaml | Serialize as YAML, parsable via Travis::Yaml.load | secure, symbol_keys, indentation, line_width, canonical, avoid_tags

The secure option can be set to :decrypted or :encrypted, enforcing the decrypted or encrypted form of secure strings to be serialized. In some serializations, this might lead to secure strings being mapped to normal strings if set to :decrypted.

Defining Structure

Good starting points for getting into the code are the root node and the language node.

A parsed configuration is very similar to a syntax tree. To create a new node type, you should inherit from one of the abstract types. Internal vocabulary is taken from the YAML spec rather than the Ruby names (ie, sequence vs array, mapping vs hash).

Scalar Values

Most of the time, scalar values are just strings. In fact, if you create a new scalar node class and don't specify and other supported type, it will treat everything as string.

le Travis::Yaml::Nodes
ass Example < Scalar
d

This will parse foo to "foo" and 1.10 to 1.10. This will also generate a warning and discard values like !float 1.10.

Value Types

You can also allow other types and change the default type unsupported implicit types are cast to.

le Travis::Yaml::Nodes
ass Example < Scalar
cast :str, :binary, :int
default_type :int
d

Available types are str, binary, bool, float, int, time, regexp, secure and null.

Default Value

It is also possible to give a scalar a default value.

le Travis::Yaml::Nodes
ass Example < Scalar
default_value "example"
d

This is handy when using it for a required entry in a mapping (for instance, language is required, but has a default).

Fixed Value Set

For entries that have a well defined set of values, you can inherit from FixedValue:

le Travis::Yaml::Nodes
ass Example < FixedValue
ignore_case

default_value :example
value :foo, :bar, baz: :bar
d

This will, for example, map FOO to "foo", baz to "bar", and blah to "example" (and generate a warning about blah being not supported).

Shorthands

There are shorthands for creating Scalar and FixedValue subclasses:

le Travis::Yaml::Nodes
ass Example < Map
map :foo, to: Scalar[:int]
map :bar, to: FixedValue[:foo, :bar]
d

Sequences

Sequences correspond to Ruby arrays. If you pass in a scalar or mapping instead of a sequence, it will be treated as if it was a sequence with a single entry of that value.

le Travis::Yaml::Nodes
ass ExampleList < Sequence
type ExampleValue # node type, defaults to Scalar
d

Mappings

Mappings correspond to hashes in Ruby.

le Travis::Yaml::Nodes
ass ExampleMapping < Mapping
# map the value for the "example" key to an Example node
# map the value for the "other" key to an Other node
map :example, :other

# map the values for "foo" and "bar" to a Scalar
map :foo, :bar, to: Scalar

# map "list" to a Sequence, keep it even if it's empty
map :list, to: Sequence, drop_empty: false

# require "setting" to be present
map :setting, required: true

# make "option" an alias for "setting"
map :option, to: :setting

# if a scalar is passed in instead of a mapping, treat it as
# the value of "setting" ("foo" becomes { setting: "foo" })
prefix_scalar :setting
d

Open Mappings

Sometimes it is not possible to define all available keys for a mapping. You can solve this by using an open mapping:

le Travis::Yaml::Nodes
ass ExampleMapping < OpenMapping
# node type for entries not specified (defaults to Scalar)
default_type ExampleValue

# map "setting" to Setting node, make it a requirement
map :setting, required: true
d

You can also limit the possible keys by overriding accept_key?.

le Travis::Yaml::Nodes
ass ExampleMapping < OpenMapping
default_type ExampleValue

def accept_key?(key)
  key.start_with? "example_"
end
d

Additional Verification

Besides the generated warnings, validations and normalizations inherent to the structure, you can define your own checks and normalizations by overriding the verify method.

le Travis::Yaml::Nodes
ass Example < Scalar
def verify
  if value == "foo"
    warning "foo is deprecated, using bar instead"
    self.value = "bar"
  end
  super
end
d

The warning method will generate track a warning, so it can be presented to the user later on. The error method will lead to the node being removed from its parent node. It will also propagate the error message as a warning in the parent node.

Nested Warnings

When reflecting upon a node, warnings and errors will only contain the messages for that specific node. To get all the warnings for the entire tree, use nested_warnings, which will also give you the path (as array of strings).

ig.nested_warnings.each do |path, message|
path      # ["my", "example", "key"]
message   # "this is the warning"

Requirements

This project requires Ruby 1.9.3 or 2.0.0 and Psych ~> 2.0 (part of the stdlib).


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.