formalist 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.
- checksums.yaml +7 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +96 -0
- data/LICENSE.md +9 -0
- data/README.md +27 -0
- data/Rakefile +13 -0
- data/lib/formalist/definition_compiler.rb +61 -0
- data/lib/formalist/display_adapters/default.rb +9 -0
- data/lib/formalist/display_adapters/radio.rb +19 -0
- data/lib/formalist/display_adapters/select.rb +19 -0
- data/lib/formalist/display_adapters.rb +12 -0
- data/lib/formalist/form/definition/attr.rb +20 -0
- data/lib/formalist/form/definition/component.rb +32 -0
- data/lib/formalist/form/definition/field.rb +43 -0
- data/lib/formalist/form/definition/group.rb +31 -0
- data/lib/formalist/form/definition/many.rb +41 -0
- data/lib/formalist/form/definition/section.rb +23 -0
- data/lib/formalist/form/definition.rb +37 -0
- data/lib/formalist/form/definition_context.rb +15 -0
- data/lib/formalist/form/result/attr.rb +82 -0
- data/lib/formalist/form/result/component.rb +51 -0
- data/lib/formalist/form/result/field.rb +77 -0
- data/lib/formalist/form/result/group.rb +51 -0
- data/lib/formalist/form/result/many.rb +127 -0
- data/lib/formalist/form/result/section.rb +54 -0
- data/lib/formalist/form/result.rb +15 -0
- data/lib/formalist/form.rb +44 -0
- data/lib/formalist/output_compiler.rb +65 -0
- data/lib/formalist/validation/collection_rules_compiler.rb +77 -0
- data/lib/formalist/validation/predicate_list_compiler.rb +73 -0
- data/lib/formalist/validation/value_rules_compiler.rb +92 -0
- data/lib/formalist/version.rb +3 -0
- data/lib/formalist.rb +7 -0
- data/spec/examples.txt +8 -0
- data/spec/integration/display_adapters_spec.rb +49 -0
- data/spec/integration/form_spec.rb +22 -0
- data/spec/integration/new_spec.rb +27 -0
- data/spec/integration/validation_spec.rb +83 -0
- data/spec/spec_helper.rb +102 -0
- data/spec/unit/output_compiler_spec.rb +51 -0
- metadata +252 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
require "formalist/validation/value_rules_compiler"
|
2
|
+
require "formalist/validation/predicate_list_compiler"
|
3
|
+
|
4
|
+
module Formalist
|
5
|
+
class Form
|
6
|
+
class Result
|
7
|
+
class Field
|
8
|
+
attr_reader :definition, :input, :rules, :predicates, :errors
|
9
|
+
|
10
|
+
def initialize(definition, input, rules, errors)
|
11
|
+
rules_compiler = Validation::ValueRulesCompiler.new(definition.name)
|
12
|
+
predicates_compiler = Validation::PredicateListCompiler.new
|
13
|
+
|
14
|
+
@definition = definition
|
15
|
+
@input = input[definition.name]
|
16
|
+
@rules = rules_compiler.(rules)
|
17
|
+
@predicates = predicates_compiler.(@rules)
|
18
|
+
@errors = errors[definition.name].to_a[0] || []
|
19
|
+
end
|
20
|
+
|
21
|
+
# Converts the field into an array format for including in a form's
|
22
|
+
# abstract syntax tree.
|
23
|
+
#
|
24
|
+
# The array takes the following format:
|
25
|
+
#
|
26
|
+
# ```
|
27
|
+
# [:field, [params]]
|
28
|
+
# ```
|
29
|
+
#
|
30
|
+
# With the following parameters:
|
31
|
+
#
|
32
|
+
# 1. Field name
|
33
|
+
# 1. Field type
|
34
|
+
# 1. Display variant name
|
35
|
+
# 1. Input data
|
36
|
+
# 1. Validation rules (if any)
|
37
|
+
# 1. Validation error messages (if any)
|
38
|
+
# 1. Field configuration
|
39
|
+
#
|
40
|
+
# @example "email" field
|
41
|
+
# field.to_ary # =>
|
42
|
+
# # [:field, [
|
43
|
+
# # :email,
|
44
|
+
# # "string",
|
45
|
+
# # "default",
|
46
|
+
# # "invalid email value",
|
47
|
+
# # [
|
48
|
+
# # [:and, [
|
49
|
+
# # [:predicate, [:filled?, []]],
|
50
|
+
# # [:predicate, [:format?, [/\s+@\s+\.\s+/]]]
|
51
|
+
# # ]]
|
52
|
+
# # ],
|
53
|
+
# # ["email is in invalid format"],
|
54
|
+
# # [
|
55
|
+
# # [:some_config_name, :some_config_value]
|
56
|
+
# # ]
|
57
|
+
# # ]]
|
58
|
+
#
|
59
|
+
# @return [Array] the field as an array.
|
60
|
+
def to_ary
|
61
|
+
# errors looks like this
|
62
|
+
# {:field_name => [["pages is missing", "another error message"], nil]}
|
63
|
+
|
64
|
+
[:field, [
|
65
|
+
definition.name,
|
66
|
+
definition.type,
|
67
|
+
definition.display_variant,
|
68
|
+
Dry::Data[definition.type].(input),
|
69
|
+
predicates,
|
70
|
+
errors,
|
71
|
+
definition.config.to_a,
|
72
|
+
]]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Formalist
|
2
|
+
class Form
|
3
|
+
class Result
|
4
|
+
class Group
|
5
|
+
attr_reader :definition, :input, :errors
|
6
|
+
attr_reader :children
|
7
|
+
|
8
|
+
def initialize(definition, input, rules, errors)
|
9
|
+
@definition = definition
|
10
|
+
@input = input
|
11
|
+
@rules = rules
|
12
|
+
@errors = errors
|
13
|
+
@children = definition.children.map { |el| el.(input, rules, errors) }
|
14
|
+
end
|
15
|
+
|
16
|
+
# Converts the group into an array format for including in a form's
|
17
|
+
# abstract syntax tree.
|
18
|
+
#
|
19
|
+
# The array takes the following format:
|
20
|
+
#
|
21
|
+
# ```
|
22
|
+
# [:group, [params]]
|
23
|
+
# ```
|
24
|
+
#
|
25
|
+
# With the following parameters:
|
26
|
+
#
|
27
|
+
# 1. Group configuration
|
28
|
+
# 1. Child form elements
|
29
|
+
#
|
30
|
+
# @example
|
31
|
+
# group.to_ary # =>
|
32
|
+
# # [:group, [
|
33
|
+
# # [
|
34
|
+
# # [:some_config_name, :some_config_value]
|
35
|
+
# # ],
|
36
|
+
# # [
|
37
|
+
# # ...child elements...
|
38
|
+
# # ]
|
39
|
+
# # ]]
|
40
|
+
#
|
41
|
+
# @return [Array] the group as an array.
|
42
|
+
def to_ary
|
43
|
+
[:group, [
|
44
|
+
definition.config.to_a,
|
45
|
+
children.map(&:to_ary),
|
46
|
+
]]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require "formalist/validation/collection_rules_compiler"
|
2
|
+
require "formalist/validation/value_rules_compiler"
|
3
|
+
require "formalist/validation/predicate_list_compiler"
|
4
|
+
|
5
|
+
module Formalist
|
6
|
+
class Form
|
7
|
+
class Result
|
8
|
+
class Many
|
9
|
+
attr_reader :definition, :input, :value_rules, :value_predicates, :collection_rules, :errors
|
10
|
+
attr_reader :child_template, :children
|
11
|
+
|
12
|
+
def initialize(definition, input, rules, errors)
|
13
|
+
value_rules_compiler = Validation::ValueRulesCompiler.new(definition.name)
|
14
|
+
value_predicates_compiler = Validation::PredicateListCompiler.new
|
15
|
+
collection_rules_compiler = Validation::CollectionRulesCompiler.new(definition.name)
|
16
|
+
|
17
|
+
@definition = definition
|
18
|
+
@input = input.fetch(definition.name, [])
|
19
|
+
@value_rules = value_rules_compiler.(rules)
|
20
|
+
@value_predicates = value_predicates_compiler.(@value_rules)
|
21
|
+
@collection_rules = collection_rules_compiler.(rules)
|
22
|
+
@errors = errors.fetch(definition.name, [])[0] || []
|
23
|
+
@child_template = build_child_template
|
24
|
+
@children = build_children
|
25
|
+
end
|
26
|
+
|
27
|
+
# Converts a collection of "many" repeating elements into an array
|
28
|
+
# format for including in a form's abstract syntax tree.
|
29
|
+
#
|
30
|
+
# The array takes the following format:
|
31
|
+
#
|
32
|
+
# ```
|
33
|
+
# [:many, [params]]
|
34
|
+
# ```
|
35
|
+
#
|
36
|
+
# With the following parameters:
|
37
|
+
#
|
38
|
+
# 1. Collection array name
|
39
|
+
# 1. Collection validation rules (if any)
|
40
|
+
# 1. Collection error messages (if any)
|
41
|
+
# 1. Collection configuration
|
42
|
+
# 1. Child element "template" (i.e. the form elements comprising a
|
43
|
+
# single entry in the collection of "many" elements, without any
|
44
|
+
# user data associated)
|
45
|
+
# 1. Child elements, one for each of the entries in the input data (or
|
46
|
+
# none, if there is no or empty input data)
|
47
|
+
#
|
48
|
+
# @example "locations" collection
|
49
|
+
# many.to_ary # =>
|
50
|
+
# # [:many, [
|
51
|
+
# # :locations,
|
52
|
+
# # [[:predicate, [:min_size?, [3]]]],
|
53
|
+
# # ["locations size cannot be less than 3"],
|
54
|
+
# # [
|
55
|
+
# # [:allow_create, true],
|
56
|
+
# # [:allow_update, true],
|
57
|
+
# # [:allow_destroy, true],
|
58
|
+
# # [:allow_reorder, true]
|
59
|
+
# # ],
|
60
|
+
# # [
|
61
|
+
# # [:field, [:name, "string", "default", nil, [], [], []]],
|
62
|
+
# # [:field, [:address, "string", "default", nil, [], [], []]]
|
63
|
+
# # [
|
64
|
+
# # [
|
65
|
+
# # [:field, [:name, "string", "default", "Icelab Canberra", [], [], []]],
|
66
|
+
# # [:field, [:address, "string", "default", "Canberra, ACT, Australia", [], [], []]]
|
67
|
+
# # ],
|
68
|
+
# # [
|
69
|
+
# # [:field, [:name, "string", "default", "Icelab Melbourne", [], [], []]],
|
70
|
+
# # [:field, [:address, "string", "default", "Melbourne, VIC, Australia", [], [], []]]
|
71
|
+
# # ]
|
72
|
+
# # ]
|
73
|
+
# # ]]
|
74
|
+
#
|
75
|
+
# @return [Array] the collection as an array.
|
76
|
+
def to_ary
|
77
|
+
local_errors = errors.select { |e| e.is_a?(String) }
|
78
|
+
|
79
|
+
[:many, [
|
80
|
+
definition.name,
|
81
|
+
value_predicates,
|
82
|
+
local_errors,
|
83
|
+
definition.config.to_a,
|
84
|
+
child_template.map(&:to_ary),
|
85
|
+
children.map { |el_list| el_list.map(&:to_ary) },
|
86
|
+
]]
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def build_child_template
|
92
|
+
template_input = {}
|
93
|
+
template_errors = {}
|
94
|
+
|
95
|
+
definition.children.map { |el| el.(template_input, collection_rules, template_errors)}
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_children
|
99
|
+
# child errors looks like this:
|
100
|
+
# {:links=>
|
101
|
+
# [[{:links=>
|
102
|
+
# [[{:url=>[["url must be filled"], ""]}],
|
103
|
+
# {:name=>"personal", :url=>""}]}],
|
104
|
+
# [{:name=>"company", :url=>"http://icelab.com.au"},
|
105
|
+
# {:name=>"personal", :url=>""}]]}
|
106
|
+
#
|
107
|
+
# or local errors:
|
108
|
+
# {:links=>[["links is missing"], nil]}
|
109
|
+
|
110
|
+
child_errors = errors[0].is_a?(Hash) ? errors : {}
|
111
|
+
|
112
|
+
input.map { |child_input|
|
113
|
+
local_child_errors = child_errors.select { |e|
|
114
|
+
e.is_a?(Hash)
|
115
|
+
}.map { |e|
|
116
|
+
e[definition.name]
|
117
|
+
}.detect { |e|
|
118
|
+
e[1] == child_input
|
119
|
+
}.to_a.dig(0, 0) || {}
|
120
|
+
|
121
|
+
definition.children.map { |el| el.(child_input, collection_rules, local_child_errors) }
|
122
|
+
}
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Formalist
|
2
|
+
class Form
|
3
|
+
class Result
|
4
|
+
class Section
|
5
|
+
attr_reader :definition, :input, :rules, :errors
|
6
|
+
attr_reader :children
|
7
|
+
|
8
|
+
def initialize(definition, input, rules, errors)
|
9
|
+
@definition = definition
|
10
|
+
@input = input
|
11
|
+
@rules = rules
|
12
|
+
@errors = errors
|
13
|
+
@children = definition.children.map { |el| el.(input, rules, errors) }
|
14
|
+
end
|
15
|
+
|
16
|
+
# Converts the section into an array format for including in a form's
|
17
|
+
# abstract syntax tree.
|
18
|
+
#
|
19
|
+
# The array takes the following format:
|
20
|
+
#
|
21
|
+
# ```
|
22
|
+
# [:section, [params]]
|
23
|
+
# ```
|
24
|
+
#
|
25
|
+
# With the following parameters:
|
26
|
+
#
|
27
|
+
# 1. Section name
|
28
|
+
# 1. Section configuration
|
29
|
+
# 1. Child form elements
|
30
|
+
#
|
31
|
+
# @example "content" section
|
32
|
+
# section.to_ary # =>
|
33
|
+
# # [:section, [
|
34
|
+
# # :content,
|
35
|
+
# # [
|
36
|
+
# # [:some_config_name, :some_config_value]
|
37
|
+
# # ],
|
38
|
+
# # [
|
39
|
+
# # ...child elements...
|
40
|
+
# # ]
|
41
|
+
# # ]]
|
42
|
+
#
|
43
|
+
# @return [Array] the section as an array.
|
44
|
+
def to_ary
|
45
|
+
[:section, [
|
46
|
+
definition.name,
|
47
|
+
definition.config.to_a,
|
48
|
+
children.map(&:to_ary),
|
49
|
+
]]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "dry-configurable"
|
2
|
+
require "formalist/definition_compiler"
|
3
|
+
require "formalist/display_adapters"
|
4
|
+
require "formalist/form/definition"
|
5
|
+
require "formalist/form/result"
|
6
|
+
|
7
|
+
module Formalist
|
8
|
+
class Form
|
9
|
+
extend Dry::Configurable
|
10
|
+
extend Definition
|
11
|
+
|
12
|
+
setting :display_adapters, DisplayAdapters
|
13
|
+
|
14
|
+
def self.display_adapters
|
15
|
+
config.display_adapters
|
16
|
+
end
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
def self.elements
|
20
|
+
@__elements__ ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
attr_reader :elements
|
25
|
+
|
26
|
+
# @api private
|
27
|
+
attr_reader :schema
|
28
|
+
|
29
|
+
def initialize(schema: nil)
|
30
|
+
definition_compiler = DefinitionCompiler.new(self.class.display_adapters)
|
31
|
+
@elements = definition_compiler.call(self.class.elements)
|
32
|
+
@schema = schema
|
33
|
+
end
|
34
|
+
|
35
|
+
def call(input = {}, options = {})
|
36
|
+
validate = options.fetch(:validate, true)
|
37
|
+
|
38
|
+
rules = schema ? schema.rules.map(&:to_ary) : []
|
39
|
+
errors = validate && schema ? schema.(input).messages : {}
|
40
|
+
|
41
|
+
Result.new(elements.map { |el| el.(input, rules, errors) })
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "dry-data"
|
2
|
+
|
3
|
+
module Formalist
|
4
|
+
class OutputCompiler
|
5
|
+
FORM_TYPES = %w[
|
6
|
+
bool
|
7
|
+
date
|
8
|
+
date_time
|
9
|
+
decimal
|
10
|
+
float
|
11
|
+
int
|
12
|
+
time
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
def call(ast)
|
16
|
+
ast.map { |node| visit(node) }.inject(:merge)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def visit(node)
|
22
|
+
send(:"visit_#{node[0]}", node[1])
|
23
|
+
end
|
24
|
+
|
25
|
+
def visit_attr(data)
|
26
|
+
name, predicates, errors, children = data
|
27
|
+
|
28
|
+
{name => children.map { |node| visit(node) }.inject(:merge) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def visit_field(data)
|
32
|
+
name, type, display_variant, value, predicates, errors = data
|
33
|
+
|
34
|
+
{name => coerce(value, type: type)}
|
35
|
+
end
|
36
|
+
|
37
|
+
def visit_group(data)
|
38
|
+
config, children = data
|
39
|
+
|
40
|
+
children.map { |node| visit(node) }.inject(:merge)
|
41
|
+
end
|
42
|
+
|
43
|
+
def visit_many(data)
|
44
|
+
name, predicates, errors, config, template, children = data
|
45
|
+
|
46
|
+
{name => children.map { |item| item.map { |node| visit(node) }.inject(:merge) }}
|
47
|
+
end
|
48
|
+
|
49
|
+
def visit_section(data)
|
50
|
+
name, config, children = data
|
51
|
+
|
52
|
+
children.map { |node| visit(node) }.inject(:merge)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def coerce(value, type:)
|
58
|
+
if FORM_TYPES.include?(type)
|
59
|
+
Dry::Data["form.#{type}"].(value)
|
60
|
+
else
|
61
|
+
Dry::Data[type].(value)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Formalist
|
2
|
+
module Validation
|
3
|
+
class CollectionRulesCompiler
|
4
|
+
attr_reader :target_name
|
5
|
+
|
6
|
+
def initialize(target_name)
|
7
|
+
@target_name = target_name
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(ast)
|
11
|
+
ast.map { |node| visit(node) }.reduce([], :concat).each_slice(2).to_a
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def visit(node)
|
17
|
+
name, nodes = node
|
18
|
+
send(:"visit_#{name}", nodes)
|
19
|
+
end
|
20
|
+
|
21
|
+
def visit_set(node)
|
22
|
+
name, rules = node
|
23
|
+
return [] unless name == target_name
|
24
|
+
|
25
|
+
rules.flatten(1)
|
26
|
+
end
|
27
|
+
|
28
|
+
def visit_each(node)
|
29
|
+
name, rule = node
|
30
|
+
return [] unless name == target_name
|
31
|
+
|
32
|
+
visit(rule)
|
33
|
+
end
|
34
|
+
|
35
|
+
def visit_predicate(node)
|
36
|
+
name, args = node
|
37
|
+
[:predicate, node]
|
38
|
+
end
|
39
|
+
|
40
|
+
def visit_and(node)
|
41
|
+
left, right = node
|
42
|
+
flatten_logical_operation(:and, [visit(left), visit(right)])
|
43
|
+
end
|
44
|
+
|
45
|
+
def visit_or(node)
|
46
|
+
left, right = node
|
47
|
+
flatten_logical_operation(:or, [visit(left), visit(right)])
|
48
|
+
end
|
49
|
+
|
50
|
+
def visit_xor(node)
|
51
|
+
left, right = node
|
52
|
+
flatten_logical_operation(:xor, [visit(left), visit(right)])
|
53
|
+
end
|
54
|
+
|
55
|
+
def visit_implication(node)
|
56
|
+
left, right = node
|
57
|
+
flatten_logical_operation(:implication, [visit(left), visit(right)])
|
58
|
+
end
|
59
|
+
|
60
|
+
def method_missing(name, *args)
|
61
|
+
[]
|
62
|
+
end
|
63
|
+
|
64
|
+
def flatten_logical_operation(name, contents)
|
65
|
+
contents = contents.select(&:any?)
|
66
|
+
|
67
|
+
if contents.length == 0
|
68
|
+
[]
|
69
|
+
elsif contents.length == 1
|
70
|
+
contents.first
|
71
|
+
else
|
72
|
+
[name, contents]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Formalist
|
2
|
+
module Validation
|
3
|
+
class PredicateListCompiler
|
4
|
+
IGNORED_PREDICATES = [:key?].freeze
|
5
|
+
|
6
|
+
def call(ast)
|
7
|
+
ast.map { |node| visit(node) }.reduce([], :concat).each_slice(2).to_a
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def visit(node)
|
13
|
+
name, nodes = node
|
14
|
+
send(:"visit_#{name}", nodes)
|
15
|
+
end
|
16
|
+
|
17
|
+
def visit_key(node)
|
18
|
+
name, predicate = node
|
19
|
+
|
20
|
+
visit(predicate)
|
21
|
+
end
|
22
|
+
|
23
|
+
def visit_val(node)
|
24
|
+
name, predicate = node
|
25
|
+
|
26
|
+
visit(predicate)
|
27
|
+
end
|
28
|
+
|
29
|
+
def visit_predicate(node)
|
30
|
+
name, args = node
|
31
|
+
return [] if IGNORED_PREDICATES.include?(name)
|
32
|
+
|
33
|
+
[:predicate, node]
|
34
|
+
end
|
35
|
+
|
36
|
+
def visit_and(node)
|
37
|
+
left, right = node
|
38
|
+
flatten_logical_operation(:and, [visit(left), visit(right)])
|
39
|
+
end
|
40
|
+
|
41
|
+
def visit_or(node)
|
42
|
+
left, right = node
|
43
|
+
flatten_logical_operation(:or, [visit(left), visit(right)])
|
44
|
+
end
|
45
|
+
|
46
|
+
def visit_xor(node)
|
47
|
+
left, right = node
|
48
|
+
flatten_logical_operation(:xor, [visit(left), visit(right)])
|
49
|
+
end
|
50
|
+
|
51
|
+
def visit_implication(node)
|
52
|
+
left, right = node
|
53
|
+
flatten_logical_operation(:implication, [visit(left), visit(right)])
|
54
|
+
end
|
55
|
+
|
56
|
+
def method_missing(name, *args)
|
57
|
+
[]
|
58
|
+
end
|
59
|
+
|
60
|
+
def flatten_logical_operation(name, contents)
|
61
|
+
contents = contents.select(&:any?)
|
62
|
+
|
63
|
+
if contents.length == 0
|
64
|
+
[]
|
65
|
+
elsif contents.length == 1
|
66
|
+
contents.first
|
67
|
+
else
|
68
|
+
[name, contents]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Formalist
|
2
|
+
module Validation
|
3
|
+
class ValueRulesCompiler
|
4
|
+
attr_reader :target_name
|
5
|
+
|
6
|
+
def initialize(target_name)
|
7
|
+
@target_name = target_name
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(ast)
|
11
|
+
ast.map { |node| visit(node) }.reduce([], :concat).each_slice(2).to_a
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def visit(node)
|
17
|
+
name, nodes = node
|
18
|
+
send(:"visit_#{name}", nodes)
|
19
|
+
end
|
20
|
+
|
21
|
+
def visit_key(node)
|
22
|
+
# We can ignore "key" checks - we'll only pick up rules for keys we
|
23
|
+
# know will exist, since they're attached to fields.
|
24
|
+
[]
|
25
|
+
end
|
26
|
+
|
27
|
+
def visit_val(node)
|
28
|
+
name, predicate = node
|
29
|
+
return [] unless name == target_name
|
30
|
+
|
31
|
+
# Skip the "val" prefix
|
32
|
+
|
33
|
+
[:val, [name, visit(predicate)]]
|
34
|
+
end
|
35
|
+
|
36
|
+
# def visit_set(node)
|
37
|
+
# name, rules = node
|
38
|
+
# return [] unless name == target_name
|
39
|
+
|
40
|
+
# rules.flatten(1)
|
41
|
+
# end
|
42
|
+
|
43
|
+
# def visit_each(node)
|
44
|
+
# name, rule = node
|
45
|
+
# return [] unless name == target_name
|
46
|
+
|
47
|
+
# visit(rule)
|
48
|
+
# end
|
49
|
+
|
50
|
+
def visit_predicate(node)
|
51
|
+
name, args = node
|
52
|
+
[:predicate, node]
|
53
|
+
end
|
54
|
+
|
55
|
+
def visit_and(node)
|
56
|
+
left, right = node
|
57
|
+
flatten_logical_operation(:and, [visit(left), visit(right)])
|
58
|
+
end
|
59
|
+
|
60
|
+
def visit_or(node)
|
61
|
+
left, right = node
|
62
|
+
flatten_logical_operation(:or, [visit(left), visit(right)])
|
63
|
+
end
|
64
|
+
|
65
|
+
def visit_xor(node)
|
66
|
+
left, right = node
|
67
|
+
flatten_logical_operation(:xor, [visit(left), visit(right)])
|
68
|
+
end
|
69
|
+
|
70
|
+
def visit_implication(node)
|
71
|
+
left, right = node
|
72
|
+
flatten_logical_operation(:implication, [visit(left), visit(right)])
|
73
|
+
end
|
74
|
+
|
75
|
+
def method_missing(name, *args)
|
76
|
+
[]
|
77
|
+
end
|
78
|
+
|
79
|
+
def flatten_logical_operation(name, contents)
|
80
|
+
contents = contents.select(&:any?)
|
81
|
+
|
82
|
+
if contents.length == 0
|
83
|
+
[]
|
84
|
+
elsif contents.length == 1
|
85
|
+
contents.first
|
86
|
+
else
|
87
|
+
[name, contents]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/lib/formalist.rb
ADDED
data/spec/examples.txt
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
example_id | status | run_time |
|
2
|
+
------------------------------------------------ | ------ | --------------- |
|
3
|
+
./spec/integration/display_adapters_spec.rb[1:1] | passed | 0.00016 seconds |
|
4
|
+
./spec/integration/display_adapters_spec.rb[1:2] | passed | 0.00024 seconds |
|
5
|
+
./spec/integration/form_spec.rb[1:1] | passed | 0.00021 seconds |
|
6
|
+
./spec/integration/new_spec.rb[1:1] | passed | 0.00063 seconds |
|
7
|
+
./spec/integration/validation_spec.rb[1:1] | passed | 0.00435 seconds |
|
8
|
+
./spec/unit/output_compiler_spec.rb[1:1] | passed | 0.00049 seconds |
|