pg-verify 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|