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
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require 'json'
|
5
|
+
require 'thread'
|
6
|
+
|
7
|
+
module Zashoku
|
8
|
+
module Net
|
9
|
+
class Server
|
10
|
+
attr_accessor :clients, :handler
|
11
|
+
|
12
|
+
def initialize(port)
|
13
|
+
@clients = []
|
14
|
+
@client_queue = {}
|
15
|
+
@semaphore = Mutex.new
|
16
|
+
@handler = ->(message) {}
|
17
|
+
@server =
|
18
|
+
begin
|
19
|
+
TCPServer.new(port)
|
20
|
+
rescue Errno::EADDRINUSE
|
21
|
+
raise "error, port #{port} is busy"
|
22
|
+
end
|
23
|
+
@server_thread = Thread.new { serve_clients! }
|
24
|
+
end
|
25
|
+
|
26
|
+
def exit
|
27
|
+
@server_thread.exit
|
28
|
+
end
|
29
|
+
|
30
|
+
def event(e)
|
31
|
+
clients.each do |client|
|
32
|
+
client.puts JSON.generate(e)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def handle(client)
|
37
|
+
loop do
|
38
|
+
message =
|
39
|
+
begin
|
40
|
+
JSON.parse(client.readline.chomp)
|
41
|
+
rescue EOFError
|
42
|
+
{ 'msg' => 'disconnect' }
|
43
|
+
end
|
44
|
+
begin
|
45
|
+
response = @handler.call(message)
|
46
|
+
client.puts JSON.generate(response: response)
|
47
|
+
rescue Errno::EPIPE
|
48
|
+
break
|
49
|
+
else
|
50
|
+
break unless response
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@clients.delete(client)
|
54
|
+
@client_queue.delete(client)
|
55
|
+
client.close
|
56
|
+
end
|
57
|
+
|
58
|
+
def serve_clients!
|
59
|
+
Thread.abort_on_exception = true
|
60
|
+
loop do
|
61
|
+
Thread.start(@server.accept) do |client|
|
62
|
+
@clients << client
|
63
|
+
@client_queue[client] = Queue.new
|
64
|
+
handle(client)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/core/options.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
class Options
|
6
|
+
def self.parse(args)
|
7
|
+
options = {
|
8
|
+
daemon: false,
|
9
|
+
generate: false,
|
10
|
+
template: nil,
|
11
|
+
generate_name: nil
|
12
|
+
}
|
13
|
+
|
14
|
+
parser = OptionParser.new do |opts|
|
15
|
+
opts.banner = 'Usage: zashoku [options]'
|
16
|
+
|
17
|
+
opts.on('-d', '--daemon', 'pass commands to the daemon') do |d|
|
18
|
+
options[:daemon] = d
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on('-g TEMPLATE', '--generate TEMPLATE', 'generate a template') do |template|
|
22
|
+
options[:template] = template
|
23
|
+
options[:generate] = true
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
27
|
+
puts opts
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on_tail('--version', 'Show version') do
|
32
|
+
puts Zashoku::Version.join('.')
|
33
|
+
exit
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
parser.parse!(args)
|
38
|
+
|
39
|
+
options[:generate_name] = args.pop if options[:template]
|
40
|
+
|
41
|
+
options
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# watch for changes in a folder's existance
|
2
|
+
|
3
|
+
module Zashoku
|
4
|
+
module Util
|
5
|
+
module FolderListen
|
6
|
+
|
7
|
+
def self.to(dirs, &block)
|
8
|
+
Listener.new(dirs, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
##
|
12
|
+
## @brief Similar to Listen gem but uses polling and is only intended
|
13
|
+
## to be used on a small number of files. the Listen gem
|
14
|
+
## wouldn't support watching an individual FOLDER to see if
|
15
|
+
## the folder went away so this is that!
|
16
|
+
##
|
17
|
+
class Listener
|
18
|
+
def initialize(dirs, &block)
|
19
|
+
@dirs = dirs
|
20
|
+
@block = block
|
21
|
+
@sleep_duration = 1
|
22
|
+
@dir_hash = dir_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
def dir_hash
|
26
|
+
@dirs.map { |dir| [dir, File.exist?(dir)]}.to_h
|
27
|
+
end
|
28
|
+
|
29
|
+
def changed?
|
30
|
+
@dir_hash != dir_hash
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop
|
34
|
+
@listen_thread.exit if @listen_thread
|
35
|
+
end
|
36
|
+
|
37
|
+
def start
|
38
|
+
Thread.abort_on_exception = true
|
39
|
+
@listen_thread = Thread.new do
|
40
|
+
while true
|
41
|
+
if changed?
|
42
|
+
@block.call
|
43
|
+
@dir_hash = dir_hash
|
44
|
+
end
|
45
|
+
sleep 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zashoku
|
4
|
+
module Util
|
5
|
+
module Term
|
6
|
+
{
|
7
|
+
save: `tput smcup`,
|
8
|
+
restore: `tput rmcup`,
|
9
|
+
hide_cursor: `tput civis`,
|
10
|
+
show_cursor: `tput cnorm`,
|
11
|
+
clear_line: "\e[2K",
|
12
|
+
bold: `tput bold`,
|
13
|
+
nobold: `tput sgr0`,
|
14
|
+
clear: "\033[2J"
|
15
|
+
}.each do |command, code|
|
16
|
+
define_singleton_method(command) do
|
17
|
+
print code
|
18
|
+
self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.echo_off
|
23
|
+
`stty -echo`
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.echo_on
|
28
|
+
`stty echo`
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.cols
|
33
|
+
HighLine::SystemExtensions.terminal_size[0]
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.rows
|
37
|
+
HighLine::SystemExtensions.terminal_size[1]
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_key
|
41
|
+
STDIN.getch
|
42
|
+
.gsub("\r", 'enter')
|
43
|
+
.gsub(' ', 'space')
|
44
|
+
.gsub('A', 'up')
|
45
|
+
.gsub('B', 'down')
|
46
|
+
.gsub('C', 'right')
|
47
|
+
.gsub('D', 'left')
|
48
|
+
.gsub("\e", 'skip')
|
49
|
+
.gsub('[', 'skip')
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.reset
|
53
|
+
restore.show_cursor.echo_on
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.ini
|
57
|
+
save.hide_cursor.echo_off
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'readline'
|
5
|
+
|
6
|
+
module Zashoku
|
7
|
+
module Util
|
8
|
+
def self.readline(prompt = '', default = '')
|
9
|
+
Term.bold.echo_on
|
10
|
+
print "\033[" + String(Term.rows) + ';1H'
|
11
|
+
Term.show_cursor
|
12
|
+
Readline.completion_append_character = ''
|
13
|
+
Readline.pre_input_hook = lambda do
|
14
|
+
Readline.insert_text default.to_s
|
15
|
+
Readline.redisplay
|
16
|
+
Readline.pre_input_hook = nil
|
17
|
+
end
|
18
|
+
input = Readline.readline(prompt, false)
|
19
|
+
Term.hide_cursor.nobold.echo_off.clear
|
20
|
+
input
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.error_message(message)
|
24
|
+
# use statusline to show error messages
|
25
|
+
print "\e[#{Term.rows - 1};1H\e[31m #{message}\e[K"
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.encode_object(d)
|
29
|
+
Base64.strict_encode64(Marshal.dump(d))
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.decode_object(e)
|
33
|
+
Marshal.load(Base64.decode64(e))
|
34
|
+
rescue ArgumentError
|
35
|
+
{}
|
36
|
+
rescue TypeError
|
37
|
+
{}
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.get_yaml(file)
|
41
|
+
YAML.safe_load(File.open(file, 'r', &:read)) || {}
|
42
|
+
rescue Errno::ENOENT
|
43
|
+
puts 'failed'
|
44
|
+
{}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/core/view.rb
ADDED
@@ -0,0 +1,268 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# require 'benchmark'
|
4
|
+
|
5
|
+
# require_relative 'color'
|
6
|
+
# require_relative '../util/util'
|
7
|
+
|
8
|
+
module Zashoku
|
9
|
+
class View
|
10
|
+
include Zashoku::Util
|
11
|
+
include Observable
|
12
|
+
|
13
|
+
attr_accessor :attributes, :format, :items_formatted
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@attributes = {}
|
17
|
+
|
18
|
+
@children = true
|
19
|
+
@expanded = -1
|
20
|
+
@selected = 0
|
21
|
+
|
22
|
+
@items_formatted = {}
|
23
|
+
|
24
|
+
@old_rows = Term.rows
|
25
|
+
|
26
|
+
# @win = Curses::Window.new(0,0,0,0)
|
27
|
+
Zashoku.logger.debug('refreshing')
|
28
|
+
refresh
|
29
|
+
|
30
|
+
dirty_all_lines
|
31
|
+
@view = [0, Term.rows - 3]
|
32
|
+
end
|
33
|
+
|
34
|
+
def dirty_all_lines
|
35
|
+
@dirty_lines = 0.step(get_len).to_a
|
36
|
+
end
|
37
|
+
|
38
|
+
def unpause; end
|
39
|
+
|
40
|
+
def pause; end
|
41
|
+
|
42
|
+
def changed!
|
43
|
+
fix_cursor
|
44
|
+
changed
|
45
|
+
notify_observers
|
46
|
+
end
|
47
|
+
|
48
|
+
def auto_update(s: :start)
|
49
|
+
case s
|
50
|
+
when :start
|
51
|
+
@update_thread = Thread.new do
|
52
|
+
loop do
|
53
|
+
refresh
|
54
|
+
changed!
|
55
|
+
sleep 1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
when :stop
|
59
|
+
@update_thread.exit if @update_thread
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def change_screen_size(redraw: true)
|
64
|
+
@items_formatted = {}
|
65
|
+
refresh_formats
|
66
|
+
|
67
|
+
diff = Term.rows - @old_rows
|
68
|
+
# @view[0] += diff
|
69
|
+
@view[1] += diff
|
70
|
+
|
71
|
+
@old_rows = Term.rows
|
72
|
+
adjust_view
|
73
|
+
if redraw
|
74
|
+
dirty_all_lines
|
75
|
+
draw
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def refresh
|
80
|
+
@items = refresh_items
|
81
|
+
refresh_attributes
|
82
|
+
refresh_formats
|
83
|
+
dirty_all_lines
|
84
|
+
@expanded = -1 if @children && @items.values[@expanded].nil?
|
85
|
+
changed!
|
86
|
+
end
|
87
|
+
|
88
|
+
def expand(i)
|
89
|
+
return if i > @items.length
|
90
|
+
oexpanded = @expanded
|
91
|
+
|
92
|
+
if @expanded == i
|
93
|
+
@selected = @expanded
|
94
|
+
@expanded = -1
|
95
|
+
else
|
96
|
+
@selected = i
|
97
|
+
@expanded = i
|
98
|
+
end
|
99
|
+
#adjust_view
|
100
|
+
|
101
|
+
arr = [@expanded, oexpanded] - [-1]
|
102
|
+
@dirty_lines = arr.min.step(@view[1]).to_a
|
103
|
+
changed!
|
104
|
+
end
|
105
|
+
|
106
|
+
def resolve_selected(resolve_me = @selected)
|
107
|
+
in_expanded = false
|
108
|
+
inside = 0
|
109
|
+
outside = resolve_me
|
110
|
+
if @expanded > -1
|
111
|
+
if (resolve_me > @expanded) && (resolve_me <= (@items.values[@expanded].length + @expanded))
|
112
|
+
outside = @expanded
|
113
|
+
inside = resolve_me - @expanded - 1
|
114
|
+
in_expanded = true
|
115
|
+
elsif resolve_me > @expanded
|
116
|
+
outside = resolve_me - @items.values[@expanded].length
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
{
|
121
|
+
'out' => outside,
|
122
|
+
'in' => inside,
|
123
|
+
'in_expanded' => in_expanded
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
def selected_group
|
128
|
+
resolve_selected['out']
|
129
|
+
end
|
130
|
+
|
131
|
+
def selected_item
|
132
|
+
rs = resolve_selected
|
133
|
+
if rs['in_expanded']
|
134
|
+
@items.values[rs["out"]][rs["in"]]
|
135
|
+
else
|
136
|
+
@items.keys[rs['out']]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def move_cursor(dir)
|
141
|
+
os = @selected
|
142
|
+
@selected -= 1 if dir == 'up'
|
143
|
+
@selected += 1 if dir == 'down'
|
144
|
+
@dirty_lines += [os, @selected]
|
145
|
+
|
146
|
+
adjust_view
|
147
|
+
fix_cursor
|
148
|
+
changed
|
149
|
+
notify_observers
|
150
|
+
end
|
151
|
+
|
152
|
+
def fix_cursor
|
153
|
+
@selected = 0 if @selected < 0
|
154
|
+
@selected = get_len - 1 if @selected >= get_len
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_len
|
158
|
+
len = @items.length
|
159
|
+
len += @items.values[@expanded].length if @expanded >= 0
|
160
|
+
len
|
161
|
+
end
|
162
|
+
|
163
|
+
def color(index)
|
164
|
+
if index == @selected
|
165
|
+
Zashoku.conf.get(%w[color selected])
|
166
|
+
else
|
167
|
+
Zashoku.conf.get(%w[color secondary])
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def adjust_view
|
172
|
+
buffer = 2
|
173
|
+
bottom = Term.rows - 3
|
174
|
+
|
175
|
+
ov = @view.clone
|
176
|
+
diff = 0
|
177
|
+
if @selected < @view[0] + buffer # and @selected > buffer
|
178
|
+
while @selected < @view[0] + buffer
|
179
|
+
@view[0] -= 1
|
180
|
+
@view[1] -= 1
|
181
|
+
end
|
182
|
+
elsif (@selected > @view[1] - (buffer + 1)) && (@selected < get_len - buffer)
|
183
|
+
while (@selected > @view[1] - (buffer + 1)) && (@selected < get_len - buffer)
|
184
|
+
@view[0] += 1
|
185
|
+
@view[1] += 1
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
diff = 0 - @view[0] if @view[0].negative?
|
190
|
+
diff = bottom - @view[1] if @view[1] < bottom
|
191
|
+
|
192
|
+
|
193
|
+
@view[0] += diff
|
194
|
+
@view[1] += diff
|
195
|
+
|
196
|
+
dirty_all_lines unless ov == @view
|
197
|
+
end
|
198
|
+
|
199
|
+
def refresh_formats
|
200
|
+
@cl = {
|
201
|
+
's' => Zashoku.conf.get(%w[color selected]),
|
202
|
+
'1' => Zashoku.conf.get(%w[color primary]),
|
203
|
+
'2' => Zashoku.conf.get(%w[color secondary]),
|
204
|
+
'm' => Zashoku.conf.get(%w[color main])
|
205
|
+
}
|
206
|
+
|
207
|
+
@title_formatted = Zashoku::Formatter.format_line(
|
208
|
+
get_format['title'],
|
209
|
+
@attributes
|
210
|
+
)
|
211
|
+
|
212
|
+
@items_formatted = Zashoku::Formatter.items(get_format, @items)
|
213
|
+
changed
|
214
|
+
notify_observers
|
215
|
+
end
|
216
|
+
|
217
|
+
def draw_line(line_on)
|
218
|
+
in_outer_region = line_on < get_len
|
219
|
+
rs = resolve_selected(line_on)
|
220
|
+
line = line_on - @view[0] + 2
|
221
|
+
|
222
|
+
lc =
|
223
|
+
if line_on == @selected
|
224
|
+
@cl['s']
|
225
|
+
elsif rs['in_expanded']
|
226
|
+
@cl['2']
|
227
|
+
else
|
228
|
+
@cl['1']
|
229
|
+
end
|
230
|
+
|
231
|
+
lo = "\e[#{line};1H#{lc}"
|
232
|
+
|
233
|
+
item =
|
234
|
+
if rs['in_expanded'] && @children
|
235
|
+
"#{@items_formatted.values[@expanded][rs['in']]}"
|
236
|
+
elsif in_outer_region
|
237
|
+
"#{@items_formatted.keys[rs['out']]}"
|
238
|
+
else
|
239
|
+
''
|
240
|
+
end
|
241
|
+
|
242
|
+
print "#{lo}#{item}\e[K" #.tr(' ', '#')
|
243
|
+
end
|
244
|
+
|
245
|
+
def draw
|
246
|
+
# set the view
|
247
|
+
line_on = @view[0]
|
248
|
+
lines_to_draw = @view[1]
|
249
|
+
|
250
|
+
# print the menu title
|
251
|
+
lo = "\e[1;1H"
|
252
|
+
print lo + @cl['m'] + @title_formatted + "\e[K"
|
253
|
+
|
254
|
+
while line_on < lines_to_draw
|
255
|
+
draw_line(line_on) if @dirty_lines.include?(line_on)
|
256
|
+
line_on += 1
|
257
|
+
end
|
258
|
+
|
259
|
+
@dirty_lines.clear
|
260
|
+
end
|
261
|
+
|
262
|
+
def refresh_attributes; end
|
263
|
+
|
264
|
+
def refresh_items; [] end
|
265
|
+
|
266
|
+
def get_format; '' end
|
267
|
+
end
|
268
|
+
end
|