zashoku 1.0.0

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