spiral 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
File without changes
data/Gemfile ADDED
@@ -0,0 +1 @@
1
+ gem 'ffi-chm', '>= 0.3.0'
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2011-07-26
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
@@ -0,0 +1,13 @@
1
+ bin/spiral
2
+ Gemfile
3
+ History.txt
4
+ lib/spiral/display_data.rb
5
+ lib/spiral/feed/base.rb
6
+ lib/spiral/feed/chm.rb
7
+ lib/spiral/renderer/w3m.rb
8
+ lib/spiral/textbox.rb
9
+ lib/spiral.rb
10
+ Manifest.txt
11
+ Rakefile
12
+ README.txt
13
+ test/test_spiral.rb
@@ -0,0 +1,63 @@
1
+ = spiral
2
+
3
+ * http://github.com/nanki/spiral
4
+
5
+ == DESCRIPTION:
6
+
7
+ CUI-based CHM viewer.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Search entries in index, contents, fulltext index.
12
+ * Asynchronous searching.
13
+ * Background HTML rendering.
14
+
15
+ == SYNOPSIS:
16
+
17
+ $ spiral ruby_on_rails.chm
18
+
19
+ == REQUIREMENTS:
20
+
21
+ * Ruby 1.8.7 / Ruby 1.9.x
22
+ * ffi-chm >= 0.3.0
23
+ * ansi (gem)
24
+ * w3m
25
+ * io-console (http://www.rubyist.net/~nobu/ruby/io-console-0.2.gem) or Ruby 1.9.3
26
+
27
+ == INSTALL:
28
+
29
+ * gem install spiral
30
+
31
+ == DEVELOPERS:
32
+
33
+ After checking out the source, run:
34
+
35
+ $ rake newb
36
+
37
+ This task will install any missing dependencies, run the tests/specs,
38
+ and generate the RDoc.
39
+
40
+ == LICENSE:
41
+
42
+ (The MIT License)
43
+
44
+ Copyright (c) 2011 NANKI Haruo
45
+
46
+ Permission is hereby granted, free of charge, to any person obtaining
47
+ a copy of this software and associated documentation files (the
48
+ 'Software'), to deal in the Software without restriction, including
49
+ without limitation the rights to use, copy, modify, merge, publish,
50
+ distribute, sublicense, and/or sell copies of the Software, and to
51
+ permit persons to whom the Software is furnished to do so, subject to
52
+ the following conditions:
53
+
54
+ The above copyright notice and this permission notice shall be
55
+ included in all copies or substantial portions of the Software.
56
+
57
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
58
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
59
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
60
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
61
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
62
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
63
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,12 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.spec 'spiral' do
7
+ developer('nanki', 'nanki@dotswitch.net')
8
+ extra_deps << ['ffi-chm', '>= 0.3.0']
9
+ extra_deps << ['ansi']
10
+ end
11
+
12
+ # vim: syntax=ruby
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: UTF-8 -*-;
3
+
4
+ require 'thread'
5
+ require 'rubygems'
6
+
7
+ gem 'ffi-chm'
8
+ gem 'io-console'
9
+ gem 'ansi'
10
+
11
+ require 'ffi-chm'
12
+ require 'io/console'
13
+ require 'ansi'
14
+
15
+ require 'spiral'
16
+
17
+ module Spiral
18
+ class Bus
19
+ attr_reader :display_data, :render_queue
20
+
21
+ def initialize
22
+ @display_data = Queue.new
23
+ @render_queue = Queue.new
24
+ end
25
+ end
26
+
27
+ class Client
28
+ include ANSI::Code
29
+
30
+ def spawn_search(str)
31
+ Thread.new(@bus) do |bus|
32
+ str_downcase = str.downcase
33
+ if str_downcase == str
34
+ pattern = Regexp.compile(Regexp.escape(str), Regexp::IGNORECASE)
35
+ else
36
+ pattern = Regexp.compile(Regexp.escape(str))
37
+ end
38
+
39
+ is = []
40
+ class << is
41
+ def add(d)
42
+ @keys ||= {}
43
+ return false if @keys[d.content_key]
44
+ @keys[d.content_key] = true
45
+ self << d
46
+ true
47
+ end
48
+ end
49
+
50
+ keys = {}
51
+
52
+ bus.display_data.push [str, is, pattern]
53
+
54
+ [@chm.contents, @chm.index].flatten.each do |i|
55
+ next unless i.Name.downcase.start_with?(str_downcase) && i.Local
56
+ next unless is.add DisplayData.new(i.Name, i.Local, @chm)
57
+ bus.display_data.push [str, is, pattern]
58
+ end
59
+
60
+ if str.size > 1
61
+ @chm.fulltext_search(str_downcase) do |leaf|
62
+ doci = 0
63
+ @chm.fulltext_index.wlcs(leaf).each do |wlc|
64
+ doci += wlc.document_index
65
+ i = @chm.topics.record(doci)
66
+ next unless is.add DisplayData.new(i.title, i.local, @chm)
67
+ bus.display_data.push [str, is, pattern]
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def spawn_input
75
+ Thread.new(@bus) do |bus|
76
+ Textbox.new.main do |cmd|
77
+ if Symbol === cmd
78
+ bus.display_data.push cmd
79
+ else
80
+ bus.render_queue.clear
81
+ @search.kill
82
+ @search = spawn_search(cmd)
83
+ end
84
+ end
85
+
86
+ on_exit
87
+ exit
88
+ end
89
+ end
90
+
91
+ def on_exit
92
+ @chm.close
93
+ end
94
+
95
+ def spawn_display
96
+ Thread.new(@bus) do |bus|
97
+ args = nil
98
+ loop do
99
+ sleep 0.01
100
+
101
+ last = nil
102
+ while data = bus.display_data.shift(true) rescue nil
103
+ last = data
104
+ end
105
+
106
+ next unless last
107
+
108
+ case last
109
+ when :refresh
110
+ when :next
111
+ bus.render_queue.clear
112
+ Thread.current[:pos] += 1
113
+ when :prev
114
+ bus.render_queue.clear
115
+ Thread.current[:pos] -= 1
116
+ else
117
+ bus.render_queue.clear
118
+ Thread.current[:pos] = 0
119
+ args = last
120
+ end
121
+
122
+ refresh *args
123
+ end
124
+ end
125
+ end
126
+
127
+ def initialize(path)
128
+ @bus = Bus.new
129
+
130
+ @chm = FFI::Chm::ChmFile.new(path)
131
+ @chm.open
132
+
133
+ render = Thread.new(@bus) do |bus|
134
+ while i = bus.render_queue.shift
135
+ i.retrieve
136
+ bus.display_data.push :refresh
137
+ end
138
+ end
139
+
140
+ display = spawn_display
141
+ display.priority = 1
142
+
143
+ @search = spawn_search ''
144
+
145
+ [@search, render, display, spawn_input].each(&:join)
146
+ end
147
+
148
+ def display(str, size = 0)
149
+ print clr
150
+ print "#{size} >> #{str}"
151
+ end
152
+
153
+ def nextline(l=nil)
154
+ @current_line = l if l
155
+
156
+ if @current_line >= $stdout.winsize.first
157
+ throw :exit
158
+ else
159
+ puts left($stdout.winsize.last)
160
+ end
161
+
162
+ @current_line += 1
163
+ end
164
+
165
+ def refresh(str, is, pattern)
166
+ print reset
167
+ print move 0, 0
168
+
169
+ if str.empty?
170
+ print cls
171
+ display str, 1
172
+ return
173
+ end
174
+
175
+ display str, is.size
176
+ nextline(1)
177
+
178
+ print save
179
+
180
+ pos = Thread.current[:pos] = [[Thread.current[:pos], is.size - 1].min, 0].max
181
+
182
+ is = is[pos..-1] || []
183
+
184
+ filled = true
185
+
186
+ catch(:exit) do
187
+ is.each do |i|
188
+ print clr
189
+ print underscore { blue { i.title } }
190
+
191
+ nextline
192
+
193
+ unless i.available?
194
+ filled &&= false
195
+ @bus.render_queue.push i
196
+ next
197
+ end
198
+
199
+ i.content.each_line do |line|
200
+ print clr
201
+ print line.rstrip.gsub(pattern) {|md| red{ md } }
202
+
203
+ nextline
204
+ end
205
+ end
206
+
207
+ $stdout.winsize.first.times do
208
+ print clr
209
+ nextline
210
+ end
211
+ end
212
+
213
+ @bus.render_queue.clear if !is.empty? && filled
214
+
215
+ print restore
216
+ end
217
+ end
218
+ end
219
+
220
+ Spiral::Client.new ARGV.first
@@ -0,0 +1,8 @@
1
+ module Spiral
2
+ VERSION = '0.0.1'
3
+
4
+ require 'spiral/display_data'
5
+ require 'spiral/textbox'
6
+ require 'spiral/feed/chm'
7
+ require 'spiral/renderer/w3m'
8
+ end
@@ -0,0 +1,46 @@
1
+ class Spiral::DisplayData
2
+ attr_reader :title, :local
3
+ attr_accessor :content
4
+
5
+ def initialize(title, local, chm)
6
+ @title = title
7
+ @local = local
8
+ @chm = chm
9
+ end
10
+
11
+ def content_key
12
+ [@chm, @local].hash
13
+ end
14
+
15
+ def trim(html, id)
16
+ doc = Nokogiri::XML.parse html
17
+ e = doc.at_css("##{id}")
18
+
19
+ p = nil
20
+ begin
21
+ p = e.parent rescue nil
22
+ ep = e.previous rescue nil
23
+ while e = ep
24
+ ep = e.previous
25
+ e.remove
26
+ end
27
+ end while e = p
28
+
29
+ doc.to_s
30
+ end
31
+
32
+ def retrieve
33
+ id = @local.split("#")[1]
34
+ html = @chm.retrieve_object("/#{@local.gsub(/#.*/, '')}") rescue "[Error]"
35
+ html = trim(html, id) if id
36
+ @content = ::Spiral::Renderer::W3m.new.render(html, :cols => $stdout.winsize.last)
37
+ end
38
+
39
+ def available?
40
+ !!@content
41
+ end
42
+
43
+ def height
44
+ @content.lines.size
45
+ end
46
+ end
@@ -0,0 +1,5 @@
1
+ module Spiral
2
+ module Feed
3
+ class Base;end
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ require 'spiral/feed/base'
2
+ require 'ffi-chm'
3
+
4
+ module Spiral
5
+ module Feed
6
+ class Chm < Base
7
+ def initialize(file)
8
+ @chm = FFI::Chm::ChmFile.new file
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ require 'open3'
2
+
3
+ module Spiral
4
+ module Renderer
5
+ class W3m
6
+ def render(html, options={})
7
+ opt = {:cols => 80}.merge options
8
+ Open3.popen3 "w3m -cols #{opt[:cols]} -dump -O UTF8 -T text/html" do |i, o, e|
9
+ i.write html
10
+ i.close
11
+ o.read.strip
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,44 @@
1
+ class Spiral::Textbox
2
+ def initialize(input=$stdin)
3
+ @input = input
4
+ end
5
+
6
+ def process(ch)
7
+ case ch
8
+ when "\027" # <C-W>
9
+ @string = @string.rstrip.split(/\b/)[0...-1].join
10
+ when "\b"
11
+ @string = @string.split(//)[0...-1].join
12
+ when "\r"
13
+ when "\003", "\004"
14
+ throw :exit
15
+ when "\x10" # <C-p>
16
+ return :prev
17
+ when "\x0e" # <C-n>
18
+ return :next
19
+ else
20
+ case ch
21
+ when /[^[:cntrl:]]/
22
+ @string += ch
23
+ else
24
+ end
25
+ end
26
+
27
+ @string
28
+ end
29
+
30
+ def main
31
+ @input.raw do |io|
32
+ yield @string = ""
33
+
34
+ return if catch(:exit) do
35
+ io.chars do |ch|
36
+ prev = @string.dup
37
+ result = process(ch)
38
+ next if !(Symbol === result) && result == prev
39
+ yield result
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,8 @@
1
+ require "test/unit"
2
+ require "spiral"
3
+
4
+ class TestSpiral < Test::Unit::TestCase
5
+ def test_sanity
6
+ flunk "write tests or I will kneecap you"
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spiral
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - nanki
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-09 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ffi-chm
16
+ requirement: &70334286560180 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.3.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70334286560180
25
+ - !ruby/object:Gem::Dependency
26
+ name: ansi
27
+ requirement: &70334286559800 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70334286559800
36
+ - !ruby/object:Gem::Dependency
37
+ name: hoe
38
+ requirement: &70334286559320 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '2.10'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70334286559320
47
+ description: CUI-based CHM viewer.
48
+ email:
49
+ - nanki@dotswitch.net
50
+ executables:
51
+ - spiral
52
+ extensions: []
53
+ extra_rdoc_files:
54
+ - History.txt
55
+ - Manifest.txt
56
+ - README.txt
57
+ files:
58
+ - bin/spiral
59
+ - Gemfile
60
+ - History.txt
61
+ - lib/spiral/display_data.rb
62
+ - lib/spiral/feed/base.rb
63
+ - lib/spiral/feed/chm.rb
64
+ - lib/spiral/renderer/w3m.rb
65
+ - lib/spiral/textbox.rb
66
+ - lib/spiral.rb
67
+ - Manifest.txt
68
+ - Rakefile
69
+ - README.txt
70
+ - test/test_spiral.rb
71
+ - .gemtest
72
+ homepage: http://github.com/nanki/spiral
73
+ licenses: []
74
+ post_install_message:
75
+ rdoc_options:
76
+ - --main
77
+ - README.txt
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project: spiral
94
+ rubygems_version: 1.8.6
95
+ signing_key:
96
+ specification_version: 3
97
+ summary: CUI-based CHM viewer.
98
+ test_files:
99
+ - test/test_spiral.rb