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