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.
@@ -0,0 +1,7 @@
1
+ module Envo
2
+ class Error < StandardError
3
+ def initialize(msg)
4
+ super
5
+ end
6
+ end
7
+ end
@@ -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
@@ -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,9 @@
1
+ module Envo
2
+ class ParseResult
3
+ def initialize()
4
+ @opts = {}
5
+ @cmds = []
6
+ end
7
+ attr_accessor :opts, :cmds
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module Envo
2
+ class ParsedCmd
3
+ def initialize(cmd, opts)
4
+ @cmd = cmd
5
+ @opts = opts
6
+ end
7
+ attr_reader :cmd
8
+ attr_accessor :opts
9
+ end
10
+ 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
@@ -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