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.
@@ -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
@@ -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/)
@@ -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)
@@ -0,0 +1,8 @@
1
+ ---
2
+ log_file: '$conf_dir/aw.log'
3
+ conf_dir: '~/.config/zashoku'
4
+
5
+ modules:
6
+ disabled: []
7
+ enabled:
8
+ - Bandcamp
@@ -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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'observer'
4
+
5
+ module Zashoku
6
+ class Controller
7
+ # include Zashoku::Util
8
+ include Observable
9
+ def items
10
+ {}
11
+ end
12
+ end
13
+ 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
+
@@ -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
@@ -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