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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +98 -0
- data/README.md +29 -0
- data/Rakefile +62 -0
- data/bin/console +15 -0
- data/bin/pg-verify.rb +18 -0
- data/bin/setup +8 -0
- data/calc.ebnf +21 -0
- data/data/config/pg-verify.yml +66 -0
- data/data/nusmv.sample.smv +179 -0
- data/data/project-template/.gitignore.resource +4 -0
- data/data/project-template/.pg-verify.yml +0 -0
- data/data/project-template/README.md +18 -0
- data/data/project-template/addon/.keep +0 -0
- data/data/project-template/program-graph.rb.resource +103 -0
- data/devpg +5 -0
- data/doc/examples/railroad_crossing.rb +61 -0
- data/doc/examples/train-tree.rb +43 -0
- data/doc/examples/weidezaun.rb +99 -0
- data/doc/examples/weidezaun.txt +29 -0
- data/doc/expose/definition.png +0 -0
- data/doc/expose/diagram.png +0 -0
- data/doc/expose/expose.md +359 -0
- data/doc/expose/validity.png +0 -0
- data/exe/pg-verify +4 -0
- data/integration_tests/ruby_dsl/001_states.rb +10 -0
- data/integration_tests/ruby_dsl/002_transitions.rb +10 -0
- data/integration_tests/ruby_dsl/003_actions.rb +14 -0
- data/integration_tests/ruby_dsl/004_guards.rb +18 -0
- data/integration_tests/ruby_dsl/005_variables.rb +16 -0
- data/integration_tests/ruby_dsl/006_state_variables.rb +26 -0
- data/integration_tests/ruby_dsl/007_variable_initialization.rb +28 -0
- data/integration_tests/ruby_dsl/008_state_initialization.rb +19 -0
- data/integration_tests/ruby_dsl/009_shared_variables.rb +26 -0
- data/integration_tests/ruby_dsl/010_complex_guards.rb +18 -0
- data/integration_tests/ruby_dsl/011_complex_actions.rb +16 -0
- data/integration_tests/ruby_dsl/012_error_components.rb +9 -0
- data/integration_tests/ruby_dsl/013_hazards.rb +25 -0
- data/integration_tests/ruby_dsl/014_tau_transitions.rb +26 -0
- data/integration_tests/ruby_dsl/015_basic_dcca.rb +19 -0
- data/integration_tests/ruby_dsl/016_pressure_tank.rb +146 -0
- data/lib/pg-verify/cli/cli.rb +235 -0
- data/lib/pg-verify/core/cmd_runner.rb +151 -0
- data/lib/pg-verify/core/core.rb +38 -0
- data/lib/pg-verify/core/extensions/array_extensions.rb +11 -0
- data/lib/pg-verify/core/extensions/enumerable_extensions.rb +19 -0
- data/lib/pg-verify/core/extensions/nil_extensions.rb +7 -0
- data/lib/pg-verify/core/extensions/string_extensions.rb +84 -0
- data/lib/pg-verify/core/shell/colorizer.rb +136 -0
- data/lib/pg-verify/core/shell/shell.rb +0 -0
- data/lib/pg-verify/core/util.rb +146 -0
- data/lib/pg-verify/doctor/doctor.rb +180 -0
- data/lib/pg-verify/ebnf_parser/ast.rb +31 -0
- data/lib/pg-verify/ebnf_parser/ebnf_parser.rb +26 -0
- data/lib/pg-verify/ebnf_parser/expression_parser.rb +177 -0
- data/lib/pg-verify/ebnf_parser/expression_parser2.rb +422 -0
- data/lib/pg-verify/ebnf_parser/expressions.ebnf +33 -0
- data/lib/pg-verify/ebnf_parser/expressions.peg +52 -0
- data/lib/pg-verify/ebnf_parser/parser_result.rb +26 -0
- data/lib/pg-verify/interpret/component_context.rb +125 -0
- data/lib/pg-verify/interpret/graph_context.rb +85 -0
- data/lib/pg-verify/interpret/interpret.rb +142 -0
- data/lib/pg-verify/interpret/pg_script.rb +72 -0
- data/lib/pg-verify/interpret/spec/ltl_builder.rb +90 -0
- data/lib/pg-verify/interpret/spec/spec_context.rb +32 -0
- data/lib/pg-verify/interpret/spec/spec_set_context.rb +67 -0
- data/lib/pg-verify/interpret/transition_context.rb +55 -0
- data/lib/pg-verify/model/allocation_set.rb +28 -0
- data/lib/pg-verify/model/assignment.rb +34 -0
- data/lib/pg-verify/model/component.rb +40 -0
- data/lib/pg-verify/model/dcca/hazard.rb +16 -0
- data/lib/pg-verify/model/dcca.rb +67 -0
- data/lib/pg-verify/model/expression.rb +106 -0
- data/lib/pg-verify/model/graph.rb +58 -0
- data/lib/pg-verify/model/model.rb +10 -0
- data/lib/pg-verify/model/parsed_expression.rb +77 -0
- data/lib/pg-verify/model/simulation/trace.rb +43 -0
- data/lib/pg-verify/model/simulation/variable_state.rb +23 -0
- data/lib/pg-verify/model/source_location.rb +45 -0
- data/lib/pg-verify/model/specs/spec.rb +44 -0
- data/lib/pg-verify/model/specs/spec_result.rb +25 -0
- data/lib/pg-verify/model/specs/spec_set.rb +43 -0
- data/lib/pg-verify/model/specs/specification.rb +50 -0
- data/lib/pg-verify/model/transition.rb +41 -0
- data/lib/pg-verify/model/validation/assignment_to_state_variable_validation.rb +26 -0
- data/lib/pg-verify/model/validation/empty_state_set_validation.rb +18 -0
- data/lib/pg-verify/model/validation/errors.rb +119 -0
- data/lib/pg-verify/model/validation/foreign_assignment_validation.rb +30 -0
- data/lib/pg-verify/model/validation/unknown_token_validation.rb +35 -0
- data/lib/pg-verify/model/validation/validation.rb +23 -0
- data/lib/pg-verify/model/variable.rb +47 -0
- data/lib/pg-verify/model/variable_set.rb +84 -0
- data/lib/pg-verify/nusmv/nusmv.rb +23 -0
- data/lib/pg-verify/nusmv/runner.rb +124 -0
- data/lib/pg-verify/puml/puml.rb +23 -0
- data/lib/pg-verify/shell/loading/line_animation.rb +36 -0
- data/lib/pg-verify/shell/loading/loading_animation.rb +80 -0
- data/lib/pg-verify/shell/loading/loading_prompt.rb +43 -0
- data/lib/pg-verify/shell/loading/no_animation.rb +20 -0
- data/lib/pg-verify/shell/shell.rb +30 -0
- data/lib/pg-verify/simulation/simulation.rb +7 -0
- data/lib/pg-verify/simulation/simulator.rb +90 -0
- data/lib/pg-verify/simulation/state.rb +53 -0
- data/lib/pg-verify/transform/hash_transformation.rb +104 -0
- data/lib/pg-verify/transform/nusmv_transformation.rb +261 -0
- data/lib/pg-verify/transform/puml_transformation.rb +89 -0
- data/lib/pg-verify/transform/transform.rb +8 -0
- data/lib/pg-verify/version.rb +5 -0
- data/lib/pg-verify.rb +47 -0
- data/pg-verify.gemspec +38 -0
- data/sig/pg-verify.rbs +4 -0
- metadata +226 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
module PgVerify
|
|
2
|
+
|
|
3
|
+
module Core
|
|
4
|
+
|
|
5
|
+
module CMDRunner
|
|
6
|
+
|
|
7
|
+
# Runs the command and raises on error.
|
|
8
|
+
def self.run_cmd(cmd, env_variables = {}, include_stderr: false)
|
|
9
|
+
start_time = Time.new
|
|
10
|
+
output, err, status = Open3.capture3(env_variables, cmd)
|
|
11
|
+
delta_seconds = (Time.new - start_time).to_i
|
|
12
|
+
raise CMDRunnerError.new(cmd, output, err, delta_seconds) unless status.success?
|
|
13
|
+
return output + err if include_stderr
|
|
14
|
+
return output
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Runs the command and returns stdout on success. Returns the specified value otherwise.
|
|
18
|
+
def self.run_or_return(cmd, fail_output = nil)
|
|
19
|
+
output, err, status = Open3.capture3(cmd)
|
|
20
|
+
return status.success? ? output : fail_output
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Runs the command and returns stdout, stderr and the status.
|
|
24
|
+
# Status will be true only if the command succeeded.
|
|
25
|
+
def self.run_for_result(cmd)
|
|
26
|
+
output, err, status = run_for_result_with_plain_status(cmd)
|
|
27
|
+
return output, err, status.success?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.run_for_result_with_plain_status(cmd)
|
|
31
|
+
output, err, status = Open3.capture3(cmd)
|
|
32
|
+
return output, err, status
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.run_for_exit_code(cmd)
|
|
36
|
+
output, err, status = Open3.capture3(cmd)
|
|
37
|
+
return status.exitstatus
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.drop_into_shell()
|
|
41
|
+
shell = run_cmd("echo ${SHELL}")
|
|
42
|
+
system("#{shell}")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.run_in_screen(cmd, session_name)
|
|
46
|
+
run_cmd("screen -S #{session_name} -dm bash -c '#{cmd}'")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.run_with_timeout(cmd)
|
|
50
|
+
require 'timeout'
|
|
51
|
+
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thread|
|
|
52
|
+
Timeout.timeout(2) do
|
|
53
|
+
wait_thread.join
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.with_drop_on_fail(shell, intent: nil, &blk)
|
|
59
|
+
begin
|
|
60
|
+
blk.call()
|
|
61
|
+
rescue CMDRunnerError => e
|
|
62
|
+
shell.info("Output:\n#{e.output}")
|
|
63
|
+
shell.error("The action that was attempted did fail after #{e.delta_seconds} seconds!")
|
|
64
|
+
shell.info("The exact command was: #{e.command.c_command}")
|
|
65
|
+
shell.info("Intent was: #{intent}") unless intent.nil?
|
|
66
|
+
shell.info("You can try to resolve this problem manually.")
|
|
67
|
+
raise e unless shell.ask_confirm(nil, question: "Do you want to open a shell and try our luck?")
|
|
68
|
+
drop_into_shell()
|
|
69
|
+
shell.info("Welcome back :D")
|
|
70
|
+
|
|
71
|
+
op1 = "Run the command again to see if it works."
|
|
72
|
+
op2 = "Just continue and pretend the command did succeed.\n(This will return '' to the caller)"
|
|
73
|
+
op3 = "Assume the command failed."
|
|
74
|
+
sel = shell.select([op1, op2, op3], prompt: "How shall we continue?").first
|
|
75
|
+
case sel
|
|
76
|
+
when op1
|
|
77
|
+
return with_drop_on_fail(shell, blk, intent: intent,)
|
|
78
|
+
when op2
|
|
79
|
+
return ""
|
|
80
|
+
when op3
|
|
81
|
+
raise e
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Runs the specified command and waits for it to complete. Calls the specified
|
|
87
|
+
# block for each line the process writes to stdout and err.
|
|
88
|
+
# Will return two things:
|
|
89
|
+
# 1. The complete output as a string
|
|
90
|
+
# 2. The result as a bool, where true means success.
|
|
91
|
+
# If raise_on_fail is set to true it will raise directly on fail and not return the results.
|
|
92
|
+
# If gulp_interrupt is set to true will also gulp interrupts.
|
|
93
|
+
def self.run_and_follow(cmd, raise_on_fail: true, gulp_interrupt: false, timeout: nil, &blk)
|
|
94
|
+
start_time = Time.new
|
|
95
|
+
output = ""
|
|
96
|
+
success = nil
|
|
97
|
+
|
|
98
|
+
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thread|
|
|
99
|
+
begin
|
|
100
|
+
# Listen to stdout & stderr in seperate threads.
|
|
101
|
+
[stdout, stderr].each do |stream|
|
|
102
|
+
Thread.new do
|
|
103
|
+
begin
|
|
104
|
+
until (line = stream.gets).nil? do
|
|
105
|
+
blk.call(line) unless blk.nil?
|
|
106
|
+
output += line
|
|
107
|
+
end
|
|
108
|
+
rescue IOError => e
|
|
109
|
+
# Expected when the stream is closed
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
unless timeout.nil?
|
|
114
|
+
require 'timeout'
|
|
115
|
+
Timeout.timeout(timeout) { wait_thread.join }
|
|
116
|
+
else
|
|
117
|
+
wait_thread.join
|
|
118
|
+
end
|
|
119
|
+
delta_seconds = Time.new - start_time
|
|
120
|
+
success = wait_thread.value.success?
|
|
121
|
+
raise CMDRunnerError.new(cmd, output, "", delta_seconds) if raise_on_fail && !success
|
|
122
|
+
rescue SignalException => e
|
|
123
|
+
raise e unless gulp_interrupt
|
|
124
|
+
success = false
|
|
125
|
+
end
|
|
126
|
+
return output, success
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def self.command_exists?(command)
|
|
131
|
+
!run_or_return("command -v '#{command}'", nil).nil?
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
class CMDRunnerError < PgVerify::Core::Error
|
|
135
|
+
def initialize(cmd, output, err, delta_seconds)
|
|
136
|
+
@cmd, @output, @err, @delta_seconds = cmd, output, err, delta_seconds
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def formatted()
|
|
140
|
+
title = "Running '#{@cmd}' failed after #{@delta_seconds} seconds:"
|
|
141
|
+
|
|
142
|
+
body = "$ #{@cmd.c_string} \n\n #{@err}"
|
|
143
|
+
return title, body
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module PgVerify
|
|
2
|
+
|
|
3
|
+
module Core
|
|
4
|
+
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
|
|
7
|
+
def formatted()
|
|
8
|
+
raise "Not implemented in subclass #{self.class}"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def to_formatted()
|
|
12
|
+
title, body, hint = self.formatted()
|
|
13
|
+
|
|
14
|
+
message = []
|
|
15
|
+
|
|
16
|
+
error_label = " ✖ ERROR ".bg_error
|
|
17
|
+
message << "#{error_label} #{title.c_error}" unless title.nil?
|
|
18
|
+
|
|
19
|
+
indent = " ".bg_error + " "
|
|
20
|
+
message << "#{indent}\n#{body.indented(str: indent)}\n#{indent}" unless body.nil?
|
|
21
|
+
|
|
22
|
+
indent = " ".bg_warn + " "
|
|
23
|
+
message << backtrace.map.map {|l| "#{indent}#{l.c_warn}"}.join("\n") if Settings.full_stack_trace
|
|
24
|
+
|
|
25
|
+
indent = " ".bg_sidenote + " "
|
|
26
|
+
message << hint.split("\n").map {|l| "#{indent}#{l.c_sidenote}"}.join("\n") unless hint.nil?
|
|
27
|
+
|
|
28
|
+
return message.join("\n")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Require all module files
|
|
38
|
+
Dir[File.join(__dir__, "**", '*.rb')].sort.each { |file| require file }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Enumerable
|
|
2
|
+
|
|
3
|
+
def powerset
|
|
4
|
+
array = self.to_a()
|
|
5
|
+
ret = Enumerator.new {|ps|
|
|
6
|
+
array.size.times {|n|
|
|
7
|
+
array.combination(n).each(&ps.method(:yield))
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return ret.to_a + [array]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def subset?(other)
|
|
14
|
+
other = other.to_a()
|
|
15
|
+
array = self.to_a()
|
|
16
|
+
return other.all? { |item| array.include?(item) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
class String
|
|
2
|
+
|
|
3
|
+
def integer?
|
|
4
|
+
self.to_i.to_s == self
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def snake_case
|
|
8
|
+
self.strip()
|
|
9
|
+
.gsub(/ +/, "_")
|
|
10
|
+
.gsub(/::/, '/')
|
|
11
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
|
12
|
+
.gsub(/([a-z\d])([A-Z])/,'\1_\2')
|
|
13
|
+
.tr("-", "_")
|
|
14
|
+
.downcase()
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def camel_case
|
|
18
|
+
return self if self !~ /_/ && self =~ /[A-Z]+.*/
|
|
19
|
+
split('_').map{|e| e.capitalize}.join
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def grep(str, e)
|
|
23
|
+
self.split("\n").select { |l| l.include?(str) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def display_length()
|
|
27
|
+
str = PgVerify::Colorizer.uncolorize(self)
|
|
28
|
+
str.length() + ( str.count("\t") * 4 )
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def line_combine(other, separator: " ")
|
|
32
|
+
PgVerify::StringUtil.line_combine(self, other, separator: separator)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def indented(num: 1, str: " " * 4)
|
|
36
|
+
PgVerify::StringUtil.indented(self, num_indents: num, indent_string: str)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def remove_before(substring)
|
|
40
|
+
split = self.split(substring)
|
|
41
|
+
return "" if split.length == 1
|
|
42
|
+
return split[1, split.length].join(substring)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def blank?
|
|
46
|
+
self.empty?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def limit_lines(num_lines, separator: "...")
|
|
50
|
+
return "" if num_lines == 0
|
|
51
|
+
split = self.split("\n")
|
|
52
|
+
return self unless split.length > num_lines
|
|
53
|
+
first_part = split[0, num_lines / 2]
|
|
54
|
+
second_part = split[split.length - (num_lines - first_part.length), split.length]
|
|
55
|
+
return first_part.join("\n") + "\n#{separator}\n" + second_part.join("\n")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def shorten(length)
|
|
59
|
+
return self if self.length <= length
|
|
60
|
+
return self[0, [length - 3, 1].max] + "..."
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def labelize(bg: :darkgreen, fg: :white)
|
|
64
|
+
"◖".send(:"c_#{bg}") + " #{self} ".send(:"bg_#{bg}").send(:"c_#{fg}").c_bold + "◗".send(:"c_#{bg}")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def file?()
|
|
68
|
+
File.file?(self)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def directory?()
|
|
72
|
+
File.directory?(self)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Support this method for Ruby <= 2.3
|
|
76
|
+
unless self.method_defined?(:delete_prefix)
|
|
77
|
+
def delete_prefix(prefix)
|
|
78
|
+
self.respond_to?(:delete_prefix)
|
|
79
|
+
return unless self.start_with?(prefix)
|
|
80
|
+
return self[prefix.length, self.length - 1]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require "rainbow"
|
|
2
|
+
|
|
3
|
+
module PgVerify
|
|
4
|
+
|
|
5
|
+
# A Module for easy coloration of strings with theme support.
|
|
6
|
+
# After #attach is called strings can be colored using one of any c_ or bg_ methods
|
|
7
|
+
# e.g: "Hello".c_cyan, "Hello".bg_blue
|
|
8
|
+
# Calls can also by chained:
|
|
9
|
+
# e.g: "Hello".c_red.bg_blue.c_bold
|
|
10
|
+
#
|
|
11
|
+
# A theme is a hash of keys to values. Values can be:
|
|
12
|
+
# 1. Hex values. e.g '#00AABB'
|
|
13
|
+
# 2. Color names that are defined elswhere. e.g: 'red'
|
|
14
|
+
# 3. Arrays of values. e.g [ 'white', '_#000000', 'bold' ]
|
|
15
|
+
# If a value starts with an underscore (e.g _red) the color will
|
|
16
|
+
# be used as the background color.
|
|
17
|
+
# Strings can then be colored using the c_<key> method.
|
|
18
|
+
# e.g: mycolor => [ "#FFFFFF", "_black" ]
|
|
19
|
+
# mystyle => [ "mycolor", "bold" ]
|
|
20
|
+
# "Hello".c_mystyle <- Colored white on black in bold
|
|
21
|
+
module Colorizer
|
|
22
|
+
|
|
23
|
+
def self.send_call(rainbow, color_expr)
|
|
24
|
+
prefix = color_expr.start_with?("_") ? "bg" : "c"
|
|
25
|
+
color_expr = color_expr.sub("_", "")
|
|
26
|
+
|
|
27
|
+
# If the color expression is a HEX (e.g #FF3400) ..
|
|
28
|
+
if color_expr.start_with?("#")
|
|
29
|
+
# Send the call directly to Rainbow
|
|
30
|
+
return rainbow.send(prefix == "bg" ? :background : :color, color_expr)
|
|
31
|
+
else
|
|
32
|
+
# Otherwise forward the call
|
|
33
|
+
return rainbow.send("#{prefix}_#{color_expr}".to_sym)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.define_methods(hash)
|
|
38
|
+
Rainbow::X11ColorNames::NAMES.each do |color_name, _|
|
|
39
|
+
define_method "c_#{color_name}".to_sym do
|
|
40
|
+
Rainbow(self).color(color_name.to_sym)
|
|
41
|
+
end
|
|
42
|
+
define_method "bg_#{color_name}".to_sym do
|
|
43
|
+
Rainbow(self).send(:background, color_name)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
hash.each do |key, color_names|
|
|
48
|
+
array = color_names.is_a?(Array) ? color_names : [ color_names ]
|
|
49
|
+
define_method "c_#{key}".to_sym do
|
|
50
|
+
rainbow = Rainbow(self)
|
|
51
|
+
array.each { |color_name|
|
|
52
|
+
rainbow = Colorizer.send_call(rainbow, color_name)
|
|
53
|
+
}
|
|
54
|
+
rainbow
|
|
55
|
+
end
|
|
56
|
+
define_method "bg_#{key}".to_sym do
|
|
57
|
+
rainbow = Rainbow(self)
|
|
58
|
+
array.each { |color_name|
|
|
59
|
+
rainbow = Colorizer.send_call(rainbow, "_#{color_name}")
|
|
60
|
+
}
|
|
61
|
+
rainbow
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.uncolorize(string)
|
|
67
|
+
string.gsub(/\e\[([;\d]+)?m/, '')
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.color?(string)
|
|
71
|
+
!!/\e\[([;\d]+)?m/.match(string)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def color(color)
|
|
75
|
+
method = "c_#{color}".to_sym
|
|
76
|
+
return self unless self.respond_to?(method)
|
|
77
|
+
self.send(method)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def c_bold
|
|
81
|
+
Rainbow(self).bold
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def c_italic
|
|
85
|
+
Rainbow(self).italic
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def c_underline
|
|
89
|
+
Rainbow(self).underline
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def c_none
|
|
93
|
+
self
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def color_bg(color)
|
|
97
|
+
Rainbow(self).background(color)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def color_regex(hash)
|
|
101
|
+
hash.each do |key, val|
|
|
102
|
+
self.gsub!(Regexp.new(key)) { |match|
|
|
103
|
+
"#{match.color(val)}"
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
self
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def color_unique()
|
|
110
|
+
r, g, b = Colorizer.unique_color(self)
|
|
111
|
+
return Rainbow(self).color(r, g, b)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def bg_color_unique()
|
|
115
|
+
r, g, b = Colorizer.unique_color(self)
|
|
116
|
+
return Rainbow(self).background(r, g, b)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# def self.unique_color(string)
|
|
120
|
+
# r = Random.new(Integer("0x#{Digest::SHA256.hexdigest(string)}"))
|
|
121
|
+
# number = r.rand(0..360)
|
|
122
|
+
# r, g, b = VsuColorUtil.hsl_to_rgb(number, 60, 50)
|
|
123
|
+
# return r, g, b
|
|
124
|
+
# end
|
|
125
|
+
|
|
126
|
+
def self.attach(theme, use_colors: true)
|
|
127
|
+
define_methods(theme)
|
|
128
|
+
use_colors = Settings.use_colors
|
|
129
|
+
use_colors &&= Settings.use_colors_in_pipe if !$stdout.isatty
|
|
130
|
+
Rainbow.enabled = use_colors
|
|
131
|
+
String.class_eval { include PgVerify::Colorizer }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
module PgVerify
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module TimeUtil
|
|
5
|
+
|
|
6
|
+
SECONDS_IN_SECOND = 1
|
|
7
|
+
SECONDS_IN_MINUTE = SECONDS_IN_SECOND * 60
|
|
8
|
+
SECONDS_IN_HOUR = SECONDS_IN_MINUTE * 60
|
|
9
|
+
SECONDS_IN_DAY = SECONDS_IN_HOUR * 24
|
|
10
|
+
SECONDS_IN_WEEK = SECONDS_IN_DAY * 7
|
|
11
|
+
SECONDS_IN_MONTH = SECONDS_IN_WEEK * 4
|
|
12
|
+
SECONDS_IN_YEAR = SECONDS_IN_MONTH * 12
|
|
13
|
+
|
|
14
|
+
def self.duration_string(seconds, short: false)
|
|
15
|
+
i_seconds = seconds.to_i
|
|
16
|
+
return "~#{'%.2f' % (seconds*1000)}#{short ? "ms" : " milliseconds"}" if i_seconds == 0
|
|
17
|
+
return duration_to_h(i_seconds, short: short)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.duration_to_h(seconds, short: false)
|
|
21
|
+
seconds = seconds.to_i
|
|
22
|
+
{
|
|
23
|
+
SECONDS_IN_YEAR => ( short ? "y" : " year" ),
|
|
24
|
+
SECONDS_IN_MONTH => ( short ? "M" : " month" ),
|
|
25
|
+
SECONDS_IN_WEEK => ( short ? "w" : " week" ),
|
|
26
|
+
SECONDS_IN_DAY => ( short ? "d" : " day" ),
|
|
27
|
+
SECONDS_IN_HOUR => ( short ? "h" : " hour" ),
|
|
28
|
+
SECONDS_IN_MINUTE => ( short ? "m" : " minute" ),
|
|
29
|
+
SECONDS_IN_SECOND => ( short ? "s" : " second" )
|
|
30
|
+
}.each do |unit_seconds, unit_string|
|
|
31
|
+
units = seconds / unit_seconds
|
|
32
|
+
unit_string = unit_string + "s" if !short && units != 1
|
|
33
|
+
return "#{units}#{unit_string} #{duration_to_h(seconds % unit_seconds, short: short)}".strip unless units == 0
|
|
34
|
+
end
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.ago_h(start_time, short: false)
|
|
39
|
+
duration_to_h((Time.now - start_time).to_i, short: short) + " ago"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.timestamp(time = Time.new)
|
|
43
|
+
return time.utc.to_i
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.from_timestamp(ts)
|
|
47
|
+
time = Time.at(ts.to_i).utc
|
|
48
|
+
return time
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class StringUtil
|
|
54
|
+
|
|
55
|
+
def self.make_unique(string, strings, &blk)
|
|
56
|
+
base_string = string
|
|
57
|
+
index = 0
|
|
58
|
+
while strings.include?(string)
|
|
59
|
+
index += 1
|
|
60
|
+
string = blk.call(base_string, index)
|
|
61
|
+
end
|
|
62
|
+
string
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.limit_width(string, width)
|
|
66
|
+
return string if string.nil? || string.length <= width
|
|
67
|
+
return string.chars.each_slice(width).map(&:join).join("\n")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.auto_complete(string, options)
|
|
71
|
+
perfect_match = options.select { |o| o == string }.uniq
|
|
72
|
+
return perfect_match unless perfect_match.empty?
|
|
73
|
+
options.select { |o| o.start_with?(string) }.uniq
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.levenshtein_suggest(string, options, suggestions: 5)
|
|
77
|
+
options.map { |o| [o, levenshtein_distance(string, o)] }
|
|
78
|
+
.sort_by{ |a| a[1] }
|
|
79
|
+
.vsu_limit(suggestions)
|
|
80
|
+
.map { |a| a[0] }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.line_combine(string1, string2, separator: " ")
|
|
84
|
+
return string2 if string1.empty?
|
|
85
|
+
lines1, lines2 = string1.split("\n"), string2.split("\n")
|
|
86
|
+
both = [lines1, lines2]
|
|
87
|
+
height = both.map(&:length).max
|
|
88
|
+
l_width = lines1.map(&:display_length).max
|
|
89
|
+
|
|
90
|
+
# Fill up empty lines to match height
|
|
91
|
+
both.each { |lines| loop { break if lines.length >= height; lines << "" } }
|
|
92
|
+
|
|
93
|
+
# Fill up left lines to align right side
|
|
94
|
+
lines1 = lines1.map { |l| l + " " * (l_width - l.display_length) }
|
|
95
|
+
|
|
96
|
+
# Combine left and right.
|
|
97
|
+
string = (0...height).map { |index|
|
|
98
|
+
lines1[index] + separator + lines2[index]
|
|
99
|
+
}.join("\n")
|
|
100
|
+
|
|
101
|
+
return string
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def self.shorten_unique(strings)
|
|
105
|
+
# TODO: Implement
|
|
106
|
+
return strings.each_with_index.map { |s, i| [s, i.to_s] }.to_h
|
|
107
|
+
|
|
108
|
+
# chars = ".- _".chars
|
|
109
|
+
# regex = /#{chars.map { |c| "\\#{c}" }.join("|")}/
|
|
110
|
+
# map = {}
|
|
111
|
+
# strings.each do |str|
|
|
112
|
+
# index = 0
|
|
113
|
+
# loop do
|
|
114
|
+
# split = str.gsub(regex, " ").split
|
|
115
|
+
# short = split.map { |word| word[0, index] }.join("")
|
|
116
|
+
# puts short
|
|
117
|
+
# sleep(1)
|
|
118
|
+
# next if map.values.include?(short)
|
|
119
|
+
# map[str] = short
|
|
120
|
+
# break
|
|
121
|
+
# end
|
|
122
|
+
|
|
123
|
+
# end
|
|
124
|
+
|
|
125
|
+
# map
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def self.indented(string, num_indents: 1, indent_string: "\t")
|
|
129
|
+
string.split("\n").map { |l| "#{indent_string * num_indents}#{l}" }.join("\n")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def self.levenshtein_distance(a, b)
|
|
133
|
+
a, b = a.downcase, b.downcase
|
|
134
|
+
costs = Array(0..b.length) # i == 0
|
|
135
|
+
(1..a.length).each do |i|
|
|
136
|
+
costs[0], nw = i, i - 1 # j == 0; nw is lev(i-1, j)
|
|
137
|
+
(1..b.length).each do |j|
|
|
138
|
+
costs[j], nw = [costs[j] + 1, costs[j-1] + 1, a[i-1] == b[j-1] ? nw : nw + 1].min, costs[j]
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
costs[b.length]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
end
|