luck 0.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.
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ .yardoc
23
+ doc
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Daniel Danopia
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,21 @@
1
+ Luck
2
+ ====
3
+
4
+ Luck is a ncurses-like CLI UI system, written in pure Ruby.
5
+
6
+ The origin of the name is that it is the opposite of "curses".
7
+
8
+ Luck was developed for [Remora](http://github.com/danopia/remora) but
9
+ any application can use it now that I packaged it as a gem.
10
+
11
+
12
+ Usage
13
+ -----
14
+
15
+ TODO
16
+
17
+
18
+ Copyright
19
+ ---------
20
+
21
+ Copyright (c) 2010 Daniel Danopia. See {file:LICENSE} for details.
data/Rakefile ADDED
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "luck"
8
+ gem.summary = %Q{Pure-ruby CLI UI system}
9
+ gem.description = %Q{Pure-ruby CLI UI system. Includes multiple panes in a display and multiple controls in a pane. luck is lucky (as opposed to ncurses being cursed)}
10
+ gem.email = "me.github@danopia.net"
11
+ gem.homepage = "http://github.com/danopia/luck"
12
+ gem.authors = ["Daniel Danopia"]
13
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ #~ require 'rake/testtask'
22
+ #~ Rake::TestTask.new(:test) do |test|
23
+ #~ test.libs << 'lib' << 'test'
24
+ #~ test.pattern = 'test/**/*_test.rb'
25
+ #~ test.verbose = true
26
+ #~ end
27
+
28
+ #~ begin
29
+ #~ require 'rcov/rcovtask'
30
+ #~ Rcov::RcovTask.new do |test|
31
+ #~ test.libs << 'test'
32
+ #~ test.pattern = 'test/**/*_test.rb'
33
+ #~ test.verbose = true
34
+ #~ end
35
+ #~ rescue LoadError
36
+ #~ task :rcov do
37
+ #~ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ #~ end
39
+ #~ end
40
+
41
+ task :test => :check_dependencies
42
+
43
+ task :default => :test
44
+
45
+ begin
46
+ require 'yard'
47
+ require 'yard/rake/yardoc_task'
48
+ YARD::Rake::YardocTask.new do |t|
49
+ extra_files = %w(LICENSE)
50
+ t.files = ['lib/**/*.rb']
51
+ t.options = ["--files=#{extra_files.join(',')}"]
52
+ end
53
+ rescue LoadError
54
+ task :yard do
55
+ abort "YARD is not available. In order to run yard, you must: sudo gem install yard"
56
+ end
57
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,35 @@
1
+ module Luck
2
+ class Control
3
+ attr_accessor :pane, :display, :x1, :y1, :x2, :y2
4
+
5
+ def initialize pane, x1, y1, x2, y2, &blck
6
+ @pane = pane
7
+ @display = pane.display
8
+ @x1, @y1 = x1, y1
9
+ @x2, @y2 = x2, y2
10
+
11
+ instance_eval &blck if blck
12
+ end
13
+
14
+ def x1
15
+ @pane.x1 + ((@x1 < 0) ? (@pane.width + @x1) : @x1)
16
+ end
17
+ def y1
18
+ @pane.y1 + ((@y1 < 0) ? (@pane.height + @y1) : @y1)
19
+ end
20
+
21
+ def x2
22
+ @pane.x1 + ((@x2 < 0) ? (@pane.width + @x2 + 1) : @x2)
23
+ end
24
+ def y2
25
+ @pane.y1 + ((@y2 < 0) ? (@pane.height + @y2 + 1) : @y2)
26
+ end
27
+
28
+ def width
29
+ x2 - x1
30
+ end
31
+ def height
32
+ y2 - y1
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,160 @@
1
+ module Luck
2
+ class Display
3
+ attr_accessor :width, :height, :client, :panes, :buffer, :dirty, :active_control
4
+
5
+ def initialize client
6
+ @client = client
7
+ @panes = {}
8
+ @dirty = true
9
+ prepare_modes
10
+
11
+ size = terminal_size
12
+ @width = size[1]
13
+ @height = size[0]
14
+ end
15
+
16
+ def pane name, *args, &blck
17
+ @panes[name] = Pane.new(self, *args, &blck)
18
+ end
19
+
20
+ def place row, col, text
21
+ print "\e[#{row.to_i};#{col.to_i}H#{text}"
22
+ end
23
+ def color codes
24
+ "\e[#{codes}m"
25
+ end
26
+
27
+ def cursor=(show)
28
+ print "\e[?25h" if show
29
+ print "\e[?25l" unless show
30
+ $stdout.flush
31
+ end
32
+
33
+ def dirty! pane=nil
34
+ if pane && @dirty.is_a?(Array) && !(@dirty.include?(pane))
35
+ @dirty << pane
36
+ elsif pane && !@dirty
37
+ @dirty = [pane]
38
+ elsif !pane
39
+ @dirty = true
40
+ end
41
+ end
42
+
43
+ def handle
44
+ handle_stdin
45
+
46
+ size = terminal_size
47
+ if @width != size[1] || @height != size[0]
48
+ @width = size[1]
49
+ @height = size[0]
50
+ dirty!
51
+ end
52
+
53
+ return unless @dirty
54
+ redraw @dirty
55
+ @dirty = false
56
+ end
57
+
58
+ def redraw panes=true
59
+ print "\e[H\e[J" if panes == true # clear all and go home
60
+ self.cursor = active_control
61
+
62
+ panes = @panes.keys if panes == true
63
+
64
+ panes.each do |key|
65
+ @panes[key].redraw
66
+ end
67
+ print "\e[u"
68
+
69
+ $stdout.flush
70
+ end
71
+
72
+ def handle_stdin
73
+ $stdin.read_nonblock(1024).each_char do |chr|
74
+ active_control.handle_char chr if active_control
75
+ #~ if chr == "\n"
76
+ #~ next if @buffer.empty?
77
+ #~
78
+ #~ if @buffer.to_i.to_s == @buffer && @results
79
+ #~ index = @buffer.to_i - 1
80
+ #~ next if index < 0 || index > @results.size
81
+ #~
82
+ #~ song = @results[index]
83
+ #~ @client.queue << song
84
+ #~
85
+ #~ unless @client.now_playing
86
+ #~ Thread.new do
87
+ #~ @client.queue.play_radio
88
+ #~ end
89
+ #~ end
90
+ #~
91
+ #~ @buffer = ''
92
+ #~ panes[:main].data[0] = @buffer.empty? ? (@search || '') : ''
93
+ #~ self.cursor = false
94
+ #~ else
95
+ #~ @search = @buffer
96
+ #~ @results = @client.search_songs(@search)['Return']
97
+ #~ panes[:main].data = @results.map do |result|
98
+ #~ "#{(@results.index(result)+1).to_s.rjust 2}) #{result['Name']} - #{result['ArtistName']} - #{result['AlbumName']}"
99
+ #~ end
100
+ #~ panes[:main].data.unshift @search
101
+ #~ panes[:main].title = "Search Results for #{@search}"
102
+ #~
103
+ #~ @buffer = ''
104
+ #~ self.cursor = false
105
+ #~ end
106
+ #~ else
107
+ #~ @buffer << chr
108
+ #~ @buffer.gsub!(/.\177/, '')
109
+ #~ @buffer.gsub!("\177", '')
110
+ #~ panes[:main].data[0] = @buffer.empty? ? (@search || '') : ''
111
+ #~ self.cursor = !(@buffer.empty?)
112
+ #~ end
113
+ end
114
+
115
+ self.cursor = active_control
116
+ $stdout.flush
117
+
118
+ rescue Errno::EAGAIN
119
+ rescue EOFError
120
+ end
121
+
122
+ ######################################
123
+ # this is all copied from cashreg :P #
124
+ ######################################
125
+
126
+ # yay grep
127
+ TIOCGWINSZ = 0x5413
128
+ TCGETS = 0x5401
129
+ TCSETS = 0x5402
130
+ ECHO = 8 # 0000010
131
+ ICANON = 2 # 0000002
132
+
133
+ # thanks google for all of this
134
+ def terminal_size
135
+ rows, cols = 25, 80
136
+ buf = [0, 0, 0, 0].pack("SSSS")
137
+ if $stdout.ioctl(TIOCGWINSZ, buf) >= 0 then
138
+ rows, cols, row_pixels, col_pixels = buf.unpack("SSSS")
139
+ end
140
+ return [rows, cols]
141
+ end
142
+
143
+ # had to convert these from C... fun
144
+ def prepare_modes
145
+ buf = [0, 0, 0, 0, 0, 0, ''].pack("IIIICCA*")
146
+ $stdout.ioctl(TCGETS, buf)
147
+ @old_modes = buf.unpack("IIIICCA*")
148
+ new_modes = @old_modes.clone
149
+ new_modes[3] &= ~ECHO # echo off
150
+ new_modes[3] &= ~ICANON # one char @ a time
151
+ $stdout.ioctl(TCSETS, new_modes.pack("IIIICCA*"))
152
+ self.cursor = false
153
+ end
154
+ def undo_modes # restore previous terminal mode
155
+ $stdout.ioctl(TCSETS, @old_modes.pack("IIIICCA*"))
156
+ print "\e[2J\e[H" # clear all and go home
157
+ self.cursor = true # show the mouse
158
+ end
159
+ end
160
+ end
data/lib/luck/label.rb ADDED
@@ -0,0 +1,46 @@
1
+ module Luck
2
+ class Label < Control
3
+ attr_accessor :alignment, :text
4
+
5
+ def initialize *args
6
+ @alignment = :left
7
+ @text = 'Label'
8
+ super
9
+ end
10
+
11
+ def align direction
12
+ @alignment = direction
13
+ end
14
+
15
+ def align_text text
16
+ case @alignment
17
+ when :center
18
+ text.center width
19
+ when :right
20
+ text.rjust width
21
+ else
22
+ text.ljust width
23
+ end
24
+ end
25
+
26
+ def redraw
27
+ if text.empty?
28
+ @display.place y1, x1, ' ' * width
29
+ return
30
+ end
31
+
32
+ row = y1
33
+ text.each_line do |line|
34
+ line.chomp!
35
+ length = line.size
36
+ offset = 0
37
+ while offset < width || offset == 0
38
+ @display.place row, x1, align_text(line[offset, width])
39
+ row += 1
40
+ offset += width
41
+ return if row >= y2
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,33 @@
1
+ module Luck
2
+ class ListBox < Control
3
+ attr_accessor :data, :numbering, :hanging_indent
4
+
5
+ def initialize *args
6
+ @data = []
7
+ @hanging_indent = 0
8
+ super
9
+ end
10
+
11
+ def number!
12
+ @numbering = true
13
+ @hanging_indent = 4
14
+ end
15
+
16
+ def redraw
17
+ row = y1
18
+ data.first(height).each_with_index do |line, index|
19
+ line = line.to_s
20
+ line = "#{(index + 1).to_s.rjust 2}. #{line}" if @numbering
21
+ length = line.size
22
+ offset = 0
23
+ while offset < length || offset == 0
24
+ @display.place row, x1 + ((offset > 0) ? @hanging_indent : 0), line[offset, width - ((offset > 0) ? @hanging_indent : 0)]
25
+ row += 1
26
+ offset += width - ((offset > 0) ? @hanging_indent : 0)
27
+ break if row >= y2
28
+ end
29
+ break if row >= y2
30
+ end
31
+ end
32
+ end
33
+ end
data/lib/luck/pane.rb ADDED
@@ -0,0 +1,76 @@
1
+ module Luck
2
+ class Pane
3
+ attr_accessor :display, :x1, :y1, :x2, :y2, :title, :controls
4
+
5
+ def initialize display, x1, y1, x2, y2, title, controls={}, &blck
6
+ @display = display
7
+ @x1, @y1 = x1, y1
8
+ @x2, @y2 = x2, y2
9
+ @title, @controls = title, controls
10
+
11
+ instance_eval &blck if blck
12
+ end
13
+
14
+ def control name, type, *args, &blck
15
+ @controls[name] = type.new(self, *args, &blck)
16
+ end
17
+
18
+ def x1
19
+ (@x1 < 0) ? (@display.width + @x1 + 1) : @x1
20
+ end
21
+ def y1
22
+ (@y1 < 0) ? (@display.height + @y1 + 1) : @y1
23
+ end
24
+
25
+ def x2
26
+ (@x2 < 0) ? (@display.width + @x2 + 1) : @x2
27
+ end
28
+ def y2
29
+ (@y2 < 0) ? (@display.height + @y2 + 1) : @y2
30
+ end
31
+
32
+ def width
33
+ x2 - x1
34
+ end
35
+ def height
36
+ y2 - y1
37
+ end
38
+
39
+ def redraw
40
+ draw_frame
41
+ draw_contents
42
+ end
43
+
44
+ def draw_contents
45
+ controls.each_value do |control|
46
+ control.redraw
47
+ end
48
+ end
49
+
50
+ def topbar
51
+ title = " * #{@title} * "
52
+ left = (((width - 1).to_f / 2) - (title.size.to_f / 2)).to_i
53
+
54
+ if title.size >= width
55
+ "#{@display.color '1;34'} #{@title[0, width - 3].center(width - 3)} #{@display.color '0;2'}"
56
+ else
57
+ title_colored = "#{@display.color '1;34'}#{title}#{@display.color '0;2'}"
58
+ ('-' * left) + title_colored + ('-' * (width - 1 - title.size - left))
59
+ end
60
+ end
61
+
62
+ def draw_frame
63
+ bottombar = '-' * (width - 1)
64
+ fillerbar = ' ' * (width - 1)
65
+
66
+ print @display.color('0;2')
67
+ @display.place y1, x1, "+#{topbar}+"
68
+ @display.place y2, x1, "+#{bottombar}+"
69
+
70
+ (y1 + 1).upto y2 - 1 do |row|
71
+ @display.place row, x1, "|#{fillerbar}|"
72
+ end
73
+ print @display.color('0')
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,53 @@
1
+ module Luck
2
+ class ProgressBar < Control
3
+ attr_accessor :maximum, :value, :precap, :bar, :arrow, :blank, :postcap
4
+
5
+ def initialize *args
6
+ @maximum = 1
7
+ @value = 0
8
+ @precap = '['
9
+ @bar = '='
10
+ @arrow = '>'
11
+ @blank = ' '
12
+ @postcap = ']'
13
+ super
14
+ end
15
+
16
+ def percentage
17
+ @value.to_f / @maximum.to_f
18
+ end
19
+
20
+ def template str
21
+ @precap, @bar, @arrow, @blank, @postcap = str.unpack 'aaaaa'
22
+ end
23
+
24
+ def redraw
25
+ @display.place y1, x1, @precap + (@bar * (percentage * (width - @precap.size - @postcap.size)) + @arrow).ljust(width - @precap.size - @postcap.size, @blank) + @postcap
26
+ end
27
+ end
28
+
29
+ class DoubleProgressBar < ProgressBar
30
+ attr_accessor :maximum2, :value2, :bar2, :arrow2
31
+
32
+ def initialize *args
33
+ @maximum2 = 1
34
+ @value2 = 0
35
+ @bar2 = '-'
36
+ @arrow2 = '>'
37
+ super
38
+ end
39
+
40
+ def percentage2
41
+ @value2.to_f / @maximum2.to_f
42
+ end
43
+
44
+ def template str
45
+ @precap, @bar2, @arrow2, @bar, @arrow, @blank, @postcap = str.unpack 'aaaaaaa'
46
+ end
47
+
48
+ def redraw
49
+ super
50
+ @display.place y1, x1, @precap + (@bar2 * (percentage2 * (width - @precap.size - @postcap.size)) + @arrow2)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,46 @@
1
+ module Luck
2
+ class TextBox < Label
3
+ attr_accessor :multiline, :handler, :label
4
+
5
+ def initialize *args
6
+ @label = ''
7
+ @text = 'Input box'
8
+ super
9
+ end
10
+
11
+ def redraw
12
+ super
13
+
14
+ case @alignment
15
+ when :center
16
+ text.center width
17
+ when :right
18
+ text.rjust width
19
+ else
20
+ @display.place y1, x1 + text.size, "\e[s"
21
+ end
22
+ #print "\e[s" # save
23
+ end
24
+
25
+ def on_submit &blck
26
+ @handler = blck
27
+ end
28
+
29
+ def text
30
+ return @text if @label.empty?
31
+ "#{@label}: #{@text}"
32
+ end
33
+
34
+ def handle_char char
35
+ if char == "\n" && !@multiline
36
+ @handler.call @text if @handler
37
+ @text = ''
38
+ elsif char == "\177"
39
+ @text.slice! -1
40
+ else
41
+ @text += char
42
+ end
43
+ redraw
44
+ end
45
+ end
46
+ end
data/lib/luck.rb ADDED
@@ -0,0 +1,9 @@
1
+ Luck = Module.new
2
+
3
+ require 'luck/display'
4
+ require 'luck/pane'
5
+ require 'luck/control'
6
+ require 'luck/listbox'
7
+ require 'luck/progressbar'
8
+ require 'luck/label'
9
+ require 'luck/textbox'
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: luck
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Danopia
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-07 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thoughtbot-shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: Pure-ruby CLI UI system. Includes multiple panes in a display and multiple controls in a pane. luck is lucky (as opposed to ncurses being cursed)
26
+ email: me.github@danopia.net
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.md
34
+ files:
35
+ - .gitignore
36
+ - LICENSE
37
+ - README.md
38
+ - Rakefile
39
+ - VERSION
40
+ - lib/luck.rb
41
+ - lib/luck/control.rb
42
+ - lib/luck/display.rb
43
+ - lib/luck/label.rb
44
+ - lib/luck/listbox.rb
45
+ - lib/luck/pane.rb
46
+ - lib/luck/progressbar.rb
47
+ - lib/luck/textbox.rb
48
+ has_rdoc: true
49
+ homepage: http://github.com/danopia/luck
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options:
54
+ - --charset=UTF-8
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.3.5
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: Pure-ruby CLI UI system
76
+ test_files: []
77
+