luck 0.0.0

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