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
@@ -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
|