pg-verify 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 (114) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/Gemfile +10 -0
  4. data/Gemfile.lock +98 -0
  5. data/README.md +29 -0
  6. data/Rakefile +62 -0
  7. data/bin/console +15 -0
  8. data/bin/pg-verify.rb +18 -0
  9. data/bin/setup +8 -0
  10. data/calc.ebnf +21 -0
  11. data/data/config/pg-verify.yml +66 -0
  12. data/data/nusmv.sample.smv +179 -0
  13. data/data/project-template/.gitignore.resource +4 -0
  14. data/data/project-template/.pg-verify.yml +0 -0
  15. data/data/project-template/README.md +18 -0
  16. data/data/project-template/addon/.keep +0 -0
  17. data/data/project-template/program-graph.rb.resource +103 -0
  18. data/devpg +5 -0
  19. data/doc/examples/railroad_crossing.rb +61 -0
  20. data/doc/examples/train-tree.rb +43 -0
  21. data/doc/examples/weidezaun.rb +99 -0
  22. data/doc/examples/weidezaun.txt +29 -0
  23. data/doc/expose/definition.png +0 -0
  24. data/doc/expose/diagram.png +0 -0
  25. data/doc/expose/expose.md +359 -0
  26. data/doc/expose/validity.png +0 -0
  27. data/exe/pg-verify +4 -0
  28. data/integration_tests/ruby_dsl/001_states.rb +10 -0
  29. data/integration_tests/ruby_dsl/002_transitions.rb +10 -0
  30. data/integration_tests/ruby_dsl/003_actions.rb +14 -0
  31. data/integration_tests/ruby_dsl/004_guards.rb +18 -0
  32. data/integration_tests/ruby_dsl/005_variables.rb +16 -0
  33. data/integration_tests/ruby_dsl/006_state_variables.rb +26 -0
  34. data/integration_tests/ruby_dsl/007_variable_initialization.rb +28 -0
  35. data/integration_tests/ruby_dsl/008_state_initialization.rb +19 -0
  36. data/integration_tests/ruby_dsl/009_shared_variables.rb +26 -0
  37. data/integration_tests/ruby_dsl/010_complex_guards.rb +18 -0
  38. data/integration_tests/ruby_dsl/011_complex_actions.rb +16 -0
  39. data/integration_tests/ruby_dsl/012_error_components.rb +9 -0
  40. data/integration_tests/ruby_dsl/013_hazards.rb +25 -0
  41. data/integration_tests/ruby_dsl/014_tau_transitions.rb +26 -0
  42. data/integration_tests/ruby_dsl/015_basic_dcca.rb +19 -0
  43. data/integration_tests/ruby_dsl/016_pressure_tank.rb +146 -0
  44. data/lib/pg-verify/cli/cli.rb +235 -0
  45. data/lib/pg-verify/core/cmd_runner.rb +151 -0
  46. data/lib/pg-verify/core/core.rb +38 -0
  47. data/lib/pg-verify/core/extensions/array_extensions.rb +11 -0
  48. data/lib/pg-verify/core/extensions/enumerable_extensions.rb +19 -0
  49. data/lib/pg-verify/core/extensions/nil_extensions.rb +7 -0
  50. data/lib/pg-verify/core/extensions/string_extensions.rb +84 -0
  51. data/lib/pg-verify/core/shell/colorizer.rb +136 -0
  52. data/lib/pg-verify/core/shell/shell.rb +0 -0
  53. data/lib/pg-verify/core/util.rb +146 -0
  54. data/lib/pg-verify/doctor/doctor.rb +180 -0
  55. data/lib/pg-verify/ebnf_parser/ast.rb +31 -0
  56. data/lib/pg-verify/ebnf_parser/ebnf_parser.rb +26 -0
  57. data/lib/pg-verify/ebnf_parser/expression_parser.rb +177 -0
  58. data/lib/pg-verify/ebnf_parser/expression_parser2.rb +422 -0
  59. data/lib/pg-verify/ebnf_parser/expressions.ebnf +33 -0
  60. data/lib/pg-verify/ebnf_parser/expressions.peg +52 -0
  61. data/lib/pg-verify/ebnf_parser/parser_result.rb +26 -0
  62. data/lib/pg-verify/interpret/component_context.rb +125 -0
  63. data/lib/pg-verify/interpret/graph_context.rb +85 -0
  64. data/lib/pg-verify/interpret/interpret.rb +142 -0
  65. data/lib/pg-verify/interpret/pg_script.rb +72 -0
  66. data/lib/pg-verify/interpret/spec/ltl_builder.rb +90 -0
  67. data/lib/pg-verify/interpret/spec/spec_context.rb +32 -0
  68. data/lib/pg-verify/interpret/spec/spec_set_context.rb +67 -0
  69. data/lib/pg-verify/interpret/transition_context.rb +55 -0
  70. data/lib/pg-verify/model/allocation_set.rb +28 -0
  71. data/lib/pg-verify/model/assignment.rb +34 -0
  72. data/lib/pg-verify/model/component.rb +40 -0
  73. data/lib/pg-verify/model/dcca/hazard.rb +16 -0
  74. data/lib/pg-verify/model/dcca.rb +67 -0
  75. data/lib/pg-verify/model/expression.rb +106 -0
  76. data/lib/pg-verify/model/graph.rb +58 -0
  77. data/lib/pg-verify/model/model.rb +10 -0
  78. data/lib/pg-verify/model/parsed_expression.rb +77 -0
  79. data/lib/pg-verify/model/simulation/trace.rb +43 -0
  80. data/lib/pg-verify/model/simulation/variable_state.rb +23 -0
  81. data/lib/pg-verify/model/source_location.rb +45 -0
  82. data/lib/pg-verify/model/specs/spec.rb +44 -0
  83. data/lib/pg-verify/model/specs/spec_result.rb +25 -0
  84. data/lib/pg-verify/model/specs/spec_set.rb +43 -0
  85. data/lib/pg-verify/model/specs/specification.rb +50 -0
  86. data/lib/pg-verify/model/transition.rb +41 -0
  87. data/lib/pg-verify/model/validation/assignment_to_state_variable_validation.rb +26 -0
  88. data/lib/pg-verify/model/validation/empty_state_set_validation.rb +18 -0
  89. data/lib/pg-verify/model/validation/errors.rb +119 -0
  90. data/lib/pg-verify/model/validation/foreign_assignment_validation.rb +30 -0
  91. data/lib/pg-verify/model/validation/unknown_token_validation.rb +35 -0
  92. data/lib/pg-verify/model/validation/validation.rb +23 -0
  93. data/lib/pg-verify/model/variable.rb +47 -0
  94. data/lib/pg-verify/model/variable_set.rb +84 -0
  95. data/lib/pg-verify/nusmv/nusmv.rb +23 -0
  96. data/lib/pg-verify/nusmv/runner.rb +124 -0
  97. data/lib/pg-verify/puml/puml.rb +23 -0
  98. data/lib/pg-verify/shell/loading/line_animation.rb +36 -0
  99. data/lib/pg-verify/shell/loading/loading_animation.rb +80 -0
  100. data/lib/pg-verify/shell/loading/loading_prompt.rb +43 -0
  101. data/lib/pg-verify/shell/loading/no_animation.rb +20 -0
  102. data/lib/pg-verify/shell/shell.rb +30 -0
  103. data/lib/pg-verify/simulation/simulation.rb +7 -0
  104. data/lib/pg-verify/simulation/simulator.rb +90 -0
  105. data/lib/pg-verify/simulation/state.rb +53 -0
  106. data/lib/pg-verify/transform/hash_transformation.rb +104 -0
  107. data/lib/pg-verify/transform/nusmv_transformation.rb +261 -0
  108. data/lib/pg-verify/transform/puml_transformation.rb +89 -0
  109. data/lib/pg-verify/transform/transform.rb +8 -0
  110. data/lib/pg-verify/version.rb +5 -0
  111. data/lib/pg-verify.rb +47 -0
  112. data/pg-verify.gemspec +38 -0
  113. data/sig/pg-verify.rbs +4 -0
  114. metadata +226 -0
@@ -0,0 +1,18 @@
1
+ module PgVerify
2
+ module Model
3
+ module Validation
4
+
5
+ module EmptyStateSetValidation
6
+
7
+ def self.validate(model)
8
+ errors = model.components.map { |cmp|
9
+ next if cmp.states.length > 0
10
+ EmptyStateSetError.new(cmp)
11
+ }.compact
12
+ return errors
13
+ end
14
+
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,119 @@
1
+ module PgVerify
2
+ module Model
3
+ module Validation
4
+
5
+ class ValidationError < PgVerify::Core::Error
6
+ def initialize(model, errors)
7
+ @model, @errors = model, errors
8
+ end
9
+
10
+ def formatted()
11
+ title = "There are #{@errors.length} errors for model #{@model.name}"
12
+
13
+ summary = @errors.map{ |e| e.formatted.first }
14
+ summary = summary.each_with_index.map { |e, i| "#{i + 1}: #{e}" }.join("\n")
15
+
16
+ body = @errors.map(&:to_formatted).join("\n")
17
+ return title, "#{summary}\n\n#{body}"
18
+ end
19
+
20
+ end
21
+
22
+ class UnknownTokenError < PgVerify::Core::Error
23
+ def initialize(token, expression, varset)
24
+ @token, @expression, @varset = token, expression, varset
25
+ end
26
+ def formatted()
27
+ title = "Unknown token"
28
+
29
+ body = []
30
+ body << @expression.source_location.to_s.c_sidenote unless @expression.source_location.blank?
31
+ body << @expression.source_location.render_code_block() unless @expression.source_location.blank?
32
+ body << ""
33
+ body << "The expression '#{@expression.to_s.c_expression}' uses token #{@token.to_s.c_string}."
34
+ body << "This is neither a known variable, nor literal."
35
+ body << "Known variables: #{@varset.names.map(&:to_s).map(&:c_var).join(", ")}"
36
+ body << "Known literals: #{@varset.values.reject{ |v| v.is_a?(Numeric) }.map(&:to_s).map(&:c_literal).join(", ")}"
37
+
38
+ return title, body.join("\n")
39
+ end
40
+ end
41
+
42
+ class UnknownVariableError < PgVerify::Core::Error
43
+ def initialize(assigned_variable, assignment_expression, var_set)
44
+ @assigned_variable, @assignment_expression, @var_set = assigned_variable, assignment_expression, var_set
45
+ end
46
+ def formatted()
47
+ title = "Unknown variable '#{@assigned_variable}'"
48
+
49
+ body = []
50
+ body << @assignment_expression.source_location.to_s.c_sidenote
51
+ body << @assignment_expression.source_location.render_code_block()
52
+ body << ""
53
+ body << "The expression '#{@assignment_expression.to_s.c_expression}' uses variable #{@assigned_variable.to_s.c_var}."
54
+ body << "However there is no such variable in the context of this model."
55
+ body << "Known variables: #{@var_set.names.map(&:to_s).map(&:c_var).join(', ')}."
56
+
57
+ hint = "Declare variables before using them in expressions"
58
+
59
+ return title, body.join("\n"), hint
60
+ end
61
+ end
62
+
63
+ class ForeignVariableAssignmentError < PgVerify::Core::Error
64
+ def initialize(variable, expression, varset, component)
65
+ @variable, @expression, @varset = variable, expression, varset
66
+ @component = component
67
+ end
68
+ def formatted()
69
+ title = "Foreign variable assignment"
70
+
71
+ body = []
72
+ body << @expression.source_location.to_s.c_sidenote
73
+ body << @expression.source_location.render_code_block()
74
+ body << ""
75
+ body << "Component #{@component.name.to_s.c_cmp} assigns a value to variable #{@variable.to_s.c_var},"
76
+ body << "using this expression '#{@expression.to_s.c_expression}'."
77
+ body << "However #{@variable.to_s.c_var} is owned by component #{@varset[@variable].owner_name.to_s.c_cmp}"
78
+
79
+ hint = "A variable can only be written to by the component which declared it"
80
+
81
+ return title, body.join("\n"), hint
82
+ end
83
+ end
84
+
85
+ class AssignmentToStateVariableError < PgVerify::Core::Error
86
+ def initialize(variable, expression, varset)
87
+ @variable, @expression, @varset = variable, expression, varset
88
+ end
89
+ def formatted()
90
+ title = "Assignment to state variable"
91
+
92
+ body = []
93
+ body << @expression.source_location.to_s.c_sidenote
94
+ body << @expression.source_location.render_code_block()
95
+ body << ""
96
+ body << "The expression '#{@expression.to_s.c_expression}' assigns a value to variable #{@variable.to_s.c_var}."
97
+ body << "However this variable is a state variable and can not be written to."
98
+
99
+ hint = "State variables are read only"
100
+
101
+ return title, body.join("\n"), hint
102
+ end
103
+ end
104
+
105
+ class EmptyStateSetError < PgVerify::Core::Error
106
+ def initialize(component)
107
+ @component = component
108
+ end
109
+ def formatted()
110
+ title = "Empty state set"
111
+ body = "The graph component '#{@component.name.to_s.c_cmp}' does not define any states."
112
+ hint = "Each graph component needs at least one state"
113
+ return title, body, hint
114
+ end
115
+ end
116
+
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,30 @@
1
+ module PgVerify
2
+ module Model
3
+ module Validation
4
+
5
+ module ForeignAssignmentValidation
6
+
7
+ def self.validate(model)
8
+ errors = []
9
+ varset = model.all_variables()
10
+
11
+ model.components.each { |component|
12
+ actions = component.transitions.map(&:action).compact
13
+ actions.each { |a|
14
+ var_strings = a.assigned_variables()
15
+ # Null values (variable not known is not an error for this validation)
16
+ vars = var_strings.map { |vs| varset[vs] }.compact
17
+ vars.map { |var|
18
+ next if var.owner_name == component.name
19
+ errors << ForeignVariableAssignmentError.new(var.name, a, varset, component)
20
+ }
21
+ }
22
+ }
23
+ return errors
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ module PgVerify
2
+ module Model
3
+ module Validation
4
+
5
+ module UnknownTokenValidation
6
+
7
+ def self.validate(model)
8
+ errors = []
9
+ varset = model.all_variables()
10
+ transitions = model.components.map(&:transitions).flatten
11
+ transitions.each { |tx|
12
+ errors += validate_expression(varset, tx.precon)
13
+ errors += validate_expression(varset, tx.guard)
14
+ errors += validate_expression(varset, tx.action)
15
+
16
+ }
17
+ return errors
18
+ end
19
+
20
+ def self.validate_expression(varset, expression)
21
+ return [] if expression.nil?
22
+ tokens = expression.word_tokens()
23
+ tokens = tokens.reject { |token|
24
+ varset.varname?(token) || varset.constname?(token)
25
+ }
26
+ return tokens.map { |token|
27
+ UnknownTokenError.new(token, expression, varset)
28
+ }
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,23 @@
1
+ module PgVerify
2
+ module Model
3
+ module Validation
4
+
5
+ def self.validate(model)
6
+ errors = []
7
+ errors += EmptyStateSetValidation.validate(model)
8
+ errors += UnknownTokenValidation.validate(model)
9
+ errors += ForeignAssignmentValidation.validate(model)
10
+ errors += AssignmentToStateVariableValidation.validate(model)
11
+ return errors
12
+ end
13
+
14
+ def self.validate!(model)
15
+ errors = validate(model)
16
+ return if errors.empty?
17
+ raise ValidationError.new(model, errors)
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,47 @@
1
+ module PgVerify
2
+ module Model
3
+
4
+ class Variable
5
+
6
+ # Name of this variable as a symbol
7
+ attr_accessor :name
8
+
9
+ # The name of the component which owns this variable as a symbol
10
+ attr_accessor :owner_name
11
+
12
+ # Range of this variable. This can either be a range like (0..2)
13
+ # an array of numbers like [0, 1, 2] or an array of symbols representing
14
+ # states of components like [:Idle, :Breaking]
15
+ attr_accessor :range
16
+
17
+ # An expression to be used to initialize this variable
18
+ # While this can be something simple like "my_var = 10", it can
19
+ # also be something more involved like "my_var > 10 && my_var < 15".
20
+ attr_accessor :init_expression
21
+
22
+ # The location in source, where this variable was defined
23
+ attr_accessor :source_location
24
+
25
+ def initialize(name, range, owner_name, source_location, init: nil)
26
+ init = Model::ParsedExpression.new(init, Model::ParsedExpression::TYPE_PL) if init.is_a?(String)
27
+ @name, @range, @owner_name, @init_expression = name, range, owner_name, init
28
+ @source_location = source_location
29
+ end
30
+
31
+ # Returns all possible values of this variable as an array
32
+ def values()
33
+ return @range if @range.is_a?(Array)
34
+ return @range.to_a
35
+ end
36
+
37
+ # A state variable represents the states of a component.
38
+ # e.g: Switch with range [:on, :off] for component switch.
39
+ def state_variable?()
40
+ return @name == @owner_name
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,84 @@
1
+
2
+ module PgVerify
3
+ module Model
4
+
5
+ class VariableSet
6
+
7
+ attr_accessor :map
8
+
9
+ def initialize(*variables)
10
+ @map = {}
11
+ self << variables
12
+ end
13
+
14
+ def <<(var)
15
+ return @map[var.name] = var if var.is_a?(Variable)
16
+ return var.map { |v| self << v } if var.is_a?(Array)
17
+ raise "Not a variable '#{var}'"
18
+ end
19
+
20
+ def get(*names)
21
+ return names.map { |n| self[n] }
22
+ end
23
+
24
+ def +(varset)
25
+ varset = VariableSet.new(*varset) if varset.is_a?(Array)
26
+ raise "Not a variable set '#{varset}'" unless varset.is_a?(VariableSet)
27
+ return VariableSet.new(*(self.to_a() + varset.to_a()))
28
+ end
29
+
30
+ def to_a()
31
+ return @map.values()
32
+ end
33
+
34
+ def names()
35
+ return @map.keys()
36
+ end
37
+
38
+ def [](name)
39
+ name = name.to_sym
40
+ raise "No such variable #{name}!" unless @map.key?(name)
41
+ return @map[name]
42
+ end
43
+
44
+ def include?(name_or_var)
45
+ return varname?(name_or_var) || constname?(name_or_var)
46
+ end
47
+
48
+ def empty?()
49
+ return @map.empty?
50
+ end
51
+
52
+ def map(&blk)
53
+ return @map.values.map(&blk)
54
+ end
55
+
56
+ def varname?(name)
57
+ return @map.key?(name.to_sym)
58
+ end
59
+
60
+ def constname?(name)
61
+ values().map(&:to_s).include?(name.to_s)
62
+ end
63
+
64
+ def to_s()
65
+ return "{#{@map.map { |name, var| "#{name}:#{var.range}(#{var.owner_name})" }.join(", ")}}"
66
+ end
67
+
68
+ def select_by_owner(owner)
69
+ owner = owner.name if owner.is_a?(Model::Component)
70
+ owner = owner.to_sym if owner.is_a?(String)
71
+ found = self.to_a().select { |var| var.owner_name == owner }
72
+ return VariableSet.new(*found)
73
+ end
74
+
75
+ # Returns all possible values of variables in this set as an array
76
+ def values()
77
+ return self.to_a().map(&:values).flatten
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+ end
84
+
@@ -0,0 +1,23 @@
1
+ # Require all module files
2
+ Dir[File.join(__dir__, "**", '*.rb')].sort.each { |file| require file }
3
+
4
+ module PgVerify
5
+ module NuSMV
6
+
7
+ class RawNuSMVError < PgVerify::Core::Error
8
+ def initialize(cmd, out, err, status, file)
9
+ @cmd, @out, @err, @status, @file = cmd, out, err, status, file
10
+ end
11
+
12
+ def formatted()
13
+ title = "Execution of NuSMV exited with #{@status}"
14
+
15
+ body = "The exact command was\n#{@cmd.c_string}\n\n"
16
+ body += "#{@out}\n#{@err}"
17
+
18
+ return title, body
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,124 @@
1
+
2
+ module PgVerify
3
+ module NuSMV
4
+
5
+ class Runner
6
+
7
+ def run_specs(program_graph)
8
+ nusmv_s = Transform::NuSmvTransformation.new.transform_graph(program_graph)
9
+ output = eval_nusmv(nusmv_s)
10
+ specs = program_graph.specification.flatten()
11
+ return parse_spec_results(program_graph, specs, output)
12
+ end
13
+
14
+ def parse_spec_results(program_graph, specs, nusmv_output)
15
+ block = Struct.new(:success, :lines)
16
+
17
+ # Split the output into blocks which describe the
18
+ # specifications
19
+ blocks, current_block = [], nil
20
+ nusmv_output.split("\n").each { |line|
21
+ result = line[/-- specification .* is (true|false)/, 1]
22
+ if !result.nil?
23
+ blocks << current_block unless current_block.nil?
24
+ current_block = block.new(result == "true", [])
25
+ next
26
+ end
27
+ current_block.lines << line unless current_block.nil?
28
+ }
29
+ blocks << current_block unless current_block.nil?
30
+
31
+ return blocks.each_with_index.map { |block, index|
32
+ trace = block.success ? nil : block.lines.select { |l| l.start_with?(/\s+/) }
33
+ trace = parse_trace(program_graph, trace.join("\n")) unless trace.nil?
34
+
35
+ Model::SpecResult.new(specs[index], block.success, trace)
36
+ }
37
+ end
38
+
39
+ # Calls NuSMV to calculate a possible state trace.
40
+ # Returns an array of states, where each state is a hash from
41
+ # variable to the value of that variable in that state
42
+ def run_simulation(program_graph, steps, random: false)
43
+ commands = []
44
+ commands << "read_model"
45
+ commands << "flatten_hierarchy"
46
+ commands << "encode_variables"
47
+ commands << "build_model"
48
+ commands << "pick_state #{random ? '-r' : ''}"
49
+ commands << "simulate -k #{steps.to_s.to_i} -v #{random ? '-r' : ''}"
50
+ commands << "quit"
51
+ nusmv_s = Transform::NuSmvTransformation.new.transform_graph(program_graph)
52
+ output = eval_nusmv(nusmv_s, commands: commands)
53
+ return parse_trace(program_graph, output)
54
+ end
55
+
56
+ def parse_trace(program_graph, nusmv_output)
57
+ var_states, current_var_state = [], nil
58
+
59
+ loop_index = -1
60
+
61
+ nusmv_output.split("\n").each { |line|
62
+ # Wait for heading of new state
63
+ if line.match(/\s*-> State: .+ <-/)
64
+ # Complete and store the old state if any
65
+ unless current_var_state.nil?
66
+ missing_keys = var_states.empty? ? [] : var_states[-1].keys - current_var_state.keys
67
+ missing_keys.each { |key|
68
+ current_var_state[key] = var_states[-1][key]
69
+ }
70
+ var_states << current_var_state
71
+ end
72
+ # Create a new state
73
+ current_var_state = {}
74
+ next
75
+ end
76
+ # Skip lines before the first state
77
+ next if current_var_state.nil?
78
+
79
+ if line.include?("Loop starts here")
80
+ loop_index == var_states.length - 1
81
+ next
82
+ end
83
+
84
+ key_val = line.split("=").map(&:strip)
85
+ key = key_val[0].gsub("v.V_", "").to_sym
86
+ val = key_val[1].gsub("L_", "")
87
+ current_var_state[key] = val
88
+ }
89
+ return Model::Trace.new(program_graph, var_states)
90
+ end
91
+
92
+ def eval_nusmv(nusmv_string, commands: [])
93
+ tmp_file = PgVerify.tmp_file("pg.smv")
94
+ File.write(tmp_file, nusmv_string)
95
+ return eval_file(tmp_file, commands: commands)
96
+ end
97
+
98
+ def eval_file(file, commands: [])
99
+ nusmv_path = find_nusmv_path()
100
+ raise "NuSMV not integrated! (TODO: Make a better error message)" if nusmv_path.blank?
101
+ if commands.blank?
102
+ nusmv_cmd = "#{nusmv_path} #{file}"
103
+ else
104
+ tmp_cmd_file = PgVerify.tmp_file("nusmv_commands")
105
+ File.write(tmp_cmd_file, commands.join("\n"))
106
+ nusmv_cmd = "#{nusmv_path} -source '#{tmp_cmd_file}' #{file}"
107
+ end
108
+ output, err, status = Open3.capture3({}, nusmv_cmd)
109
+ raise RawNuSMVError.new(nusmv_cmd, output, err, status, file) unless status.success?
110
+ return output
111
+ end
112
+
113
+ def find_nusmv_path
114
+ # Return by settings path if that exists
115
+ return Settings.nusmv.path if !Settings.nusmv.path.blank? && File.file?(Settings.nusmv.path)
116
+
117
+ # Fall back to looking in the addon directory
118
+ candidates = Dir[File.join(PgVerify.addon_dir, "*", "bin", "NuSMV*")]
119
+ return candidates.sort.first unless candidates.empty?
120
+ end
121
+
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,23 @@
1
+ # Require all module files
2
+ Dir[File.join(__dir__, "**", '*.rb')].sort.each { |file| require file }
3
+
4
+
5
+ module PgVerify
6
+ module Puml
7
+
8
+ def self.find_path
9
+ # Return by settings path if that exists
10
+ return Settings.puml.path if !Settings.puml.path.blank? && File.file?(Settings.puml.path)
11
+
12
+ # Fall back to looking in the addon directory
13
+ candidates = Dir[File.join(PgVerify.addon_dir, "plantuml-*.jar")]
14
+ return candidates.sort.first unless candidates.empty?
15
+ end
16
+
17
+ def self.convert_file(in_path, out_path)
18
+ # TODO: The PlantUML jar switches focus to the desktop is if it would
19
+ # attempt to open a window each time it is invoked.
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ require_relative 'loading_animation.rb'
2
+
3
+ module PgVerify
4
+ module Shell
5
+
6
+ class LineAnimation < LoadingAnimation
7
+
8
+ def initialize(loading_message)
9
+ super(loading_message)
10
+ @last_line = nil
11
+ end
12
+
13
+ def on_anim(passed_time)
14
+ color = (passed_time * 2).to_i % 2 == 0 ? :loading : :white
15
+ circle = (passed_time * 2).to_i % 2 == 0 ? "▶" : "-"
16
+ circle = circle.color(color)
17
+ " #{circle} #{string = format_line(@last_line)} "
18
+ end
19
+
20
+ def printl(string)
21
+ return unless started?()
22
+ string = string.to_s
23
+ string = (string ||= "").gsub("\n", '')
24
+ string = string.c_sidenote unless Colorizer.color?(string)
25
+ @last_line = string
26
+ # print_anim(string)
27
+ end
28
+
29
+ def format_line(string)
30
+ return loading_message() if string.blank?
31
+ "#{loading_message()} [ #{string} ]"
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,80 @@
1
+ module PgVerify
2
+ module Shell
3
+
4
+ class LoadingAnimation
5
+
6
+ attr_reader :loading_message, :last_output_length, :start_time
7
+
8
+ def initialize(loading_message)
9
+ @loading_message = loading_message
10
+ @last_output_length = 0
11
+ @animation_thread = nil
12
+ @start_time = nil
13
+ end
14
+
15
+ def start
16
+ return unless @animation_thread.nil?
17
+ @start_time = Time.now
18
+ on_start()
19
+ @animation_thread = Thread.new {
20
+ begin
21
+ while true do
22
+ passed_time = Time.now - @start_time
23
+ string = on_anim(passed_time)
24
+ print_anim(string) unless string.nil?
25
+ sleep(1.0 / 10)
26
+ end
27
+ rescue => e
28
+ puts e
29
+ puts e.backtrace
30
+ raise e
31
+ end
32
+ }
33
+ self
34
+ end
35
+
36
+ def stop(status, finish_message)
37
+ return if @animation_thread.nil?
38
+ @animation_thread.exit
39
+ prompt = Shell.gen_prompt(status)
40
+ string = on_stop(prompt, finish_message)
41
+ @animation_thread = nil
42
+ print_anim(string)
43
+ puts
44
+ end
45
+
46
+ def print_anim(string)
47
+ return if string.blank?
48
+ string = string.to_s
49
+ # Fill the string with spaces to overwrite the last printed string
50
+ print_string = string + ( " " * [0, @last_output_length - string.length].max )
51
+ # Set the length of the last print to the string length without spaces,
52
+ # since they do not need to be overwritten.
53
+ @last_output_length = string.length
54
+ # Print the string with spaces.
55
+ print("\r#{print_string}")
56
+ end
57
+
58
+ def on_anim(passed_time); end
59
+ def on_start(); end
60
+ def printl(string); end
61
+
62
+ def on_stop(prompt, finish_message)
63
+ message = "#{prompt} #{@loading_message}"
64
+ message += " | #{finish_message}".c_sidenote unless finish_message.nil? || finish_message.empty?
65
+ if Settings.print_loading_times
66
+ duration = TimeUtil.duration_string((Time.now - @start_time).to_i)
67
+ duration_string = duration.nil? ? "" : "Took #{duration}".c_sidenote
68
+ return Shell.expand_to_console(message, duration_string)
69
+ else
70
+ return message
71
+ end
72
+ end
73
+
74
+ def started?()
75
+ !@animation_thread.nil?
76
+ end
77
+
78
+ end
79
+ end
80
+ end