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,77 @@
1
+ require 'time'
2
+
3
+ module Travis::Yaml
4
+ module Parser
5
+ class Ruby
6
+ def self.parses?(value)
7
+ value.is_a? Hash
8
+ end
9
+
10
+ def self.parse(value)
11
+ new(value).parse
12
+ end
13
+
14
+ def initialize(value, implicit = false)
15
+ @value = value
16
+ @implicit = implicit
17
+ end
18
+
19
+ def parse(root = nil)
20
+ root ||= Travis::Yaml::Nodes::Root.new
21
+ accept(root, @value)
22
+ root
23
+ end
24
+
25
+ def accept(node, value)
26
+ case value
27
+ when Array then node.visit_sequence self, value
28
+ when Hash then node.visit_mapping self, value
29
+ when SecureString then node.visit_scalar self, :secure, value, @implicit
30
+ when String then node.visit_scalar self, :str, value, @implicit
31
+ when Symbol then node.visit_scalar self, :str, value.to_s, @implicit
32
+ when Integer then node.visit_scalar self, :int, value, @implicit
33
+ when Float then node.visit_scalar self, :float, value, @implicit
34
+ when DateTime, Time, Date then node.visit_scalar self, :time, value, @implicit
35
+ when true, false then node.visit_scalar self, :bool, value, @implicit
36
+ when Regexp then node.visit_scalar self, :regexp, value, @implicit
37
+ when nil then node.visit_scalar self, :null, value, @implicit
38
+ else node.visit_unexpected self, value
39
+ end
40
+ node.verify
41
+ end
42
+
43
+ def cast(type, value)
44
+ case type
45
+ when :str then value.to_s
46
+ when :binary then value.unpack('m').first
47
+ when :bool then !!value
48
+ when :float then Float value
49
+ when :int then Integer value
50
+ when :time then value.to_time
51
+ when :secure then SecureString === value ? value : SecureString.new(value.value)
52
+ when :regexp then Regexp.new(value)
53
+ when :null then nil
54
+ else raise ArgumentError, 'unknown scalar type %p' % type
55
+ end
56
+ rescue RegexpError => error
57
+ raise ArgumentError, "broken regular expression - #{error.message}"
58
+ end
59
+
60
+ def apply_mapping(node, value)
61
+ value.each_pair { |key, value| node.visit_pair(self, key, value) }
62
+ end
63
+
64
+ def apply_sequence(node, value)
65
+ value.each { |child| node.visit_child(self, child) }
66
+ end
67
+
68
+ def generate_key(node, value)
69
+ case value
70
+ when String then value
71
+ when Symbol then value.to_s
72
+ else node.visit_unexpected(self, value, "expected string as key")
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,12 @@
1
+ module Travis::Yaml
2
+ class SecureString
3
+ attr_accessor :encrypted_string, :decrypted_string
4
+ def initialize(string, encrypted = true)
5
+ if encrypted
6
+ @encryped_string = string
7
+ else
8
+ @decrypted_string = string
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ module Travis
2
+ module Yaml
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ # Usage: ruby play/travis.rb [FILE]
2
+ # Will output any error/warnings
3
+ require 'bundler/setup'
4
+ require 'travis/yaml'
5
+
6
+ file = ARGV[0] || ".travis.yml"
7
+ content = File.read(file)
8
+ Travis::Yaml.parse!(content, file)
@@ -0,0 +1,183 @@
1
+ require 'bundler/setup'
2
+ require 'travis/yaml'
3
+
4
+ module Travis::Yaml
5
+ def spec(**options)
6
+ Nodes::Root.spec(**options)
7
+ end
8
+
9
+ module Nodes
10
+ TEMPLATE_VARS = Notifications::Template::VARIABLES.map { |v| "`%{#{v}}`"}.join(", ")
11
+ SPEC_DESCRIPTIONS = {
12
+ Stage => "Commands that will be run on the VM.",
13
+ Notifications::Template => "Strings will be interpolated. Available variables: #{TEMPLATE_VARS}.",
14
+ %w[gemfile] => "Gemfile(s) to use.",
15
+ "gemfile" => "Gemfile to use."
16
+ }
17
+
18
+ TYPES = {
19
+ binary: 'binary string',
20
+ bool: 'boolean value',
21
+ float: 'float value',
22
+ int: 'integer value',
23
+ null: 'null value',
24
+ str: 'string',
25
+ time: 'time value',
26
+ secure: 'encrypted string',
27
+ regexp: 'regular expression'
28
+ }
29
+
30
+ class Node
31
+ def self.spec_description(*prefix)
32
+ description = SPEC_DESCRIPTIONS[prefix] || SPEC_DESCRIPTIONS[prefix.last]
33
+ ancestors.each { |a| description ||= SPEC_DESCRIPTIONS[a] }
34
+ description
35
+ end
36
+
37
+ def self.spec_format
38
+ end
39
+
40
+ def self.spec(*prefix, **options)
41
+ options[:experimental] ||= false
42
+ options[:required] ||= false
43
+ [{ key: prefix, description: spec_description(*prefix), format: spec_format, **options }]
44
+ end
45
+ end
46
+
47
+ class Scalar
48
+ def self.spec_format(append = "")
49
+ formats = cast.any? ? cast : [default_type]
50
+ formats.map { |f| TYPES[f] ? TYPES[f]+append : f.to_s }.join(', ').gsub(/, ([^,]+)$/, ' or \1')
51
+ end
52
+ end
53
+
54
+ class Version
55
+ def self.spec_description(*prefix)
56
+ super || "`#{prefix.last}` version to use."
57
+ end
58
+ end
59
+
60
+ class VersionList
61
+ def self.spec_description(*prefix)
62
+ super || "List of `#{prefix.last}` versions to use."
63
+ end
64
+ end
65
+
66
+ class FixedValue
67
+ def self.spec_description(*prefix)
68
+ super || begin
69
+ list = valid_values.map { |v| "`#{v}`#{" (default)" if default == v.to_s}" }.join(', ').gsub(/,([^,]+)$/, ' or\1')
70
+ if aliases.any?
71
+ alias_list = aliases.map { |k,v| "`#{k}` for `#{v}`" }.join(', ').gsub(/,([^,]+)$/, ' or\1')
72
+ list += "; or one of the known aliases: #{alias_list}"
73
+ end
74
+ "Value has to be #{list}. Setting is#{" not" if ignore_case?} case sensitive."
75
+ end
76
+ end
77
+ end
78
+
79
+ class Notifications::Notification
80
+ def self.spec_notification_format
81
+ "list of strings or encrypted strings"
82
+ end
83
+
84
+ def self.spec_format
85
+ super + ", or #{spec_notification_format}, or boolean value"
86
+ end
87
+ end
88
+
89
+ class Notifications::Flowdoc
90
+ def self.spec_notification_format
91
+ "string, encrypted string"
92
+ end
93
+ end
94
+
95
+ class Sequence
96
+ def self.spec_format
97
+ "list of " << type.spec_format("s") << "; or a single " << type.spec_format if type.spec_format
98
+ end
99
+
100
+ def self.spec(*prefix, **options)
101
+ specs = super
102
+ specs += type.spec(*prefix, '[]') unless type <= Scalar and not type < FixedValue
103
+ specs
104
+ end
105
+ end
106
+
107
+ class Root
108
+ def self.spec(*)
109
+ super[1..-1].sort_by { |e| e[:key] }
110
+ end
111
+ end
112
+
113
+ class OpenMapping
114
+ def self.spec(*prefix, **options)
115
+ super + default_type.spec(*prefix, '*')
116
+ end
117
+ end
118
+
119
+ class Mapping
120
+ def self.spec_format(append = "")
121
+ "key value mapping#{append}"
122
+ end
123
+
124
+ def self.default_spec_description
125
+ "a key value map"
126
+ end
127
+
128
+ def self.spec_options(key, **options)
129
+ if self < LanguageSpecific and languages = LanguageSpecific::LANGUAGE_SPECIFIC[key.to_sym]
130
+ languages = languages.map { |v| "`#{v}`#{" (default)" if v.to_s == 'ruby'}" }.join(', ').gsub(/,([^,]+)$/, ' or\1')
131
+ end
132
+ { required: required.include?(key), experimental: experimental.include?(key), languages: languages }
133
+ end
134
+
135
+ def self.spec_aliases(*prefix, **options)
136
+ aliases.map { |k,v| { key: [*prefix, k], alias_for: [*prefix, v], **spec_options(k, **options) } }
137
+ end
138
+
139
+ def self.spec(*prefix, **options)
140
+ specs = mapping.sort_by(&:first).inject(super) { |l, (k,v)| l + v.spec(*prefix, k, **spec_options(k, **options)) }
141
+ specs + spec_aliases(*prefix, **options)
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ def self.format_name(key)
148
+ key.join('.').gsub('.[]', '[]')
149
+ end
150
+
151
+ def self.format_link(key)
152
+ key.join.gsub('[]', '')
153
+ end
154
+
155
+ content = <<-MARKDOWN
156
+ ## The `.travis.yml` Format
157
+ Here is a list of all the options understood by travis-yaml.
158
+
159
+ Note that stricitly speaking Travis CI might not have the same understanding of these as travis-yaml has at the moment, since travis-yaml is not yet being used.
160
+
161
+ ### Available Options
162
+ MARKDOWN
163
+
164
+ Travis::Yaml.spec.each do |entry|
165
+ content << "#### `" << format_name(entry[:key]) << "`\n"
166
+ content << "**This setting is only relevant if [`language`](#language) is set to #{entry[:languages]}.**\n\n" if entry[:languages]
167
+ content << "**This setting is required!**\n\n" if entry[:required]
168
+ content << "**This setting is experimental and might be removed!**\n\n" if entry[:experimental]
169
+ if other = entry[:alias_for]
170
+ content << "Alias for " << "[`#{format_name(other)}`](##{format_link(other)})." << "\n\n"
171
+ else
172
+ content << entry[:description] << "\n\n" if entry[:description]
173
+ content << "**Expected format:** " << entry[:format].capitalize << ".\n\n" if entry[:format]
174
+ end
175
+ end
176
+
177
+ content << <<-MARKDOWN
178
+ ## Generating the Specification
179
+
180
+ This file is generated. You currently update it by running `play/spec.rb`.
181
+ MARKDOWN
182
+
183
+ File.write('SPEC.md', content)
@@ -0,0 +1,296 @@
1
+ require 'bundler/setup'
2
+ require 'travis/yaml'
3
+ require 'sinatra'
4
+ require 'slim'
5
+ require 'gh'
6
+
7
+ configure :production do
8
+ GH.set token: ENV.fetch('GITHUB_TOKEN')
9
+ end
10
+
11
+ get '/' do
12
+ slim ""
13
+ end
14
+
15
+ get '/style.css' do
16
+ sass :style
17
+ end
18
+
19
+ post '/' do
20
+ redirect to(params[:repo]) if params[:repo]
21
+ show_result
22
+ end
23
+
24
+ get '/gist/:id' do
25
+ file = params[:file] || '.travis.yml'
26
+ params[:yml] = GH["/gists/#{params[:id]}"]['files'][file]['content']
27
+ show_result
28
+ end
29
+
30
+ get '/:owner/:name' do
31
+ params[:repo] = request.path_info[1..-1]
32
+ show_result
33
+ end
34
+
35
+ error GH::Error, NoMethodError do
36
+ halt 400, slim("ul.result\n li failed to fetch <b class='error'>.travis.yml</b>")
37
+ end
38
+
39
+ helpers do
40
+ def show_result
41
+ halt 400, 'needs repo or yml' unless params[:repo] or params[:yml]
42
+ branch = params[:branch] || 'master'
43
+ params[:yml] ||= GH["/repos/#{params[:repo]}/contents/.travis.yml?ref=#{branch}"]['content'].to_s.unpack('m').first
44
+ @result = Travis::Yaml.parse(params[:yml])
45
+ @matrix = Travis::Yaml::Matrix.new(@result)
46
+ slim :result
47
+ end
48
+ end
49
+
50
+ __END__
51
+
52
+ @@ result
53
+
54
+ - if @result.nested_warnings.empty?
55
+ p.result Hooray, your .travis.yml seems to be solid!
56
+ - else
57
+ ul.result
58
+ - @result.nested_warnings.each do |key, warning|
59
+ li
60
+ - if key.any?
61
+ | in <b class="error">#{key.join('.')}</b> section:
62
+ = " "
63
+ == slim('= error', {}, error: warning).gsub(/&quot;(.+?)&quot;/, '<b class="error">\1</b>')
64
+
65
+ - if @matrix.size > 1
66
+ p.jobs It will generate #{@matrix.size} jobs:
67
+ ul.jobs
68
+ - @matrix.each do |job|
69
+ li = job.matrix_attributes.map { |k,v| "%s=%p" % [k,v] if v }.compact.join(', ')
70
+
71
+ @@ layout
72
+
73
+ html
74
+ head
75
+ title Validate your .travis.yml file
76
+ link rel="stylesheet" type="text/css" href="/style.css"
77
+ body
78
+ h1
79
+ a href="/" Travis WebLint
80
+ p.tagline
81
+ | Uses <a href="https://github.com/travis-ci/travis-yaml">travis-yaml</a> to check your .travis.yml config.
82
+
83
+ == yield
84
+
85
+ form class="first" action="/" method="post" accept-charset="UTF-8"
86
+ label for="repo" Enter your Github repository
87
+ input type="text" id="repo" name="repo" maxlength="80" placeholder="travis-ci/travis-yaml" required=true value=params[:repo]
88
+ input type="submit" value="Validate"
89
+
90
+ form action="/" method="post" accept-charset="UTF-8" id="ymlform"
91
+ label for="yml" Or paste your .travis.yml
92
+ textarea id="yml" name="yml" maxlength="10000" autofocus=true = params[:yml]
93
+ input type="submit" value="Validate"
94
+
95
+ javascript:
96
+ var input = document.getElementById("yml");
97
+ var form = document.getElementById("ymlform");
98
+ input.onkeydown = function(event) {
99
+ if ((event.metaKey || event.ctrlKey) && event.keyCode == 13) form.submit();
100
+ };
101
+
102
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
103
+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
104
+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
105
+ })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
106
+
107
+ ga('create', 'UA-24868285-9', 'travis-ci.org');
108
+ ga('send', 'pageview');
109
+
110
+ @@ style
111
+
112
+ // http://meyerweb.com/eric/tools/css/reset/
113
+ // v2.0 | 20110126
114
+ // License: none (public domain)
115
+
116
+ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video
117
+ margin: 0
118
+ padding: 0
119
+ border: 0
120
+ font-size: 100%
121
+ font: inherit
122
+ vertical-align: baseline
123
+
124
+ // HTML5 display-role reset for older browsers
125
+ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section
126
+ display: block
127
+
128
+ body
129
+ line-height: 1
130
+
131
+ ol, ul
132
+ list-style: none
133
+
134
+ blockquote, q
135
+ quotes: none
136
+
137
+ blockquote
138
+ &:before, &:after
139
+ content: ''
140
+ content: none
141
+
142
+ q
143
+ &:before, &:after
144
+ content: ''
145
+ content: none
146
+
147
+ table
148
+ border-collapse: collapse
149
+ border-spacing: 0
150
+
151
+ // General
152
+
153
+ body
154
+ margin: 2em auto 2em auto
155
+ width: 960px
156
+ font-size: 14px
157
+ line-height: 1.4286
158
+ color: #555
159
+ background: #fff
160
+ font-family: "Helvetica Neue", Arial, Verdana, sans-serif
161
+
162
+ b
163
+ font-weight: bold
164
+
165
+ a
166
+ color: #36c
167
+ outline: none
168
+ text-decoration: underline
169
+
170
+ a:visited
171
+ color: #666
172
+
173
+ a:hover
174
+ color: #6c3
175
+ text-decoration: none
176
+
177
+ h1
178
+ color: #000
179
+ font-size: 4em
180
+ font-weight: bold
181
+ line-height: 1em
182
+
183
+ h1 a:link, h1 a:visited, h1 a:hover, h1 a:active
184
+ color: #000
185
+ text-decoration: none
186
+
187
+ h2
188
+ font-size: 2em
189
+ font-weight: bold
190
+ line-height: 2em
191
+
192
+ p.tagline
193
+ color: #777
194
+ display: block
195
+ font: italic 1.25em Georgia, Times, Serif
196
+ line-height: 1.67em
197
+ margin: 1em 0 4em 0
198
+ padding: 0 0 1.25em 0
199
+ border-bottom: 1px solid #ccc
200
+
201
+ // Result
202
+
203
+ .result
204
+ font-size: 1.5em
205
+ margin-bottom: 2em
206
+
207
+ p.result
208
+ color: #6c3
209
+
210
+ ul.result
211
+ list-style: none
212
+
213
+ ul.result li:before
214
+ content: ">"
215
+ display: inline-block
216
+ background-color: #c00
217
+ color: #fff
218
+ width: 1.4em
219
+ height: 1.4em
220
+ font-size: 40%
221
+ margin-right: 1em
222
+ text-align: center
223
+ position: relative
224
+ top: -0.5em
225
+
226
+ // jobs
227
+
228
+ .jobs
229
+ font-size: 1.5em
230
+
231
+ ul.jobs
232
+ list-style: none
233
+ margin-bottom: 2em
234
+ font-size: 1.25em
235
+
236
+ ul.jobs li:before
237
+ content: ">"
238
+ display: inline-block
239
+ background-color: #000
240
+ color: #fff
241
+ width: 1.4em
242
+ height: 1.4em
243
+ font-size: 48%
244
+ margin-right: 1em
245
+ text-align: center
246
+ position: relative
247
+ top: -0.5em
248
+
249
+ // Form
250
+
251
+ form
252
+ display: inline-block
253
+ vertical-align: top
254
+ width: 475px
255
+
256
+ form.left
257
+ margin-right: 55px
258
+
259
+ label, input
260
+ display: block
261
+
262
+ label
263
+ margin-bottom: 0.5em
264
+
265
+ input, textarea
266
+ font-size: 14px
267
+ line-height: 1.4286
268
+ color: #555
269
+ font-family: "Helvetica Neue", Arial, Verdana, sans-serif
270
+ border: 1px solid #ccc
271
+ margin: 0
272
+
273
+ input[type=text]
274
+ padding: 4px 8px
275
+ width: 400px
276
+
277
+ input[type=submit]
278
+ background: #efefef
279
+ padding: 4px 8px
280
+ margin-top: 0.5em
281
+
282
+ input[type=submit]:hover
283
+ cursor: pointer
284
+
285
+ textarea
286
+ padding: 4px 8px
287
+ width: 460px
288
+ height: 250px
289
+
290
+ // Various
291
+
292
+ .error
293
+ color: #c00
294
+
295
+ .note
296
+ margin-top: 5em