envo 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/bin/envo-install +18 -0
- data/bin/envo_gen_tmp_helper.rb +50 -0
- data/bin/envo_run +30 -0
- data/lib/envo.rb +45 -0
- data/lib/envo/cli/envo.bat +11 -0
- data/lib/envo/cli/envo.sh +6 -0
- data/lib/envo/cli/help.rb +20 -0
- data/lib/envo/cli/installer_bash.rb +131 -0
- data/lib/envo/cli/installer_win_cmd.rb +96 -0
- data/lib/envo/cli/runner.rb +129 -0
- data/lib/envo/cli_parser.rb +54 -0
- data/lib/envo/cmd_clean.rb +53 -0
- data/lib/envo/cmd_copy.rb +56 -0
- data/lib/envo/cmd_list.rb +50 -0
- data/lib/envo/cmd_list_add.rb +102 -0
- data/lib/envo/cmd_list_del.rb +78 -0
- data/lib/envo/cmd_path.rb +28 -0
- data/lib/envo/cmd_reset.rb +66 -0
- data/lib/envo/cmd_run.rb +68 -0
- data/lib/envo/cmd_set.rb +76 -0
- data/lib/envo/cmd_show.rb +82 -0
- data/lib/envo/cmd_swap.rb +49 -0
- data/lib/envo/cmd_unset.rb +45 -0
- data/lib/envo/context.rb +156 -0
- data/lib/envo/error.rb +7 -0
- data/lib/envo/host.rb +24 -0
- data/lib/envo/host_shell.rb +11 -0
- data/lib/envo/logger.rb +27 -0
- data/lib/envo/parse_result.rb +9 -0
- data/lib/envo/parsed_cmd.rb +10 -0
- data/lib/envo/script_parser.rb +58 -0
- data/lib/envo/shell/bash.rb +63 -0
- data/lib/envo/shell/win_cmd.rb +47 -0
- data/lib/envo/state.rb +71 -0
- data/lib/envo/val/list_val.rb +107 -0
- data/lib/envo/val/no_val.rb +35 -0
- data/lib/envo/val/path_list_val.rb +24 -0
- data/lib/envo/val/path_val.rb +41 -0
- data/lib/envo/val/string_val.rb +40 -0
- data/lib/envo/val/val_builder.rb +59 -0
- data/lib/envo/version.rb +4 -0
- metadata +89 -0
data/lib/envo/error.rb
ADDED
data/lib/envo/host.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Envo
|
2
|
+
class Host
|
3
|
+
def initialize(shell)
|
4
|
+
@shell = shell
|
5
|
+
end
|
6
|
+
attr_reader :shell
|
7
|
+
|
8
|
+
def env
|
9
|
+
ENV
|
10
|
+
end
|
11
|
+
|
12
|
+
def pwd
|
13
|
+
Dir.pwd
|
14
|
+
end
|
15
|
+
|
16
|
+
def home
|
17
|
+
Dir.home
|
18
|
+
end
|
19
|
+
|
20
|
+
def path_exists?(path)
|
21
|
+
File.exist?(path)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Envo
|
2
|
+
HostShell = -> {
|
3
|
+
env_shell = ENV['SHELL']
|
4
|
+
break Shell::WinCmd if !env_shell
|
5
|
+
if env_shell =~ /bash/
|
6
|
+
raise Error.new "bash on Windows (msys) is not supported yet" if env_shell =~ /^[a-zA-Z]\:/
|
7
|
+
break Shell::Bash
|
8
|
+
end
|
9
|
+
raise Error.new "Unknown shell! Please report on https://github.com/iboB/envo/issues"
|
10
|
+
}.()
|
11
|
+
end
|
data/lib/envo/logger.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Envo
|
2
|
+
class Logger
|
3
|
+
ERROR = 0
|
4
|
+
WARN = 1
|
5
|
+
INFO = 2
|
6
|
+
DEBUG = 3
|
7
|
+
def initialize()
|
8
|
+
@max_level = DEBUG
|
9
|
+
end
|
10
|
+
attr_accessor :max_level
|
11
|
+
def log(level, text)
|
12
|
+
return if level > max_level
|
13
|
+
stream = level == 0 ? STDERR : STDOUT
|
14
|
+
stream.puts(text)
|
15
|
+
end
|
16
|
+
def plog(level, text)
|
17
|
+
return if level > max_level
|
18
|
+
stream = level == 0 ? STDERR : STDOUT
|
19
|
+
stream.print(text)
|
20
|
+
end
|
21
|
+
def error(text); log(ERROR, text); end
|
22
|
+
def warn(text); log(WARN, text); end
|
23
|
+
def puts(text); log(INFO, text); end
|
24
|
+
def print(text); plog(INFO, text); end
|
25
|
+
def debug(text); log(DEBUG, text); end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module Envo
|
4
|
+
class ScriptParser
|
5
|
+
def initialize(opts)
|
6
|
+
@known_cmds = {}
|
7
|
+
@known_opts = opts
|
8
|
+
end
|
9
|
+
def add_cmd(name, parse_func)
|
10
|
+
raise Envo::Error "cmd #{name} is already added to parser" if @known_cmds[name]
|
11
|
+
@known_cmds[name] = parse_func
|
12
|
+
end
|
13
|
+
def parse(lines)
|
14
|
+
result = ParseResult.new
|
15
|
+
|
16
|
+
lines.each_with_index do |line, li|
|
17
|
+
li += 1
|
18
|
+
line.strip!
|
19
|
+
next if line.empty?
|
20
|
+
next if line[0] == '#' # comment
|
21
|
+
|
22
|
+
line_opts = if line[0] == '{' # opts pack
|
23
|
+
i = line.index('}')
|
24
|
+
raise Envo::Error.new "#{li}: malformed options pack" if !i
|
25
|
+
opts = line[1...i].split(',')
|
26
|
+
line = line[i+1..]
|
27
|
+
opts
|
28
|
+
else
|
29
|
+
[]
|
30
|
+
end
|
31
|
+
|
32
|
+
raise Envo::Error.new "#{li}: missing command" if line.empty?
|
33
|
+
|
34
|
+
tokens = []
|
35
|
+
begin
|
36
|
+
tokens = CSV::parse_line(line, col_sep: ' ').compact
|
37
|
+
rescue
|
38
|
+
puts "AAAAAAA: #{line.inspect}"
|
39
|
+
raise Envo::Error.new "#{li}: malformed line"
|
40
|
+
end
|
41
|
+
|
42
|
+
raise Envo::Error.new "#{li}: missing command" if tokens.empty?
|
43
|
+
cmd = tokens.shift
|
44
|
+
raise Envo::Error.new "#{li}: unknown command '#{cmd}'" if !@known_cmds[cmd]
|
45
|
+
parsed_cmd = @known_cmds[cmd].(cmd, tokens, line_opts)
|
46
|
+
|
47
|
+
cmd_opts = {}
|
48
|
+
parsed_cmd.opts.each do |opt|
|
49
|
+
cmd_opts.merge! @known_opts.parse_script(opt)
|
50
|
+
end
|
51
|
+
parsed_cmd.opts = cmd_opts
|
52
|
+
|
53
|
+
result.cmds << parsed_cmd
|
54
|
+
end
|
55
|
+
result
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Envo
|
2
|
+
module Shell
|
3
|
+
module Bash
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def installer
|
7
|
+
Cli::InstallerBash
|
8
|
+
end
|
9
|
+
|
10
|
+
def path_var_name
|
11
|
+
'PATH'
|
12
|
+
end
|
13
|
+
def home_var_name
|
14
|
+
'HOME'
|
15
|
+
end
|
16
|
+
|
17
|
+
def likely_abs_path?(val)
|
18
|
+
!val.empty? && val[0] == '/'
|
19
|
+
end
|
20
|
+
def likely_rel_path?(val)
|
21
|
+
return !val.empty? && val[0] == '.'
|
22
|
+
end
|
23
|
+
def fix_path(path)
|
24
|
+
path
|
25
|
+
end
|
26
|
+
|
27
|
+
LIST_SEP = ':'
|
28
|
+
def likely_list?(val)
|
29
|
+
# we have some work
|
30
|
+
# if the value includes our list separtor ":", we need to make sure whether a url:port combination is not a better fit
|
31
|
+
return false if !val.include?(LIST_SEP)
|
32
|
+
|
33
|
+
sep_cnt = val.count(LIST_SEP)
|
34
|
+
return true if sep_cnt > 2
|
35
|
+
|
36
|
+
# match scheme://url
|
37
|
+
return false if val =~ /^\w+\:\/\//
|
38
|
+
|
39
|
+
return true if sep_cnt == 2 # everything else with 2 separators is a list
|
40
|
+
|
41
|
+
# match display type strings address:digit.digit
|
42
|
+
return false if val =~ /\:\d.\d$/
|
43
|
+
|
44
|
+
# match something:number to be interpreted as addr:port
|
45
|
+
!(val =~ /.*\:\d+$/)
|
46
|
+
end
|
47
|
+
def list_to_ar(list)
|
48
|
+
list.split(LIST_SEP)
|
49
|
+
end
|
50
|
+
def ar_to_list(ar)
|
51
|
+
ar.join(LIST_SEP)
|
52
|
+
end
|
53
|
+
|
54
|
+
def cmd_set_env_var(name, value)
|
55
|
+
escaped = value.to_s.inspect.gsub("'"){ "\\'" }
|
56
|
+
"export #{name}=#{escaped}"
|
57
|
+
end
|
58
|
+
def cmd_unset_env_var(name)
|
59
|
+
"unset -v #{name}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Envo
|
2
|
+
module Shell
|
3
|
+
module WinCmd
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def installer
|
7
|
+
Cli::InstallerWinCmd
|
8
|
+
end
|
9
|
+
|
10
|
+
def path_var_name
|
11
|
+
'Path'
|
12
|
+
end
|
13
|
+
def home_var_name
|
14
|
+
'HOME'
|
15
|
+
end
|
16
|
+
|
17
|
+
def likely_abs_path?(val)
|
18
|
+
val =~ /^[a-zA-Z]\:\\/
|
19
|
+
end
|
20
|
+
def likely_rel_path?(val)
|
21
|
+
return !val.empty? && val[0] == '.'
|
22
|
+
end
|
23
|
+
def fix_path(path)
|
24
|
+
path.gsub('/', '\\')
|
25
|
+
end
|
26
|
+
|
27
|
+
LIST_SEP = ';'
|
28
|
+
def likely_list?(val)
|
29
|
+
val.include?(LIST_SEP)
|
30
|
+
end
|
31
|
+
def list_to_ar(list)
|
32
|
+
list.split(LIST_SEP)
|
33
|
+
end
|
34
|
+
def ar_to_list(ar)
|
35
|
+
ar.join(LIST_SEP)
|
36
|
+
end
|
37
|
+
|
38
|
+
def cmd_set_env_var(name, value)
|
39
|
+
escaped = value # TODO
|
40
|
+
"set #{name}=#{escaped}"
|
41
|
+
end
|
42
|
+
def cmd_unset_env_var(name)
|
43
|
+
"set #{name}="
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/envo/state.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
module Envo
|
2
|
+
class State
|
3
|
+
def initialize(env)
|
4
|
+
@real_env = env.to_h
|
5
|
+
@work_env = nil
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :real_env
|
9
|
+
|
10
|
+
def set(name, val)
|
11
|
+
if val == nil
|
12
|
+
unset(name)
|
13
|
+
else
|
14
|
+
work_env[name] = val.to_s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def unset(name)
|
19
|
+
work_env.delete(name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get(name)
|
23
|
+
work_env[name]
|
24
|
+
end
|
25
|
+
|
26
|
+
def work_env
|
27
|
+
return @work_env if @work_env
|
28
|
+
# if @real_env is ENV, we can use to_h to clone it into the work env
|
29
|
+
# however it can be an actual hash in which case to_h will return the same one
|
30
|
+
# and we would have to use clone
|
31
|
+
# so to make this work in all cases we preform a manual shallow copy
|
32
|
+
@work_env = @real_env.map { |k, v| [k, v] }.to_h
|
33
|
+
end
|
34
|
+
|
35
|
+
class Patch
|
36
|
+
def initialize(removed = [], changed = {}, added = {})
|
37
|
+
@removed = removed
|
38
|
+
@changed = changed
|
39
|
+
@added = added
|
40
|
+
end
|
41
|
+
|
42
|
+
def empty?
|
43
|
+
@removed.empty? && @changed.empty? && @added.empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :removed, :changed, :added
|
47
|
+
end
|
48
|
+
|
49
|
+
def diff
|
50
|
+
return Patch.new if !@work_env
|
51
|
+
|
52
|
+
real_names = @real_env.keys
|
53
|
+
work_names = @work_env.keys
|
54
|
+
|
55
|
+
removed_names = real_names - work_names
|
56
|
+
added_names = work_names - real_names
|
57
|
+
preserved_names = real_names - removed_names
|
58
|
+
|
59
|
+
changed = preserved_names.map { |v|
|
60
|
+
r = @real_env[v]
|
61
|
+
w = @work_env[v]
|
62
|
+
|
63
|
+
r == w ? nil : [v, w]
|
64
|
+
}.compact.to_h
|
65
|
+
|
66
|
+
added = added_names.map { |v| [v, @work_env[v]] }.to_h
|
67
|
+
|
68
|
+
Patch.new(removed_names, changed, added)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Envo
|
2
|
+
class ListVal
|
3
|
+
def initialize(ar)
|
4
|
+
@ar = ar
|
5
|
+
end
|
6
|
+
attr_reader :ar
|
7
|
+
|
8
|
+
def insert(elem, pos = nil)
|
9
|
+
# assume unique elements
|
10
|
+
old_index = @ar.index(elem)
|
11
|
+
new_index = case pos
|
12
|
+
when :front then 0
|
13
|
+
when :back then -1
|
14
|
+
else old_index
|
15
|
+
end
|
16
|
+
|
17
|
+
return @ar << elem if !new_index
|
18
|
+
return @ar if new_index == old_index
|
19
|
+
return @ar.insert(new_index, elem) if !old_index
|
20
|
+
|
21
|
+
# we need to reorder
|
22
|
+
@ar.delete_at(old_index)
|
23
|
+
@ar.insert(new_index, elem)
|
24
|
+
end
|
25
|
+
def delete(elem)
|
26
|
+
@ar.delete(elem)
|
27
|
+
end
|
28
|
+
def delete_at(index)
|
29
|
+
@ar.delete_at(index)
|
30
|
+
end
|
31
|
+
def uniq!
|
32
|
+
@ar.uniq!
|
33
|
+
end
|
34
|
+
def clean!
|
35
|
+
uniq!
|
36
|
+
end
|
37
|
+
def shift(elem, dir)
|
38
|
+
i = @ar.index(elem)
|
39
|
+
return nil if i == nil
|
40
|
+
shift_at(i, dir)
|
41
|
+
end
|
42
|
+
def shift_at(i, dir)
|
43
|
+
return nil if i>@ar.size
|
44
|
+
|
45
|
+
if dir == :front
|
46
|
+
return i if i == 0
|
47
|
+
elem = ar[i]
|
48
|
+
@ar.delete_at i
|
49
|
+
@ar.unshift(elem)
|
50
|
+
0
|
51
|
+
elsif dir == :back
|
52
|
+
return i if i == (@ar.size-1)
|
53
|
+
elem = ar[i]
|
54
|
+
@ar.delete_at i
|
55
|
+
@ar << elem
|
56
|
+
@ar.size-1
|
57
|
+
elsif dir == :up
|
58
|
+
return i if i == 0
|
59
|
+
@ar[i-1], @ar[i] = @ar[i], @ar[i-1]
|
60
|
+
i - 1
|
61
|
+
elsif dir == :down
|
62
|
+
return i if i == (@ar.size-1)
|
63
|
+
@ar[i+1], @ar[i] = @ar[i], @ar[i+1]
|
64
|
+
i + 1
|
65
|
+
else
|
66
|
+
-1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def pp_attribs(elem)
|
71
|
+
@ar.count(elem) > 1 ? 'D' : ' '
|
72
|
+
end
|
73
|
+
def pretty_print(ctx)
|
74
|
+
ctx.puts "["
|
75
|
+
@ar.each_with_index do |v, i|
|
76
|
+
str = pp_attribs(v) + ' '
|
77
|
+
str += "#{i}:".ljust(4)
|
78
|
+
str += v
|
79
|
+
ctx.puts str
|
80
|
+
end
|
81
|
+
ctx.puts ']'
|
82
|
+
end
|
83
|
+
|
84
|
+
# casts
|
85
|
+
def type
|
86
|
+
:list
|
87
|
+
end
|
88
|
+
def accept_assign?(other)
|
89
|
+
other.list?
|
90
|
+
end
|
91
|
+
def invalid_description
|
92
|
+
@ar.empty? ? "empty list" : nil
|
93
|
+
end
|
94
|
+
def list?
|
95
|
+
true
|
96
|
+
end
|
97
|
+
def to_list
|
98
|
+
return self
|
99
|
+
end
|
100
|
+
def accept_item?(item)
|
101
|
+
true
|
102
|
+
end
|
103
|
+
def to_s
|
104
|
+
raise StandardError.new "list can't be converted to String"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|