tla2dot 0.0.3
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/README.md +125 -0
- data/bin/tla2dot.rb +5 -0
- data/lib/cli/cli.rb +114 -0
- data/lib/tla2dot/parser.tab.rb +712 -0
- data/lib/tla2dot/parser.y +415 -0
- data/lib/tla2dot/template.rb +184 -0
- data/lib/tla2dot.rb +10 -0
- data/lib/utils/logger.rb +70 -0
- data/mustache/defaults.mustache +19 -0
- data/mustache/root.mustache +20 -0
- data/mustache/stateOutput.mustache +1 -0
- metadata +135 -0
@@ -0,0 +1,415 @@
|
|
1
|
+
# -*- mode: ruby -*-
|
2
|
+
#
|
3
|
+
# Parse state dump from TCL tlaplus
|
4
|
+
|
5
|
+
class Tla2DotParser
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
options no_result_var
|
10
|
+
|
11
|
+
rule
|
12
|
+
|
13
|
+
target : entries { @graph }
|
14
|
+
|
15
|
+
entries : entry
|
16
|
+
| entries entry
|
17
|
+
|
18
|
+
entry : state { @graph.add_node( val[0] ) }
|
19
|
+
| trans
|
20
|
+
|
21
|
+
|
22
|
+
trans : TRANS '-->' INTEGER { @graph.add_edge( val[0], val[2] ) }
|
23
|
+
|
24
|
+
|
25
|
+
# state : STATE variables { Node.new( val[0], val[1] ) }
|
26
|
+
# | STATE { Node.new( val[0], {} ) }
|
27
|
+
|
28
|
+
state : state_def variables { val[0].variables=val[1]; val[0] }
|
29
|
+
| state_def
|
30
|
+
|
31
|
+
state_def : state_id
|
32
|
+
| state_id actiondef
|
33
|
+
|
34
|
+
state_id : STATE { Node.new( val[0], {} ) }
|
35
|
+
|
36
|
+
|
37
|
+
actiondef : '<' actionspecs '>'
|
38
|
+
|
39
|
+
actionspecs : actionspec
|
40
|
+
| actionspecs actionspec
|
41
|
+
|
42
|
+
actionspec : IDENT
|
43
|
+
| ','
|
44
|
+
| INTEGER
|
45
|
+
|
46
|
+
variables : variable
|
47
|
+
| variables variable { k=val[1].keys.first; val[0][k] = val[1][k]; val[0] }
|
48
|
+
|
49
|
+
variable : AND name '=' value { { val[1] => val[3] } }
|
50
|
+
|
51
|
+
name : IDENT
|
52
|
+
|
53
|
+
value : INTEGER { val[0].to_i }
|
54
|
+
| STRING
|
55
|
+
| IDENT
|
56
|
+
| record
|
57
|
+
| set
|
58
|
+
| seq
|
59
|
+
|
60
|
+
record : '[' ']' { {} }
|
61
|
+
| '[' rlist ']' { val[1] }
|
62
|
+
|
63
|
+
rlist : ritem { val[0] }
|
64
|
+
| rlist ',' ritem { k=val[2].keys.first; val[0][k] = val[2][k]; val[0] }
|
65
|
+
|
66
|
+
ritem : name '|->' value { { val[0] => val[2] } }
|
67
|
+
|
68
|
+
|
69
|
+
seq : '<' '<' '>' '>' { [] }
|
70
|
+
| '<' '<' seqlist '>' '>' { val[2] }
|
71
|
+
|
72
|
+
seqlist : seqitem
|
73
|
+
| seqlist ',' seqitem { val[0].push( val[2][0] ); val[0] }
|
74
|
+
|
75
|
+
seqitem : value { [ val[0] ] }
|
76
|
+
|
77
|
+
|
78
|
+
set : '{' '}' { [] }
|
79
|
+
| '{' slist '}' { val[1] }
|
80
|
+
|
81
|
+
slist : sitem
|
82
|
+
| slist ',' sitem { val[0].push( val[2][0] ); val[0] }
|
83
|
+
|
84
|
+
sitem : value { [ val[0] ] }
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
---- inner
|
89
|
+
|
90
|
+
class Node
|
91
|
+
|
92
|
+
include TLA2DOT::Utils::MyLogger # mix logger
|
93
|
+
|
94
|
+
@@logger = nil; # common logger for all nodes
|
95
|
+
@@options = {}; # common logger for all nodes
|
96
|
+
|
97
|
+
|
98
|
+
# ------------------------------------------------------------------
|
99
|
+
# Attributes
|
100
|
+
|
101
|
+
# instance
|
102
|
+
attr_accessor :name #
|
103
|
+
attr_accessor :variables # hash of name=>
|
104
|
+
attr_accessor :edges # list of nodes reachable
|
105
|
+
|
106
|
+
# ------------------------------------------------------------------
|
107
|
+
# constructore
|
108
|
+
def initialize( name, variables )
|
109
|
+
@name = name
|
110
|
+
@variables = variables
|
111
|
+
@edges = []
|
112
|
+
@filter_render_variables = []
|
113
|
+
end
|
114
|
+
|
115
|
+
def myLogger
|
116
|
+
return @@logger if @@logger
|
117
|
+
@@logger = getLogger( "Node", getOptions )
|
118
|
+
@@logger.info( "#{__method__} created" )
|
119
|
+
@@logger
|
120
|
+
end
|
121
|
+
|
122
|
+
def setOptions( options )
|
123
|
+
@@options = options
|
124
|
+
end
|
125
|
+
|
126
|
+
def getOptions
|
127
|
+
@@options
|
128
|
+
end
|
129
|
+
|
130
|
+
def name
|
131
|
+
@name
|
132
|
+
end
|
133
|
+
|
134
|
+
def variables=( variables )
|
135
|
+
@variables = variables
|
136
|
+
end
|
137
|
+
|
138
|
+
def variables
|
139
|
+
@variables
|
140
|
+
end
|
141
|
+
|
142
|
+
def ==( node )
|
143
|
+
node.respond_to?( :name) && self.name == node.name
|
144
|
+
end
|
145
|
+
|
146
|
+
def add_edge( node )
|
147
|
+
@edges << node unless @edges.include?( node )
|
148
|
+
end
|
149
|
+
|
150
|
+
def successor_cnt
|
151
|
+
@edges.size
|
152
|
+
end
|
153
|
+
|
154
|
+
# array of successors for the node
|
155
|
+
def successors
|
156
|
+
@edges
|
157
|
+
end
|
158
|
+
|
159
|
+
# array tranitions (from_state->to_state) from this node to another
|
160
|
+
def transitions
|
161
|
+
myLogger.debug( "#{__method__} starting" )
|
162
|
+
successors.select{ |n| n.name != name }.map{ |n| { :to_state => n.name, :from_state => self.name } }
|
163
|
+
end
|
164
|
+
|
165
|
+
# array of variable names to render
|
166
|
+
def filter_render_variables
|
167
|
+
myLogger.debug( "#{__method__} filter_render_variables=#{@filter_render_variables} #{@filter_render_variables.class}" )
|
168
|
+
@filter_render_variables
|
169
|
+
end
|
170
|
+
|
171
|
+
# called when added to graph
|
172
|
+
def filter_render_variables=( variables )
|
173
|
+
|
174
|
+
@filter_render_variables = variables
|
175
|
+
end
|
176
|
+
|
177
|
+
# return true is state should show variable
|
178
|
+
def include_variable( variable )
|
179
|
+
|
180
|
+
return filter_render_variables.include?( variable ) if
|
181
|
+
filter_render_variables.kind_of?( Array )
|
182
|
+
|
183
|
+
# assume boolean
|
184
|
+
return filter_render_variables
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
# node variable to render as key/state hash (including 'id')
|
190
|
+
def content
|
191
|
+
myLogger.debug( "#{__method__} starting" )
|
192
|
+
vars = variables;
|
193
|
+
vars['ID'] = name
|
194
|
+
vars.select{ |k,v| include_variable( k ) }.
|
195
|
+
map { |k,v|
|
196
|
+
{:key =>k, :val => v, :state => render_value(v) }
|
197
|
+
}
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
# escape for mustache rendering
|
202
|
+
def render_value( v )
|
203
|
+
return v.to_s.
|
204
|
+
gsub( /\{/, "\\{" ).
|
205
|
+
gsub( /\}/, "\\}" ).
|
206
|
+
gsub( /=>/, "=" ).
|
207
|
+
# gsub( /\{/, "" ).
|
208
|
+
# gsub( /\}/, "" ).
|
209
|
+
# gsub( /\[/, "" ).
|
210
|
+
# gsub( /\]/, "" ).
|
211
|
+
# gsub( /\[/, "\\[" ).
|
212
|
+
# gsub( /\]/, "\\]" ).
|
213
|
+
gsub( /"/, '\\"' )
|
214
|
+
# case
|
215
|
+
# when v.kind_of?( Hash )
|
216
|
+
# return v.to_s.gsub( /\{/, "\\{" ).gsub( /\}/, "\\}" )
|
217
|
+
# else
|
218
|
+
# return v
|
219
|
+
# end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
|
227
|
+
class Graph
|
228
|
+
|
229
|
+
include TLA2DOT::Utils::MyLogger # mix logger
|
230
|
+
|
231
|
+
|
232
|
+
attr_reader :nodes #
|
233
|
+
|
234
|
+
def initialize( filter_render_variables, options )
|
235
|
+
@nodes = {}
|
236
|
+
# filter_render_variables passed to node
|
237
|
+
@filter_render_variables = filter_render_variables
|
238
|
+
setOptions( options )
|
239
|
+
end
|
240
|
+
|
241
|
+
def myLogger
|
242
|
+
return @logger if @logger
|
243
|
+
@logger = getLogger( "Graph", getOptions )
|
244
|
+
@logger.info( "#{__method__} created" )
|
245
|
+
@logger
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
def setOptions( options )
|
250
|
+
@options = options
|
251
|
+
end
|
252
|
+
|
253
|
+
def getOptions
|
254
|
+
@options
|
255
|
+
end
|
256
|
+
|
257
|
+
def add_node(node)
|
258
|
+
node.filter_render_variables=( @filter_render_variables )
|
259
|
+
node.setOptions( getOptions )
|
260
|
+
myLogger.debug( "#{__method__} added node #{node}, @filter_render_variables=#{@filter_render_variables} #{@filter_render_variables.class}" )
|
261
|
+
@nodes[node.name] = node
|
262
|
+
self
|
263
|
+
end
|
264
|
+
|
265
|
+
|
266
|
+
def nodes
|
267
|
+
@nodes
|
268
|
+
end
|
269
|
+
|
270
|
+
def states
|
271
|
+
@nodes.values
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
def node_cnt
|
276
|
+
@nodes.size
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
def add_edge(predecessor_name, successor_name)
|
281
|
+
|
282
|
+
predecessor_node = @nodes[predecessor_name]
|
283
|
+
raise "Unknown precessor node #{predecessor_name}" unless predecessor_node
|
284
|
+
successor_node = @nodes[successor_name]
|
285
|
+
raise "Unknown precessor node #{successor_name}" unless successor_node
|
286
|
+
@nodes[predecessor_name].add_edge(@nodes[successor_name])
|
287
|
+
end
|
288
|
+
|
289
|
+
def [](name)
|
290
|
+
@nodes[name]
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# ------------------------------------------------------------------
|
295
|
+
|
296
|
+
include TLA2DOT::Utils::MyLogger # mix logger
|
297
|
+
PROGNAME = "parser" # progname for logger
|
298
|
+
|
299
|
+
def initialize( options = {} )
|
300
|
+
@logger = getLogger( PROGNAME, options )
|
301
|
+
@logger.debug( "#{__method__} initialized" )
|
302
|
+
setOptions( options )
|
303
|
+
end
|
304
|
+
|
305
|
+
def setOptions( options )
|
306
|
+
@options = options
|
307
|
+
end
|
308
|
+
|
309
|
+
def getOptions
|
310
|
+
@options
|
311
|
+
end
|
312
|
+
|
313
|
+
|
314
|
+
|
315
|
+
# entry point
|
316
|
+
def parse(str, filter_render_variables = [] )
|
317
|
+
@logger.info( "#{__method__} parsing started" )
|
318
|
+
@graph = Graph.new( filter_render_variables, getOptions )
|
319
|
+
@line = 0
|
320
|
+
return @graph if str.nil? || str.empty?
|
321
|
+
@str = str.kind_of?( Array ) ? str : [ str ]
|
322
|
+
begin
|
323
|
+
ret = yyparse self, :scan
|
324
|
+
@logger.info( "#{__method__} parsing done" )
|
325
|
+
return ret
|
326
|
+
rescue Exception => e
|
327
|
+
puts "Error on line #{@line} near #{@current}"
|
328
|
+
@logger.error( "#{__method__} error #{e}" )
|
329
|
+
# puts e
|
330
|
+
return nil
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
private
|
335
|
+
|
336
|
+
# return next line for scanner
|
337
|
+
def next_str
|
338
|
+
@current = @str.any? ? @str.shift : nil
|
339
|
+
@line += 1
|
340
|
+
@current
|
341
|
+
end
|
342
|
+
|
343
|
+
|
344
|
+
# racc token scanner
|
345
|
+
def scan
|
346
|
+
while true
|
347
|
+
str = next_str
|
348
|
+
break if str.nil?
|
349
|
+
until str.empty?
|
350
|
+
case str
|
351
|
+
when /\A\s+/
|
352
|
+
str = $'
|
353
|
+
when /\AState (\d+):/
|
354
|
+
yield [ :STATE, $1 ]
|
355
|
+
str = $'
|
356
|
+
when /\AState (\d+)\/(-?\d+):/
|
357
|
+
# using fingerprint name
|
358
|
+
yield [ :STATE, $2 ]
|
359
|
+
str = $'
|
360
|
+
when /\AState (\d+):/
|
361
|
+
yield [ :STATE, $1 ]
|
362
|
+
str = $'
|
363
|
+
when /\ATransition ([0-9\-\+]+)/
|
364
|
+
yield [ :TRANS, $1 ]
|
365
|
+
str = $'
|
366
|
+
when /\A"([^"]*)"*/
|
367
|
+
yield [ :STRING, $1 ]
|
368
|
+
str = $'
|
369
|
+
when /\A[a-zA-Z]\w*/
|
370
|
+
yield [ :IDENT, $& ]
|
371
|
+
str = $'
|
372
|
+
when /\A-?\d+/
|
373
|
+
yield [ :INTEGER, $& ]
|
374
|
+
str = $'
|
375
|
+
when /\A\/\\/
|
376
|
+
yield [ :AND, $& ]
|
377
|
+
str = $'
|
378
|
+
when /\A\|->/
|
379
|
+
yield [ $&, $& ]
|
380
|
+
str = $'
|
381
|
+
# when /\A\n/
|
382
|
+
# puts "Nl"
|
383
|
+
# yield [ :NL, $& ]
|
384
|
+
# str = $'
|
385
|
+
when /\A\-->/
|
386
|
+
yield [ $&, $& ]
|
387
|
+
str = $'
|
388
|
+
when /\A=/
|
389
|
+
yield [ $&, $& ]
|
390
|
+
str = $'
|
391
|
+
else
|
392
|
+
c = str[0,1]
|
393
|
+
yield [ c, c ]
|
394
|
+
str = str[1..-1]
|
395
|
+
end
|
396
|
+
end # until
|
397
|
+
end # while
|
398
|
+
yield [ false, '$'] # is optional from Racc 1.3.7
|
399
|
+
end
|
400
|
+
|
401
|
+
---- footer
|
402
|
+
|
403
|
+
if $0 == __FILE__
|
404
|
+
src = <<EOS
|
405
|
+
{
|
406
|
+
name => MyName,
|
407
|
+
id => MyIdent
|
408
|
+
}
|
409
|
+
EOS
|
410
|
+
puts 'Parsing (String):'
|
411
|
+
print src
|
412
|
+
puts
|
413
|
+
puts 'Result (Ruby Object):'
|
414
|
+
p HashParser.new.parse(src)
|
415
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'mustache' # extendending implementation of
|
2
|
+
|
3
|
+
module TLA2DOT
|
4
|
+
|
5
|
+
class Template < Mustache
|
6
|
+
|
7
|
+
include TLA2DOT::Utils::MyLogger # mix logger
|
8
|
+
PROGNAME = "template" # progname for logger
|
9
|
+
|
10
|
+
# ------------------------------------------------------------------
|
11
|
+
# Attributes
|
12
|
+
|
13
|
+
# instance
|
14
|
+
attr_writer :partials # f: partial-name --> template string
|
15
|
+
attr_writer :templates # f: template-name --> template string
|
16
|
+
|
17
|
+
# ------------------------------------------------------------------
|
18
|
+
# Constructor
|
19
|
+
|
20
|
+
def initialize( options={} )
|
21
|
+
@logger = getLogger( PROGNAME, options )
|
22
|
+
@logger.info( "#{__method__} created" )
|
23
|
+
@logger.debug( "#{__method__}, options='#{options}" )
|
24
|
+
|
25
|
+
@template_extension = "mustache"# type part in filename
|
26
|
+
|
27
|
+
# for mustache templates
|
28
|
+
if options[:templates] then
|
29
|
+
@template_paths = options[:templates]
|
30
|
+
else
|
31
|
+
@template_paths = TLA2DOT::Cli::TEMPLATES
|
32
|
+
end
|
33
|
+
# init partial cache
|
34
|
+
@partials = {}
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
# ------------------------------------------------------------------
|
39
|
+
# Services
|
40
|
+
|
41
|
+
def to_str( template_name, data )
|
42
|
+
@logger.info( "#{__method__}: template_name=#{template_name}, data=#{data}, nodes.size=#{data.nodes.size}" )
|
43
|
+
# @logger.debug( "#{__method__}: nodes=#{data.nodes}" )
|
44
|
+
@data = data
|
45
|
+
template = get_template( template_name )
|
46
|
+
render( template, { :tsti=>"moikka", :data=>data, "tst" => "hello" } )
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# def data
|
52
|
+
# @data
|
53
|
+
# end
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
# ------------------------------------------------------------------
|
58
|
+
# Integrate with mustache
|
59
|
+
|
60
|
+
# method used by mustache framework - delegate to 'get_partial'
|
61
|
+
def partial(name)
|
62
|
+
@logger.debug( "#{__method__} name=#{name}" )
|
63
|
+
get_partial( name )
|
64
|
+
end
|
65
|
+
|
66
|
+
# cache @partials - for easier extension
|
67
|
+
def get_partial( name )
|
68
|
+
@logger.debug( "#{__method__} name=#{name}" )
|
69
|
+
return @partials[name] if @partials[name]
|
70
|
+
|
71
|
+
partial_file = get_template_filepath( name )
|
72
|
+
@logger.info( "#{__method__} read partial_file=#{partial_file}" )
|
73
|
+
@partials[name] = File.read( partial_file )
|
74
|
+
@partials[name]
|
75
|
+
end
|
76
|
+
|
77
|
+
# hide @templates - for easier extension
|
78
|
+
def get_template( name )
|
79
|
+
|
80
|
+
template_file = get_template_filepath( name )
|
81
|
+
@logger.info( "#{__method__} read template_file=#{template_file}" )
|
82
|
+
File.read( template_file )
|
83
|
+
|
84
|
+
end # def get_template( name )
|
85
|
+
|
86
|
+
|
87
|
+
# return path to an existing template file name
|
88
|
+
private
|
89
|
+
|
90
|
+
def get_template_filepath( name )
|
91
|
+
|
92
|
+
@template_paths.each do |directory_or_gemname|
|
93
|
+
|
94
|
+
template_path = get_template_filepath_resolve( directory_or_gemname, name )
|
95
|
+
|
96
|
+
return template_path if File.exists?( template_path )
|
97
|
+
|
98
|
+
end # each
|
99
|
+
|
100
|
+
|
101
|
+
# could not find
|
102
|
+
|
103
|
+
raise <<-eos
|
104
|
+
|
105
|
+
No such template '#{name}' found in directories: #{@template_paths.join(", ")}
|
106
|
+
|
107
|
+
Use opition -t list directories or Gems, where file '#{name}.#{@template_extension}' can be located.
|
108
|
+
|
109
|
+
eos
|
110
|
+
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
# return path to plain 'directory' or to gem directory
|
115
|
+
def get_template_filepath_resolve( directory_or_gemname, template_file )
|
116
|
+
@logger.info( "#{__method__} directory_or_gemname=#{directory_or_gemname}" )
|
117
|
+
if directory_or_gemname[-1] == '/' then
|
118
|
+
return get_template_filepath_in_directory( directory_or_gemname[0..-2], template_file )
|
119
|
+
else
|
120
|
+
directory = gemname_to_directory( directory_or_gemname )
|
121
|
+
return get_template_filepath_in_directory( directory, template_file )
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# return directory to 'gemspec'
|
126
|
+
def gemname_to_directory( gemname_and_spec )
|
127
|
+
|
128
|
+
# The version requirements are optional.
|
129
|
+
# You can also specify multiple version requirements, just append more at the end
|
130
|
+
gem_spec = gemname_and_spec.split(',')
|
131
|
+
gem_name, *gem_ver_reqs = gem_spec[0], gem_spec[1]
|
132
|
+
@logger.debug( "#{__method__}, gem_name=#{gem_name}, *gem_ver_reqs=#{gem_ver_reqs}" )
|
133
|
+
gdep = Gem::Dependency.new(gem_name, *gem_ver_reqs)
|
134
|
+
# find latest that satisifies
|
135
|
+
found_gspec = gdep.matching_specs.sort_by(&:version).last
|
136
|
+
@logger.debug( "#{__method__}, found_gspec=#{found_gspec}" )
|
137
|
+
# instead of using Gem::Dependency, you can also do:
|
138
|
+
# Gem::Specification.find_all_by_name(gem_name, *gem_ver_reqs)
|
139
|
+
|
140
|
+
if found_gspec
|
141
|
+
@logger.debug( "#{__method__}, Requirement '#{gdep}' already satisfied by #{found_gspec.name}-#{found_gspec.version}" )
|
142
|
+
template_path = "#{found_gspec.gem_dir}/mustache"
|
143
|
+
@logger.debug( "#{__method__}, template_path=#{template_path}" )
|
144
|
+
else
|
145
|
+
#puts "Requirement '#{gdep}' not satisfied; installing..."
|
146
|
+
# ver_args = gdep.requirements_list.map{|s| ['-v', s] }.flatten
|
147
|
+
# # multi-arg is safer, to avoid injection attacks
|
148
|
+
# system('gem', 'install', gem_name, *ver_args)
|
149
|
+
raise "Could not find gem '#{gdep}' - try 'gem install #{gdep}'"
|
150
|
+
end
|
151
|
+
|
152
|
+
return template_path
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
# return path to 'template_file' in an existing 'directory'
|
158
|
+
def get_template_filepath_in_directory( directory, template_file )
|
159
|
+
@logger.debug( "#{__method__} directory=#{directory}, template_file=#{template_file}" )
|
160
|
+
|
161
|
+
if ! File.exists?( directory ) then
|
162
|
+
raise <<-eos
|
163
|
+
|
164
|
+
No such directory '#{directory}'.
|
165
|
+
|
166
|
+
Option -t should list
|
167
|
+
- existing paths OR
|
168
|
+
- Gems which includes 'mustache' directory
|
169
|
+
|
170
|
+
eos
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
template_path = "#{directory}/#{template_file}.#{@template_extension}"
|
175
|
+
@logger.info( "#{__method__} read template_path=#{template_path}" )
|
176
|
+
|
177
|
+
return template_path
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
end # class
|
183
|
+
|
184
|
+
end # module
|
data/lib/tla2dot.rb
ADDED
data/lib/utils/logger.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
# see http://hawkins.io/2013/08/using-the-ruby-logger/
|
4
|
+
|
5
|
+
module TLA2DOT
|
6
|
+
|
7
|
+
module Utils
|
8
|
+
|
9
|
+
module MyLogger
|
10
|
+
|
11
|
+
# no logging done
|
12
|
+
|
13
|
+
class NullLoger < Logger
|
14
|
+
def initialize(*args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(*args, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
LOGFILE="tla2dot.log"
|
22
|
+
|
23
|
+
def getLogger( progname, options={} )
|
24
|
+
|
25
|
+
level = get_level( options )
|
26
|
+
|
27
|
+
if level.nil?
|
28
|
+
|
29
|
+
return NullLoger.new
|
30
|
+
|
31
|
+
else
|
32
|
+
|
33
|
+
logger = Logger.new( LOGFILE )
|
34
|
+
logger.level=level
|
35
|
+
logger.progname = progname
|
36
|
+
return logger
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end # getLogger
|
41
|
+
|
42
|
+
# ------------------------------------------------------------------
|
43
|
+
private
|
44
|
+
|
45
|
+
def get_level( options )
|
46
|
+
|
47
|
+
# puts "#{__method__}: options=#{options}"
|
48
|
+
|
49
|
+
level_name = options && options[:log] ? options[:log] : ENV['LOG_LEVEL']
|
50
|
+
|
51
|
+
level = case level_name
|
52
|
+
when 'warn', 'WARN'
|
53
|
+
Logger::WARN
|
54
|
+
when 'info', 'INFO'
|
55
|
+
Logger::INFO
|
56
|
+
when 'debug', 'DEBUG'
|
57
|
+
Logger::DEBUG
|
58
|
+
when 'error', 'ERROR'
|
59
|
+
Logger::ERROR
|
60
|
+
else
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
|
64
|
+
return level
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
fontname = "Bitstream Vera Sans";
|
2
|
+
fontsize = 8;
|
3
|
+
shape = "record";
|
4
|
+
labeljust="l";
|
5
|
+
rankdir=LR;
|
6
|
+
|
7
|
+
|
8
|
+
node [ fontname = "Courier"
|
9
|
+
fontsize = 8
|
10
|
+
shape = "record"
|
11
|
+
|
12
|
+
];
|
13
|
+
|
14
|
+
edge [
|
15
|
+
fontname = "Bitstream Vera Sans"
|
16
|
+
fontsize = 8
|
17
|
+
arrowhead = "none"
|
18
|
+
];
|
19
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
digraph G {
|
2
|
+
|
3
|
+
/* font size layout etc. */
|
4
|
+
{{> defaults }}
|
5
|
+
|
6
|
+
/* nodes */
|
7
|
+
{{# data }}
|
8
|
+
{{# states}}
|
9
|
+
{{name}} [{{> stateOutput }}];
|
10
|
+
{{/ states}}
|
11
|
+
{{/ data }}
|
12
|
+
|
13
|
+
/* arcs */
|
14
|
+
{{# data }}
|
15
|
+
{{# states}}{{# transitions }} {{ from_state }} -> {{to_state}};
|
16
|
+
{{/ transitions }}{{/ states}}
|
17
|
+
{{/ data }}
|
18
|
+
|
19
|
+
|
20
|
+
}
|