Skip to content

Commit d845e05

Browse files
committed
(PUP-6841) Add example of parser API usage
1 parent bf242dc commit d845e05

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed

parser/example.rb

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
require 'puppet'
2+
3+
# Example of a module setting everything up to perform custom
4+
# validation of an AST model produced by parsing puppet source.
5+
#
6+
module MyValidation
7+
8+
# A module for the new issues that the this new kind of validation will generate
9+
#
10+
module Issues
11+
# (see Puppet::Pops::Issues#issue)
12+
# This is boiler plate code
13+
def self.issue (issue_code, *args, &block)
14+
Puppet::Pops::Issues.issue(issue_code, *args, &block)
15+
end
16+
17+
INVALID_WORD = issue :INVALID_WORD, :text do
18+
"The word '#{text}' is not a real word."
19+
end
20+
end
21+
22+
# This is the class that performs the actual validation by checking input
23+
# and sending issues to an acceptor.
24+
#
25+
class MyChecker
26+
attr_reader :acceptor
27+
def initialize(diagnostics_producer)
28+
@@bad_word_visitor ||= Puppet::Pops::Visitor.new(nil, "badword", 0, 0)
29+
# add more polymorphic checkers here
30+
31+
# remember the acceptor where the issues should be sent
32+
@acceptor = diagnostics_producer
33+
end
34+
35+
# Validates the entire model by visiting each model element and calling the various checkers
36+
# (here just the example 'check_bad_word'), but a series of things could be checked.
37+
#
38+
# The result is collected by the configured diagnostic provider/acceptor
39+
# given when creating this Checker.
40+
#
41+
# Returns the @acceptor for convenient chaining of operations
42+
#
43+
def validate(model)
44+
# tree iterate the model, and call the checks for each element
45+
46+
# While not strictly needed, here a check is made of the root (the "Program" AST object)
47+
check_bad_word(model)
48+
49+
# Then check all of its content
50+
model.eAllContents.each {|m| check_bad_word(m) }
51+
@acceptor
52+
end
53+
54+
# perform the bad_word check on one AST element
55+
# (this is done using a polymorphic visitor)
56+
#
57+
def check_bad_word(o)
58+
@@bad_word_visitor.visit_this_0(self, o)
59+
end
60+
61+
protected
62+
63+
def badword_Object(o)
64+
# ignore all not covered by an explicit badword_xxx method
65+
end
66+
67+
# A bare word is a QualifiedName
68+
#
69+
def badword_QualifiedName(o)
70+
if o.value == 'bigly'
71+
acceptor.accept(Issues::INVALID_WORD, o, :text => o.value)
72+
end
73+
end
74+
end
75+
76+
class MyFactory < Puppet::Pops::Validation::Factory
77+
# Produces the checker to use
78+
def checker(diagnostic_producer)
79+
MyChecker.new(diagnostic_producer)
80+
end
81+
82+
# Produces the label provider to use.
83+
#
84+
def label_provider
85+
# We are dealing with AST, so the existing one will do fine.
86+
# This is what translates objects into a meaningful description of what that thing is
87+
#
88+
Puppet::Pops::Model::ModelLabelProvider.new()
89+
end
90+
91+
# Produces the severity producer to use. Here it is configured what severity issues have
92+
# if they are not all errors. (If they are all errors this method is not needed at all).
93+
#
94+
def severity_producer
95+
# Gets a default severity producer that is then configured below
96+
p = super
97+
98+
# Configure each issue that should **not** be an error
99+
#
100+
p[Issues::INVALID_WORD] = :warning
101+
102+
# examples of what may be done here
103+
# p[Issues::SOME_ISSUE] = <some condition> ? :ignore : :warning
104+
# p[Issues::A_DEPRECATION] = :deprecation
105+
106+
# return the configured producer
107+
p
108+
end
109+
110+
# Allow simpler call when not caring about getting the actual acceptor
111+
def diagnostic_producer(acceptor=nil)
112+
acceptor.nil? ? super(Puppet::Pops::Validation::Acceptor.new) : super(acceptor)
113+
end
114+
end
115+
116+
# We create a diagnostic formatter that outputs the error with a simple predefined
117+
# format for location, severity, and the message. This format is a typical output from
118+
# something like a linter or compiler.
119+
# (We do this because there is a bug in the DiagnosticFormatter's `format` method prior to
120+
# Puppet 4.9.0. It could otherwise have been used directly.
121+
#
122+
class Formatter < Puppet::Pops::Validation::DiagnosticFormatter
123+
def format(diagnostic)
124+
"#{format_location(diagnostic)} #{format_severity(diagnostic)}#{format_message(diagnostic)}"
125+
end
126+
end
127+
end
128+
129+
# -- Example usage of the new validator
130+
131+
# Get a parser
132+
parser = Puppet::Pops::Parser::EvaluatingParser.singleton
133+
134+
# parse without validation
135+
result = parser.parser.parse_string('$x = if 1 < 2 { smaller } else { bigly }', 'testing.pp')
136+
result = result.model
137+
138+
# validate using the default validator and get hold of the acceptor containing the result
139+
acceptor = parser.validate(result)
140+
141+
# -- At this point, we have done everything `puppet parser validate` does except report the errors
142+
# and raise an exception if there were errors.
143+
144+
# The acceptor may now contain errors and warnings as found by the standard puppet validation.
145+
# We could look at the amount of errors/warnings produced and decide it is too much already
146+
# or we could simply continue. Here, some feedback is printed:
147+
#
148+
puts "Standard validation errors found: #{acceptor.error_count}"
149+
puts "Standard validation warnings found: #{acceptor.warning_count}"
150+
151+
# Validate using the 'MyValidation' defined above
152+
#
153+
validator = MyValidation::MyFactory.new().validator(acceptor)
154+
155+
# Perform the validation - this adds the produced errors and warnings into the same acceptor
156+
# as was used for the standard validation
157+
#
158+
validator.validate(result)
159+
160+
# We can print total statistics
161+
# (If we wanted to generated the extra validation separately we would have had to
162+
# use a separate acceptor, and then add everything in that acceptor to the main one.)
163+
#
164+
puts "Total validation errors found: #{acceptor.error_count}"
165+
puts "Total validation warnings found: #{acceptor.warning_count}"
166+
167+
# Output the errors and warnings using a provided simple starter formatter
168+
formatter = MyValidation::Formatter.new
169+
170+
puts "\nErrors and warnings found:"
171+
acceptor.errors_and_warnings.each do |diagnostic|
172+
puts formatter.format(diagnostic)
173+
end

0 commit comments

Comments
 (0)