termcontroller 0.1.0 → 0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1fad37d234332ef83aa87356c2ce2cb16f0625bc5e53e37db193e2d0f0343fd
4
- data.tar.gz: cad01ac6503432df902fe0c1d204c6698d13088376e23a418c1d7ca5d5709cab
3
+ metadata.gz: ccc884e481a6ba710cb7edbd7edc1b05c20ed49e7f8e81cb0288813472006466
4
+ data.tar.gz: 6738a465db9c70c9d4d67f1626957de5209fbfbee95557cf9115f8734ff970e9
5
5
  SHA512:
6
- metadata.gz: b0d4ba2609f21e798a4ea6798d02f94f7dba23f6011d015568206c219dbccedb8b994c6d66e30fc4b25c6e4ab9c56b7455f12aa1dec856ba5dac4eddcae77e4e
7
- data.tar.gz: 7d4b1b9a57cc196538851e537403e2ff8bafbfeab55d22ad3fccbf70ad27c4df22bc0ac99701c1ffa587af02baeb62f2ac088527bd1655a26f39d7cc9cdee01d
6
+ metadata.gz: d6c8d516c1a855d46833a6ead5e0a28ab2a682b1ce18a8039d301933acb2811d627b65c62b2ecbd0298c8379af98a7502d86a542342fc3c48911f996ab57cfba
7
+ data.tar.gz: 5f313f8f34231eaa7715c0df26e7abd470ee770af60cdbffb4c4dc7f1f8c08132df5e1f4558b713e26a9be307e5f496c51cfd7817efb12c6ac557dbe87d8e5e5
data/.gitignore CHANGED
@@ -6,6 +6,6 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
-
9
+ *~
10
10
  # rspec failure tracking
11
11
  .rspec_status
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Termcontroller
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/termcontroller`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ A very basic Controller (in the MVC sense) for Ruby terminal
4
+ applications. This was pulled out of my text editor,
5
+ [Re](https://github.com/vidarh/re).
6
6
 
7
7
  ## Installation
8
8
 
@@ -26,15 +26,23 @@ TODO: Write usage instructions here
26
26
 
27
27
  ## Development
28
28
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
29
+ After checking out the repo, run `bin/setup` to install dependencies.
30
+ Then, run `rake spec` to run the tests. You can also run `bin/console`
31
+ for an interactive prompt that will allow you to experiment.
30
32
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
33
+ To install this gem onto your local machine, run `bundle exec rake
34
+ install`. To release a new version, update the version number in
35
+ `version.rb`, and then run `bundle exec rake release`, which will create
36
+ a git tag for the version, push git commits and tags, and push the `.gem`
37
+ file to [rubygems.org](https://rubygems.org).
32
38
 
33
39
  ## Contributing
34
40
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/termcontroller.
41
+ Bug reports and pull requests are welcome on GitHub at
42
+ https://github.com/vidarh/termcontroller.
36
43
 
37
44
 
38
45
  ## License
39
46
 
40
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
47
+ The gem is available as open source under the terms of the [MIT
48
+ License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,258 @@
1
+ # coding: utf-8
2
+
3
+ require 'io/console'
4
+
5
+ # # Controller
6
+ #
7
+ # This is way too ill defined. The purpose is to be able to have a
8
+ # separate thread handle the keyboard processing asynchronously,
9
+ # reading from the input, and for an application to then be able to
10
+ # call into it to read a command, or to have the controller dispatch
11
+ # the command directly to a specified target object based on the
12
+ # key bindings.
13
+ #
14
+ # It works well enough to e.g. allow temporarily pausing the processing
15
+ # and then dispatching "binding.pry" and re-enable the processing when
16
+ # it returns.
17
+ #
18
+ # FIXME: Split it into a separate gem.
19
+ #
20
+ # FIXME: Should probably treat this as a singleton.
21
+ #
22
+ #
23
+
24
+ module Termcontroller
25
+ class Controller
26
+
27
+ attr_reader :lastcmd,:lastkey,:lastchar
28
+ attr_accessor :mode
29
+
30
+ @@con = IO.console
31
+
32
+ # Pause *any* Controller instance
33
+ @@pause = false
34
+ def self.pause!
35
+ old = @@pause
36
+ @@pause = true
37
+ @@con.cooked do
38
+ yield
39
+ end
40
+ ensure
41
+ @@pause = old
42
+ end
43
+
44
+ def paused?
45
+ @mode == :pause || @@pause
46
+ end
47
+
48
+ def initialize(target, keybindings)
49
+ @target = target
50
+ @keybindings = keybindings
51
+ @buf = ""
52
+ @commands = []
53
+ @mode = :cooked
54
+
55
+ @kb = KeyboardMap.new
56
+ @@con = @con = IO.console
57
+ raise if !@con
58
+
59
+ at_exit do
60
+ cleanup
61
+ end
62
+
63
+ trap("CONT") { resume }
64
+
65
+ @t = Thread.new { readloop }
66
+ end
67
+
68
+ def setup
69
+ STDOUT.print "\e[?2004h" # Enable bracketed paste
70
+ STDOUT.print "\e[?1000h" # Enable mouse reporting
71
+ STDOUT.print "\e[?1006h" # Enable extended reporting
72
+ end
73
+
74
+ def cleanup
75
+ STDOUT.print "\e[?2004l" #Disable bracketed paste
76
+ STDOUT.print "\e[?1000l" #Disable mouse reporting
77
+ end
78
+
79
+ def readloop
80
+ loop do
81
+ if paused?
82
+ sleep(0.05)
83
+ elsif @mode == :cooked
84
+ read_input
85
+ else
86
+ fill_buf
87
+ end
88
+ end
89
+ end
90
+
91
+ def pause
92
+ old = @mode
93
+ @mode = :pause
94
+ sleep(0.1)
95
+ IO.console.cooked!
96
+ yield
97
+ rescue Interrupt
98
+ ensure
99
+ @mode = old
100
+ end
101
+
102
+ def fill_buf(timeout=0.1)
103
+ if paused?
104
+ sleep(0.1)
105
+ Thread.pass
106
+ return
107
+ end
108
+ @con.raw!
109
+ return if !IO.select([$stdin],nil,nil,0.1)
110
+ str = $stdin.read_nonblock(4096)
111
+ str.force_encoding("utf-8")
112
+ @buf << str
113
+ rescue IO::WaitReadable
114
+ end
115
+
116
+ def getc(timeout=0.1)
117
+ if !paused?
118
+ while @buf.empty?
119
+ fill_buf
120
+ end
121
+ @buf.slice!(0) if !paused? && @mode == :cooked
122
+ else
123
+ sleep(0.1)
124
+ Thread.pass
125
+ return nil
126
+ end
127
+ rescue Interrupt
128
+ end
129
+
130
+ def raw
131
+ @mode = :raw
132
+ yield
133
+ rescue Interrupt
134
+ ensure
135
+ @mode = :cooked
136
+ end
137
+
138
+ def read_char
139
+ sleep(0.01) if @buf.empty?
140
+ @buf.slice!(0)
141
+ rescue Interrupt
142
+ end
143
+
144
+ def get_command
145
+ map = @keybindings
146
+ loop do
147
+ c = nil
148
+ char = getc
149
+ return nil if !char
150
+
151
+ c1 = Array(@kb.call(char)).first
152
+ c = map[c1.to_sym] if c1
153
+
154
+ if c.nil? && c1.kind_of?(String)
155
+ return [:insert_char, c1]
156
+ end
157
+
158
+ if c.nil?
159
+ if c1
160
+ @lastchar = c1.to_sym
161
+ args = c1.respond_to?(:args) ? c1.args : []
162
+ return Array(c1.to_sym).concat(args || [])
163
+ else
164
+ @lastchar = char.inspect
165
+ return nil
166
+ end
167
+ end
168
+
169
+ if c.kind_of?(Hash)
170
+ map = c
171
+ else
172
+ @lastchar = c1.to_sym.to_s.split("_").join(" ")
173
+ @lastchar += " (#{c.to_s})" if c.to_s != @lastchar
174
+ return c
175
+ end
176
+ end
177
+ end
178
+
179
+ def do_command(c)
180
+ return nil if !c
181
+ if @target.respond_to?(Array(c).first)
182
+ @lastcmd = c
183
+ @target.instance_eval { send(*Array(c)) }
184
+ else
185
+ @lastchar = "Unbound: #{Array(c).first.inspect}"
186
+ end
187
+ end
188
+
189
+ def read_input
190
+ c = get_command
191
+ if !c
192
+ Thread.pass
193
+ return
194
+ end
195
+ if Array(c).first == :insert_char
196
+ # FIXME: Attempt to combine multiple :insert_char into one.
197
+ #Probably should happen in get_command
198
+ #while (c2 = get_command) && Array(c2).first == :insert_char
199
+ # c.last << c2.last
200
+ #end
201
+ #@commands << c
202
+ #c = c2
203
+ #return nil if !c
204
+ end
205
+ @commands << c
206
+ Thread.pass
207
+ end
208
+
209
+ def next_command
210
+ if @commands.empty?
211
+ sleep(0.001)
212
+ Thread.pass
213
+ end
214
+ @commands.shift
215
+ end
216
+
217
+ def handle_input(prefix="",timeout=0.1)
218
+ if c = next_command
219
+ do_command(c)
220
+ end
221
+ return c
222
+ end
223
+
224
+ def pry(e=nil)
225
+ pause do
226
+ cleanup
227
+ puts ANSI.cls
228
+ binding.pry
229
+ end
230
+ setup
231
+ end
232
+
233
+ def suspend
234
+ pause do
235
+ yield if block_given?
236
+ Process.kill("STOP", 0)
237
+ end
238
+ end
239
+
240
+ def resume
241
+ @mode = :cooked
242
+ setup
243
+ end
244
+ end
245
+ end
246
+
247
+ at_exit do
248
+ #
249
+ # FIXME: This is a workaround for Controller putting
250
+ # STDIN into nonblocking mode and not cleaning up, which
251
+ # causes all kind of problems with a variety of tools (more,
252
+ # docker etc.) which expect it to be blocking.
253
+ Termcontroller::Controller.pause! do
254
+ stdin_flags = STDIN.fcntl(Fcntl::F_GETFL)
255
+ STDIN.fcntl(Fcntl::F_SETFL, stdin_flags & ~Fcntl::O_NONBLOCK) #
256
+ IO.console.cooked!
257
+ end
258
+ end
@@ -1,3 +1,3 @@
1
1
  module Termcontroller
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2"
3
3
  end
@@ -1,4 +1,5 @@
1
1
  require "termcontroller/version"
2
+ require "termcontroller/controller"
2
3
 
3
4
  module Termcontroller
4
5
  class Error < StandardError; end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: termcontroller
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: '0.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vidar Hokstad
@@ -27,6 +27,7 @@ files:
27
27
  - bin/console
28
28
  - bin/setup
29
29
  - lib/termcontroller.rb
30
+ - lib/termcontroller/controller.rb
30
31
  - lib/termcontroller/version.rb
31
32
  - termcontroller.gemspec
32
33
  homepage: https://github.com/vidarh/termcontroller