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