highway 0.0.1 → 1.0.1
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.
- checksums.yaml +7 -0
- data/lib/highway.rb +8 -4
- data/lib/highway/compiler/analyze/analyzer.rb +249 -0
- data/lib/highway/compiler/analyze/tree/root.rb +95 -0
- data/lib/highway/compiler/analyze/tree/segments/text.rb +36 -0
- data/lib/highway/compiler/analyze/tree/segments/variable.rb +43 -0
- data/lib/highway/compiler/analyze/tree/stage.rb +48 -0
- data/lib/highway/compiler/analyze/tree/step.rb +69 -0
- data/lib/highway/compiler/analyze/tree/values/array.rb +45 -0
- data/lib/highway/compiler/analyze/tree/values/base.rb +67 -0
- data/lib/highway/compiler/analyze/tree/values/hash.rb +45 -0
- data/lib/highway/compiler/analyze/tree/values/primitive.rb +43 -0
- data/lib/highway/compiler/analyze/tree/variable.rb +48 -0
- data/lib/highway/compiler/build/builder.rb +154 -0
- data/lib/highway/compiler/build/output/invocation.rb +70 -0
- data/lib/highway/compiler/build/output/manifest.rb +52 -0
- data/lib/highway/compiler/parse/parser.rb +92 -0
- data/lib/highway/compiler/parse/tree/root.rb +73 -0
- data/lib/highway/compiler/parse/tree/step.rb +62 -0
- data/lib/highway/compiler/parse/tree/variable.rb +48 -0
- data/lib/highway/compiler/parse/versions/v1.rb +110 -0
- data/lib/highway/compiler/suite.rb +56 -0
- data/lib/highway/environment.rb +282 -0
- data/lib/highway/fastlane/action.rb +67 -0
- data/lib/highway/interface.rb +135 -0
- data/lib/highway/main.rb +173 -0
- data/lib/highway/runtime/context.rb +229 -0
- data/lib/highway/runtime/report.rb +80 -0
- data/lib/highway/runtime/runner.rb +286 -0
- data/lib/highway/steps/infrastructure.rb +20 -0
- data/lib/highway/steps/library/action.rb +42 -0
- data/lib/highway/steps/library/appcenter.rb +106 -0
- data/lib/highway/steps/library/appstore.rb +137 -0
- data/lib/highway/steps/library/carthage.rb +67 -0
- data/lib/highway/steps/library/cocoapods.rb +76 -0
- data/lib/highway/steps/library/copy_artifacts.rb +36 -0
- data/lib/highway/steps/library/lane.rb +42 -0
- data/lib/highway/steps/library/sh.rb +36 -0
- data/lib/highway/steps/library/slack.rb +381 -0
- data/lib/highway/steps/library/testflight.rb +105 -0
- data/lib/highway/steps/library/xcode_archive.rb +162 -0
- data/lib/highway/steps/library/xcode_test.rb +264 -0
- data/lib/highway/steps/parameters/base.rb +52 -0
- data/lib/highway/steps/parameters/compound.rb +141 -0
- data/lib/highway/steps/parameters/single.rb +74 -0
- data/lib/highway/steps/registry.rb +92 -0
- data/lib/highway/steps/step.rb +52 -0
- data/lib/highway/steps/types/any.rb +65 -0
- data/lib/highway/steps/types/anyof.rb +64 -0
- data/lib/highway/steps/types/array.rb +44 -0
- data/lib/highway/steps/types/bool.rb +36 -0
- data/lib/highway/steps/types/enum.rb +44 -0
- data/lib/highway/steps/types/hash.rb +45 -0
- data/lib/highway/steps/types/number.rb +38 -0
- data/lib/highway/steps/types/set.rb +39 -0
- data/lib/highway/steps/types/string.rb +47 -0
- data/lib/highway/steps/types/url.rb +35 -0
- data/lib/highway/utilities.rb +51 -0
- data/lib/highway/version.rb +9 -1
- metadata +194 -22
- data/.gitignore +0 -4
- data/Gemfile +0 -4
- data/Rakefile +0 -1
- data/highway.gemspec +0 -24
@@ -0,0 +1,70 @@
|
|
1
|
+
#
|
2
|
+
# invocation.rb
|
3
|
+
# Copyright © 2019 Netguru S.A. All rights reserved.
|
4
|
+
#
|
5
|
+
|
6
|
+
module Highway
|
7
|
+
module Compiler
|
8
|
+
module Build
|
9
|
+
module Output
|
10
|
+
|
11
|
+
# This class represents a step invocation in the build manifest. It
|
12
|
+
# contains information about step definition class, parameters and
|
13
|
+
# execution policy.
|
14
|
+
class Invocation
|
15
|
+
|
16
|
+
public
|
17
|
+
|
18
|
+
# Initialize an instance.
|
19
|
+
#
|
20
|
+
# @param index [Integer] Index of invocation, 1-based.
|
21
|
+
# @param step_class [Class] Definition class of the step.
|
22
|
+
# @param parameters [Highway::Compiler::Analyze::Tree::Values::Hash] Hash value of step parameters.
|
23
|
+
# @param policy [Symbol] Execution policy of the step invocation.
|
24
|
+
# @param keypath [Array<String>] A keypath used for debugging purposes.
|
25
|
+
def initialize(index:, step_class:, parameters:, policy:, keypath:)
|
26
|
+
@index = index
|
27
|
+
@step_class = step_class
|
28
|
+
@parameters = parameters
|
29
|
+
@policy = policy
|
30
|
+
@keypath = keypath
|
31
|
+
end
|
32
|
+
|
33
|
+
# Index of invocation, 1-based.
|
34
|
+
#
|
35
|
+
# @return [Integer]
|
36
|
+
attr_reader :index
|
37
|
+
|
38
|
+
# Definition class of the step.
|
39
|
+
#
|
40
|
+
# @return [Class]
|
41
|
+
attr_reader :step_class
|
42
|
+
|
43
|
+
# Hash value of step parameters.
|
44
|
+
#
|
45
|
+
# @return [Highway::Compiler::Analyze::Tree::Values::Hash]
|
46
|
+
attr_reader :parameters
|
47
|
+
|
48
|
+
# Execution policy of the step invocation.
|
49
|
+
#
|
50
|
+
# @return [Symbol]
|
51
|
+
attr_reader :policy
|
52
|
+
|
53
|
+
# A keypath used for debugging purposes.
|
54
|
+
#
|
55
|
+
# @return [Array<String>]
|
56
|
+
attr_reader :keypath
|
57
|
+
|
58
|
+
# An identifier of the invocation, joined by index and step name.
|
59
|
+
#
|
60
|
+
# @return [String]
|
61
|
+
def identifier
|
62
|
+
return "#{index}-#{step_class.name}"
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#
|
2
|
+
# manifest.rb
|
3
|
+
# Copyright © 2019 Netguru S.A. All rights reserved.
|
4
|
+
#
|
5
|
+
|
6
|
+
require "highway/compiler/build/output/invocation"
|
7
|
+
|
8
|
+
module Highway
|
9
|
+
module Compiler
|
10
|
+
module Build
|
11
|
+
module Output
|
12
|
+
|
13
|
+
# This class represents a build manifest. It contains compiled step
|
14
|
+
# invocations.
|
15
|
+
class Manifest
|
16
|
+
|
17
|
+
public
|
18
|
+
|
19
|
+
# Initialize an instance.
|
20
|
+
def initialize()
|
21
|
+
@invocations = Array.new()
|
22
|
+
end
|
23
|
+
|
24
|
+
# The preset.
|
25
|
+
#
|
26
|
+
# @return [String]
|
27
|
+
attr_accessor :preset
|
28
|
+
|
29
|
+
# Invocations in the manifest.
|
30
|
+
#
|
31
|
+
# @return [Array<Highway::Compiler::Build::Output::Invocation>]
|
32
|
+
attr_reader :invocations
|
33
|
+
|
34
|
+
# Add an invocation to the manifest.
|
35
|
+
#
|
36
|
+
# @param index [Integer] Index of invocation, 1-based.
|
37
|
+
# @param step_class [Class] Definition class of the step.
|
38
|
+
# @param parameters [Highway::Compiler::Analyze::Tree::Values::Hash] Hash value of step parameters.
|
39
|
+
# @param policy [:normal, :always] Execution policy of the step invocation.
|
40
|
+
# @param keypath [Array<String>] A keypath used for debugging purposes.
|
41
|
+
#
|
42
|
+
# @return [Void]
|
43
|
+
def add_invocation(index:, step_class:, parameters:, policy:, keypath:)
|
44
|
+
@invocations << Invocation.new(index: index, step_class: step_class, parameters: parameters, policy: policy, keypath: keypath)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
#
|
2
|
+
# parser.rb
|
3
|
+
# Copyright © 2019 Netguru S.A. All rights reserved.
|
4
|
+
#
|
5
|
+
|
6
|
+
require "yaml"
|
7
|
+
|
8
|
+
require "highway/compiler/parse/versions/v1"
|
9
|
+
|
10
|
+
module Highway
|
11
|
+
module Compiler
|
12
|
+
module Parse
|
13
|
+
|
14
|
+
# This class is responsible for syntactic analysis of a configuration
|
15
|
+
# file. This is the first phase of the compiler.
|
16
|
+
class Parser
|
17
|
+
|
18
|
+
public
|
19
|
+
|
20
|
+
# Initialize an instance.
|
21
|
+
#
|
22
|
+
# @param interface [Highway::Interface] The interface.
|
23
|
+
def initialize(interface:)
|
24
|
+
@interface = interface
|
25
|
+
end
|
26
|
+
|
27
|
+
# Parse the configuration file.
|
28
|
+
#
|
29
|
+
# The parser is backwards compatible with previous versions of
|
30
|
+
# configuration files.
|
31
|
+
#
|
32
|
+
# This method only loads the file, searches for the version declaration
|
33
|
+
# and then delegates the wotk to parser of a particular version.
|
34
|
+
#
|
35
|
+
# The parser produces a parse tree which is then used by semantic
|
36
|
+
# analysis phase to produce semantic tree.
|
37
|
+
#
|
38
|
+
# @param path [String] Path to configuration file.
|
39
|
+
#
|
40
|
+
# @return [Highway::Compiler::Parse::Tree::Root]
|
41
|
+
def parse(path:)
|
42
|
+
|
43
|
+
# Load the file.
|
44
|
+
|
45
|
+
unless File.exist?(path)
|
46
|
+
@interface.fatal!("The configuration file '#{path}' does not exist.")
|
47
|
+
end
|
48
|
+
|
49
|
+
begin
|
50
|
+
raw = YAML.load_file(path)
|
51
|
+
rescue StandardError => error
|
52
|
+
@interface.fatal!("The configuration file '#{path}' is not a valid YAML file.")
|
53
|
+
end
|
54
|
+
|
55
|
+
# Make sure it contains a hash.
|
56
|
+
|
57
|
+
unless raw.is_a?(Hash)
|
58
|
+
@interface.fatal!("The configuration file does not contain a top-level dictionary.")
|
59
|
+
end
|
60
|
+
|
61
|
+
# Specify the known versions and their corresponding subparsers.
|
62
|
+
|
63
|
+
known = {
|
64
|
+
1 => Parse::Versions::V1,
|
65
|
+
}
|
66
|
+
|
67
|
+
# Find and validate the version.
|
68
|
+
|
69
|
+
unless version = (Integer(raw.fetch("version", nil)) rescue nil)
|
70
|
+
@interface.fatal!("Missing or invalid value of: 'version'.")
|
71
|
+
end
|
72
|
+
|
73
|
+
unless known.keys.include?(version)
|
74
|
+
expected_versions = known.keys.map { |v| "'#{v}'" }.join(", ")
|
75
|
+
@interface.fatal!("Invalid value of: 'version'. Expected one of: [#{expected_versions}], got: '#{version}'.")
|
76
|
+
end
|
77
|
+
|
78
|
+
# Delegate the work to parser of a specific version.
|
79
|
+
|
80
|
+
subparser = known[version].new(interface: @interface)
|
81
|
+
|
82
|
+
# Parse and return the tree.
|
83
|
+
|
84
|
+
subparser.parse(raw: raw)
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
#
|
2
|
+
# root.rb
|
3
|
+
# Copyright © 2019 Netguru S.A. All rights reserved.
|
4
|
+
#
|
5
|
+
|
6
|
+
require "highway/compiler/parse/tree/step"
|
7
|
+
require "highway/compiler/parse/tree/variable"
|
8
|
+
|
9
|
+
module Highway
|
10
|
+
module Compiler
|
11
|
+
module Parse
|
12
|
+
module Tree
|
13
|
+
|
14
|
+
# This class represents a root node of a parse tree. It contains other
|
15
|
+
# nodes, such as variables and steps.
|
16
|
+
class Root
|
17
|
+
|
18
|
+
public
|
19
|
+
|
20
|
+
# Initialize an instance.
|
21
|
+
#
|
22
|
+
# @param version [Integer] Version of the parse tree.
|
23
|
+
def initialize(version:)
|
24
|
+
@version = version
|
25
|
+
@variables = Array.new()
|
26
|
+
@steps = Array.new()
|
27
|
+
end
|
28
|
+
|
29
|
+
# Version of the parse tree.
|
30
|
+
#
|
31
|
+
# @return [Integer]
|
32
|
+
attr_reader :version
|
33
|
+
|
34
|
+
# Variables in the tree.
|
35
|
+
#
|
36
|
+
# @return [Array<Highway::Compiler::Parse::Tree::Variable>]
|
37
|
+
attr_reader :variables
|
38
|
+
|
39
|
+
# Steps in the tree.
|
40
|
+
#
|
41
|
+
# @return [Array<Highway::Compiler::Parse::Tree::Step>]
|
42
|
+
attr_reader :steps
|
43
|
+
|
44
|
+
# Add a variable to the tree.
|
45
|
+
#
|
46
|
+
# @param name [String] Name of the variable.
|
47
|
+
# @param value [String] Raw value of the variable.
|
48
|
+
# @param preset [String] Parent preset of the variable.
|
49
|
+
#
|
50
|
+
# @return [Void]
|
51
|
+
def add_variable(name:, value:, preset:)
|
52
|
+
@variables << Variable.new(name: name, value: value, preset: preset)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Add a step to the tree.
|
56
|
+
#
|
57
|
+
# @param index [Integer] Index of step in its scope.
|
58
|
+
# @param name [String] Name of the step.
|
59
|
+
# @param parameters [Hash] Parameters of the step.
|
60
|
+
# @param preset [String] Parent preset of the step.
|
61
|
+
# @param stage [String] Parent stage of the step.
|
62
|
+
#
|
63
|
+
# @return [Void]
|
64
|
+
def add_step(name:, parameters:, preset:, stage:, index:)
|
65
|
+
@steps << Step.new(index: index, name: name, parameters: parameters, preset: preset, stage: stage)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#
|
2
|
+
# step.rb
|
3
|
+
# Copyright © 2019 Netguru S.A. All rights reserved.
|
4
|
+
#
|
5
|
+
|
6
|
+
module Highway
|
7
|
+
module Compiler
|
8
|
+
module Parse
|
9
|
+
module Tree
|
10
|
+
|
11
|
+
# This class represents a step node in a parse tree. It contains
|
12
|
+
# information about a single step and its parameters.
|
13
|
+
class Step
|
14
|
+
|
15
|
+
public
|
16
|
+
|
17
|
+
# Initialize an instance.
|
18
|
+
#
|
19
|
+
# @param index [Integer] Index of step in its scope.
|
20
|
+
# @param name [String] Name of the step.
|
21
|
+
# @param parameters [Hash] Parameters of the step.
|
22
|
+
# @param preset [String] Parent preset of the step.
|
23
|
+
# @param stage [String] Parent stage of the step.
|
24
|
+
def initialize(index:, name:, parameters:, preset:, stage:)
|
25
|
+
@name = name
|
26
|
+
@parameters = parameters
|
27
|
+
@preset = preset
|
28
|
+
@stage = stage
|
29
|
+
@index = index
|
30
|
+
end
|
31
|
+
|
32
|
+
# Name of the step.
|
33
|
+
#
|
34
|
+
# @return [String]
|
35
|
+
attr_reader :name
|
36
|
+
|
37
|
+
# Parameters of the step.
|
38
|
+
#
|
39
|
+
# @return [Hash]
|
40
|
+
attr_reader :parameters
|
41
|
+
|
42
|
+
# Parent preset of the step.
|
43
|
+
#
|
44
|
+
# @return [String]
|
45
|
+
attr_reader :preset
|
46
|
+
|
47
|
+
# Parent stage of the step.
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
attr_reader :stage
|
51
|
+
|
52
|
+
# Index of step in its scope.
|
53
|
+
#
|
54
|
+
# @return [Integer]
|
55
|
+
attr_reader :index
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#
|
2
|
+
# variable.rb
|
3
|
+
# Copyright © 2019 Netguru S.A. All rights reserved.
|
4
|
+
#
|
5
|
+
|
6
|
+
module Highway
|
7
|
+
module Compiler
|
8
|
+
module Parse
|
9
|
+
module Tree
|
10
|
+
|
11
|
+
# This class represents a variable node in a parse tree. It contains
|
12
|
+
# information about a single variable.
|
13
|
+
class Variable
|
14
|
+
|
15
|
+
public
|
16
|
+
|
17
|
+
# Initialize an instance.
|
18
|
+
#
|
19
|
+
# @param name [String] Name of the variable.
|
20
|
+
# @param value [String] Raw value of the variable.
|
21
|
+
# @param preset [String] Parent preset of the variable.
|
22
|
+
def initialize(name:, value:, preset:)
|
23
|
+
@name = name
|
24
|
+
@value = value
|
25
|
+
@preset = preset
|
26
|
+
end
|
27
|
+
|
28
|
+
# Name of the variable.
|
29
|
+
#
|
30
|
+
# @return [String]
|
31
|
+
attr_reader :name
|
32
|
+
|
33
|
+
# Raw value of the variable.
|
34
|
+
#
|
35
|
+
# @return [String]
|
36
|
+
attr_reader :value
|
37
|
+
|
38
|
+
# Parent preset of the variable.
|
39
|
+
#
|
40
|
+
# @return [String]
|
41
|
+
attr_reader :preset
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
#
|
2
|
+
# v1.rb
|
3
|
+
# Copyright © 2019 Netguru S.A. All rights reserved.
|
4
|
+
#
|
5
|
+
|
6
|
+
require "highway/compiler/parse/tree/root"
|
7
|
+
|
8
|
+
module Highway
|
9
|
+
module Compiler
|
10
|
+
module Parse
|
11
|
+
module Versions
|
12
|
+
|
13
|
+
# This class is responsible for parsing a configuration file v1.
|
14
|
+
class V1
|
15
|
+
|
16
|
+
public
|
17
|
+
|
18
|
+
# Initialize an instance.
|
19
|
+
#
|
20
|
+
# @param reporter [Highway::Interface] The interface.
|
21
|
+
def initialize(interface:)
|
22
|
+
@interface = interface
|
23
|
+
end
|
24
|
+
|
25
|
+
# Parse the configuration file v1.
|
26
|
+
#
|
27
|
+
# @param raw [Hash] Raw content of configuration file.
|
28
|
+
#
|
29
|
+
# @return [Highway::Compiler::Parse::Tree::Root]
|
30
|
+
def parse(raw:)
|
31
|
+
|
32
|
+
parse_tree = Parse::Tree::Root.new(version: 1)
|
33
|
+
|
34
|
+
validate_toplevel_keys(raw: raw)
|
35
|
+
|
36
|
+
parse_variables(raw: raw, parse_tree: parse_tree)
|
37
|
+
parse_steps(raw: raw, parse_tree: parse_tree)
|
38
|
+
|
39
|
+
parse_tree
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def validate_toplevel_keys(raw:)
|
46
|
+
expected = %w(version variables bootstrap test deploy report)
|
47
|
+
raw.each_key do |key|
|
48
|
+
assert_toplevel_key_valid(key, expected: expected)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_variables(raw:, parse_tree:)
|
53
|
+
variables = raw.fetch("variables", {})
|
54
|
+
assert_value_type(variables, expected: Hash, keypath: ["variables"])
|
55
|
+
variables.each_pair do |preset, names_and_values|
|
56
|
+
assert_value_type(names_and_values, expected: Hash, keypath: ["variables", preset])
|
57
|
+
names_and_values.each_pair do |name, value|
|
58
|
+
parse_tree.add_variable(name: name, value: value, preset: preset)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_steps(raw:, parse_tree:)
|
64
|
+
%w(bootstrap test deploy report).each do |stage|
|
65
|
+
presets = raw.fetch(stage, {})
|
66
|
+
assert_value_type(presets, expected: Hash, keypath: [stage])
|
67
|
+
presets.each_pair do |preset, steps|
|
68
|
+
assert_value_type(steps, expected: Array, keypath: [stage, preset])
|
69
|
+
steps.each_with_index do |step, step_index|
|
70
|
+
assert_value_type(step, expected: Hash, keypath: [stage, preset, step_index])
|
71
|
+
assert_value_length(step, expected: 1, keypath: [stage, preset, step_index])
|
72
|
+
assert_value_type(step.values.first, expected: Hash, keypath: [stage, preset, step.keys.first])
|
73
|
+
parse_tree.add_step(index: step_index, name: step.keys.first, parameters: step.values.first, preset: preset, stage: stage)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def assert_toplevel_key_valid(actual, expected:)
|
80
|
+
unless expected.include?(actual)
|
81
|
+
expected_keys = expected.map { |key| "'#{key}'" }.join(", ")
|
82
|
+
@interface.fatal!("Invalid top-level key: '#{actual}'. Expected one of: [#{expected_keys}].")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def assert_value_type(actual, expected:, keypath:)
|
87
|
+
if expected.is_a?(Class)
|
88
|
+
unless actual.is_a?(expected)
|
89
|
+
@interface.fatal!("Invalid type of value: '#{actual}' at: '#{keypath}'. Expected: '#{expected}', got: '#{actual.class}'.")
|
90
|
+
end
|
91
|
+
elsif expected.is_a?(Array)
|
92
|
+
unless (expected.any? { |klass| actual.is_a?(klass) })
|
93
|
+
expected_types = expected.map { |klass| "'#{klass}'"}.join(", ")
|
94
|
+
@interface.fatal!("Invalid type of value: '#{actual}' at: '#{keypath}'. Expected one of: [#{expected_types}], got: '#{actual.class}'.")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def assert_value_length(actual, expected:, keypath:)
|
100
|
+
unless actual.length == expected
|
101
|
+
@interface.fatal!("Invalid length of value at: '#{keypath}'. Expected: #{expected}, got: #{actual}.")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|