zashoku 1.0.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/README.md +16 -0
- data/bin/zashoku +17 -0
- data/config/daemon.yml +8 -0
- data/config/view.yml +16 -0
- data/lib/core/config/config.rb +96 -0
- data/lib/core/config/daemon_config.rb +19 -0
- data/lib/core/config/view_config.rb +19 -0
- data/lib/core/controller.rb +13 -0
- data/lib/core/formatter.rb +115 -0
- data/lib/core/item.rb +24 -0
- data/lib/core/module.rb +52 -0
- data/lib/core/net/client.rb +115 -0
- data/lib/core/net/server.rb +70 -0
- data/lib/core/options.rb +43 -0
- data/lib/core/util/folder_listen.rb +53 -0
- data/lib/core/util/term.rb +61 -0
- data/lib/core/util/util.rb +47 -0
- data/lib/core/view.rb +268 -0
- data/lib/daemon.rb +83 -0
- data/lib/generator.rb +44 -0
- data/lib/viewer.rb +178 -0
- data/lib/zashoku.rb +35 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d4fa01a68fedb50955e21f463118c0b40b7a3ea3
|
4
|
+
data.tar.gz: f6c95b13d29fbde419815753ed527fef44a6de14
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b4f12efc2dc904b9ffe2d5d1204be2b083e77318d6673398bbc805d3677a1503e8cb1e19cfe72d9e52b8cdeea9392d53263360c3b64846802a82659df534bca2
|
7
|
+
data.tar.gz: 62b9a8b0d03efba68fd7523fe5a093de69789423d2a59ff1ffa6600903aace9d3db2720227fdad9a8c130b59166043d0f371d24d1bbfc42fc32c94c3bba269eb
|
data/README.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# zashoku
|
2
|
+
command line application framework
|
3
|
+
|
4
|
+
## features
|
5
|
+
+ for lazy people
|
6
|
+
+ generators
|
7
|
+
+ optimized view
|
8
|
+
+ mvc
|
9
|
+
+ client / server model
|
10
|
+
|
11
|
+
## installation
|
12
|
+
`gem install zashoku`
|
13
|
+
|
14
|
+
## documentation
|
15
|
+
+ [rubydoc.info](http://www.rubydoc.info/gems/zashoku/)
|
16
|
+
+ [wiki](https://github.com/annacrombie/zashoku/wiki/)
|
data/bin/zashoku
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
if File.exist?(File.join(File.dirname(__FILE__), '../lib/zashoku.rb'))
|
6
|
+
require_relative '../lib/zashoku'
|
7
|
+
end
|
8
|
+
# rescue LoadError
|
9
|
+
# require 'zashoku'
|
10
|
+
end
|
11
|
+
|
12
|
+
unless defined? Zashoku
|
13
|
+
puts 'cant find zashoku library'
|
14
|
+
exit
|
15
|
+
end
|
16
|
+
|
17
|
+
Zashoku.main(ARGV)
|
data/config/daemon.yml
ADDED
data/config/view.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
color:
|
3
|
+
selected: "\e[47m\e[31m"
|
4
|
+
primary: "\e[0m\e[32m"
|
5
|
+
secondary: "\e[0m\e[33m"
|
6
|
+
main: "\e[0m\e[34m"
|
7
|
+
modules:
|
8
|
+
disabled: []
|
9
|
+
enabled:
|
10
|
+
- Bandcamp
|
11
|
+
keymaps:
|
12
|
+
global:
|
13
|
+
259: current_view move_cursor up
|
14
|
+
258: current_view move_cursor down
|
15
|
+
" ": current_view expand $selected_group
|
16
|
+
q: quit
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
module Zashoku
|
7
|
+
class Config < Controller
|
8
|
+
CONF_DIR = File.join(Dir.home, '.config/zashoku')
|
9
|
+
CONF_FILE = File.join(CONF_DIR, '/conf.yml')
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
FileUtils.mkdir_p(CONF_DIR) unless File.directory?(CONF_DIR)
|
13
|
+
reload
|
14
|
+
save unless File.exist?(self.class::CONF_FILE)
|
15
|
+
end
|
16
|
+
|
17
|
+
def reload
|
18
|
+
@conf = merge!(default_conf, user_conf)
|
19
|
+
end
|
20
|
+
|
21
|
+
def merge!(this, that)
|
22
|
+
this&.merge(that) do |_k, old, nu|
|
23
|
+
old.class == Hash ? merge!(old, nu) : nu
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def base_conf
|
28
|
+
{}
|
29
|
+
end
|
30
|
+
|
31
|
+
def default_conf
|
32
|
+
module_conf.reduce(base_conf) do |mem, mc|
|
33
|
+
merge!(mem, mc)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def module_conf
|
38
|
+
{}
|
39
|
+
end
|
40
|
+
|
41
|
+
def user_conf
|
42
|
+
read(self.class::CONF_FILE)
|
43
|
+
end
|
44
|
+
|
45
|
+
def pref_model(title, value, trail)
|
46
|
+
Model.new(title: title, value: value, trail: trail)
|
47
|
+
end
|
48
|
+
|
49
|
+
def items
|
50
|
+
@conf.map do |title, pref|
|
51
|
+
next if IGNORE.key? title
|
52
|
+
case pref
|
53
|
+
when Hash
|
54
|
+
pref.map do |subt, subpref|
|
55
|
+
pref_model("#{title}_#{subt}", subpref, [title, subt])
|
56
|
+
end
|
57
|
+
when Array
|
58
|
+
pref_model(title, pref.join(':'), [title])
|
59
|
+
else
|
60
|
+
pref_model(title, pref, [title])
|
61
|
+
end
|
62
|
+
end.flatten.compact
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_dir(path)
|
66
|
+
{
|
67
|
+
# '$airing_dir' => @conf['airing_dir'],
|
68
|
+
# '$conf_dir' => @conf['conf_dir'],
|
69
|
+
'~' => Dir.home
|
70
|
+
}.inject(path) { |p, r| p.gsub(r[0], r[1]) }
|
71
|
+
end
|
72
|
+
|
73
|
+
def set(trail, destination)
|
74
|
+
trail = [trail] if trail.class == String
|
75
|
+
|
76
|
+
trail[0...-1].inject(@conf, :fetch)[trail.last] = destination
|
77
|
+
save
|
78
|
+
changed
|
79
|
+
notify_observers
|
80
|
+
end
|
81
|
+
|
82
|
+
def get(trail)
|
83
|
+
@conf.dig(*trail)
|
84
|
+
end
|
85
|
+
|
86
|
+
def save
|
87
|
+
File.open(self.class::CONF_FILE, 'w') { |f| f.write(YAML.dump(@conf)) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def read(file)
|
91
|
+
YAML.safe_load(File.open(file, 'r', &:read))
|
92
|
+
rescue
|
93
|
+
{}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'config'
|
4
|
+
|
5
|
+
module Zashoku
|
6
|
+
class DaemonConfig < Zashoku::Config
|
7
|
+
CONF_FILE = File.join(CONF_DIR, '/daemon.yml')
|
8
|
+
|
9
|
+
def module_conf
|
10
|
+
Zashoku.modules.map do |_k, mod|
|
11
|
+
mod.send(:daemon_config)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def base_conf
|
16
|
+
read(File.join(Zashoku::Root, 'config/daemon.yml'))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'config'
|
4
|
+
|
5
|
+
module Zashoku
|
6
|
+
class ViewConfig < Zashoku::Config
|
7
|
+
CONF_FILE = File.join(CONF_DIR, '/view.yml')
|
8
|
+
|
9
|
+
def module_conf
|
10
|
+
Zashoku.modules.map do |_k, mod|
|
11
|
+
mod.send(:view_config)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def base_conf
|
16
|
+
read(File.join(Zashoku::Root, 'config/view.yml'))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'unicode/display_width'
|
4
|
+
|
5
|
+
module Zashoku
|
6
|
+
module Formatter
|
7
|
+
PAD = ' '
|
8
|
+
SPLIT = '@split@'
|
9
|
+
BYTE_SUFFIXES = {
|
10
|
+
1_000_000_000_000 => 'T',
|
11
|
+
1_000_000_000 => 'G',
|
12
|
+
1_000_000 => 'M',
|
13
|
+
1_000 => 'K',
|
14
|
+
1 => ''
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def self.round(n, precision = 10.0)
|
18
|
+
(Float(n) * precision).round / precision
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.duration(secs)
|
22
|
+
[secs / 3_600, (secs / 60) % 60, secs % 60].map do |i|
|
23
|
+
i.round.to_s.rjust(2, '0')
|
24
|
+
end.join(":")
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.size(bytes)
|
28
|
+
return '0B' if bytes < 1
|
29
|
+
suffix = BYTE_SUFFIXES.select { |b, _s| bytes >= b }.max
|
30
|
+
|
31
|
+
"#{round(Float(bytes) / suffix[0])}#{suffix[1]}B"
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.progress(progress)
|
35
|
+
"#{Integer(progress)}.#{Integer(progress * 10.0) % 10}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.items(format, items)
|
39
|
+
items.map do |parent, children|
|
40
|
+
[
|
41
|
+
format_line(format['parent'], parent.attr),
|
42
|
+
children&.map do |child|
|
43
|
+
format_line(format['child'], child.attr)
|
44
|
+
end || []
|
45
|
+
]
|
46
|
+
end.to_h
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.pre_format(string, obj)
|
50
|
+
split_chars = []
|
51
|
+
mode = :char
|
52
|
+
padding = 0
|
53
|
+
|
54
|
+
string.chars.reduce('') do |str, char|
|
55
|
+
str +
|
56
|
+
case mode
|
57
|
+
when :escape
|
58
|
+
mode = :char
|
59
|
+
char.to_s
|
60
|
+
when :split
|
61
|
+
mode = :char
|
62
|
+
split_chars << char
|
63
|
+
SPLIT
|
64
|
+
when :attribute
|
65
|
+
mode = :char
|
66
|
+
str = obj.key?(char) ? obj[char].to_s : ''
|
67
|
+
pad =
|
68
|
+
if padding > Unicode::DisplayWidth.of(str)
|
69
|
+
PAD * (padding - Unicode::DisplayWidth.of(str))
|
70
|
+
else
|
71
|
+
''
|
72
|
+
end
|
73
|
+
padding = 0
|
74
|
+
"#{pad}#{str}"
|
75
|
+
when :char
|
76
|
+
case char
|
77
|
+
when '%'
|
78
|
+
mode = :attribute
|
79
|
+
''
|
80
|
+
when '@'
|
81
|
+
mode = :split
|
82
|
+
''
|
83
|
+
when '#'
|
84
|
+
padding += 1
|
85
|
+
''
|
86
|
+
when '\\'
|
87
|
+
mode = :escape
|
88
|
+
''
|
89
|
+
else
|
90
|
+
char.to_s
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end.split(SPLIT).zip(split_chars)
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.format_line(string, obj)
|
97
|
+
pfs = pre_format(string, obj)
|
98
|
+
len = pfs.reduce(0) { |l, s| l + Unicode::DisplayWidth.of(s[0]) } + 1
|
99
|
+
Zashoku.logger.debug("formatting #{pfs}, len: #{len}\n--->#{Util::Term.cols} - #{len} / #{pfs.length - 1}")
|
100
|
+
buffer = pfs.length > 1 ? (Util::Term.cols - len) / (pfs.length - 1) : 0
|
101
|
+
|
102
|
+
buffer -= (Util::Term.cols - len - buffer)
|
103
|
+
|
104
|
+
pfs.reduce('') do |final, seg|
|
105
|
+
buf = pfs.last != seg && buffer.positive? ? (seg[1] || PAD) * buffer : ''
|
106
|
+
"#{final}#{seg[0]}#{buf}"
|
107
|
+
end #[0..Util::Term.cols - 1]
|
108
|
+
|
109
|
+
#f.chars.reduce('') do |m,v|
|
110
|
+
# m + (Unicode::DisplayWidth.of(m + v) <= Util::Term.cols ? v : '')
|
111
|
+
#end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
data/lib/core/item.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zashoku
|
4
|
+
class Item
|
5
|
+
attr_accessor :attr
|
6
|
+
def initialize(attributes)
|
7
|
+
@attributes = attributes
|
8
|
+
keys = []
|
9
|
+
@attr = attributes.map { |k, v|
|
10
|
+
pk = k.chars.map { |c| [c, c.swapcase] }.flatten
|
11
|
+
kc = 0
|
12
|
+
kc += 1 while kc < pk.length && keys.include?(pk[kc])
|
13
|
+
keys << pk[kc]
|
14
|
+
[pk[kc], v]
|
15
|
+
}.to_h
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(m, *args, &block)
|
19
|
+
p @attributes[m.to_s]
|
20
|
+
return @attributes[m.to_s] if @attributes.key?(m.to_s)
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/core/module.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module Zashoku
|
6
|
+
module Module
|
7
|
+
LoadPaths = [
|
8
|
+
File.join(Zashoku::Root, 'lib/core/modules/'),
|
9
|
+
File.join(Zashoku::Root, 'modules/'),
|
10
|
+
File.join(Dir.home, '.config/zashoku/modules/')
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
def self.require_module(mod)
|
14
|
+
mod = mod.downcase
|
15
|
+
LoadPaths.map do |path|
|
16
|
+
mod_path = File.join(path, "#{mod}/lib/#{mod}.rb")
|
17
|
+
next unless File.exist?(mod_path)
|
18
|
+
begin
|
19
|
+
require mod_path
|
20
|
+
rescue LoadError
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end.any?
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.load(modules)
|
27
|
+
require_all(modules).map do |mod|
|
28
|
+
[mod.name.split(':').last.downcase, mod]
|
29
|
+
end.to_h
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.require_all(modules)
|
33
|
+
modules.map do |mod|
|
34
|
+
Zashoku.const_get(mod) if require_module(mod)
|
35
|
+
end.compact
|
36
|
+
end
|
37
|
+
|
38
|
+
###
|
39
|
+
|
40
|
+
def view; end
|
41
|
+
|
42
|
+
def controller; end
|
43
|
+
|
44
|
+
def view_config
|
45
|
+
Util.get_yaml(File.join(self::Root, 'config/view.yml'))
|
46
|
+
end
|
47
|
+
|
48
|
+
def daemon_config
|
49
|
+
Util.get_yaml(File.join(self::Root, 'config/daemon.yml'))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require 'json'
|
5
|
+
require 'thread'
|
6
|
+
|
7
|
+
module Zashoku
|
8
|
+
module Net
|
9
|
+
class Client
|
10
|
+
attr_accessor :callbacks
|
11
|
+
|
12
|
+
def initialize(port, host = 'localhost')
|
13
|
+
@socket = connect(host, port)
|
14
|
+
@alive = true
|
15
|
+
|
16
|
+
@callbacks = []
|
17
|
+
|
18
|
+
@command_queue = Queue.new
|
19
|
+
@result_queue = Queue.new
|
20
|
+
@event_queue = Queue.new
|
21
|
+
|
22
|
+
@command_thread = Thread.new { pump_commands! }
|
23
|
+
@results_thread = Thread.new { pump_results! }
|
24
|
+
@events_thread = Thread.new { dispatch_events! }
|
25
|
+
|
26
|
+
@semaphore = Mutex.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def connect(host, port)
|
30
|
+
TCPSocket.new(host, port)
|
31
|
+
rescue Errno::ECONNREFUSED
|
32
|
+
puts "error: could not connect to #{host}:#{port}"
|
33
|
+
exit
|
34
|
+
end
|
35
|
+
|
36
|
+
def alive?
|
37
|
+
@alive
|
38
|
+
end
|
39
|
+
|
40
|
+
def lost_connection
|
41
|
+
@alive = false
|
42
|
+
Zashoku.logger.debug('lost connection')
|
43
|
+
Zashoku.alert('no connection')
|
44
|
+
Thread.exit
|
45
|
+
end
|
46
|
+
|
47
|
+
def disconnect
|
48
|
+
command('disconnect')
|
49
|
+
@command_thread.exit
|
50
|
+
@results_thread.exit
|
51
|
+
@events_thread.exit
|
52
|
+
@alive = false
|
53
|
+
end
|
54
|
+
|
55
|
+
def command(msg, args = {})
|
56
|
+
return unless alive?
|
57
|
+
@semaphore.synchronize {
|
58
|
+
id = Random.rand
|
59
|
+
|
60
|
+
payload = { 'msg' => msg }.merge(args)
|
61
|
+
Zashoku.logger.debug("client.command #{id} #{payload}")
|
62
|
+
@command_queue << JSON.generate(payload)
|
63
|
+
|
64
|
+
result = @result_queue.pop
|
65
|
+
result.keys.length == 1 ? result['response'] : result
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def pump_commands!
|
70
|
+
loop do
|
71
|
+
begin
|
72
|
+
@socket.puts(@command_queue.pop)
|
73
|
+
rescue
|
74
|
+
lost_connection
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def pump_results!
|
80
|
+
loop do
|
81
|
+
distribute_results!
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def distribute_results!
|
86
|
+
response = JSON.parse(@socket.readline)
|
87
|
+
Zashoku.logger.debug("distributing #{response.class} #{response.length}")
|
88
|
+
if response['event']
|
89
|
+
@event_queue << response
|
90
|
+
else
|
91
|
+
@result_queue << response
|
92
|
+
end
|
93
|
+
rescue
|
94
|
+
lost_connection
|
95
|
+
end
|
96
|
+
|
97
|
+
def dispatch_events!
|
98
|
+
loop do
|
99
|
+
event = @event_queue.pop
|
100
|
+
|
101
|
+
callbacks.each do |callback|
|
102
|
+
Thread.new do
|
103
|
+
callback.call event
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def get_items(_mod)
|
110
|
+
{}
|
111
|
+
# Util.decode_object(send("items #{mod}"))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|