fastreader 1.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.
@@ -0,0 +1,240 @@
1
+ module FeedsController
2
+
3
+ def show_feeds(feeds=[])
4
+ @all_feeds ||= feeds
5
+ # So we can redisplay them after exiting a search subset
6
+ begin
7
+
8
+ @command_window = CommandWindow.new(@scr)
9
+ @items = @all_feeds
10
+
11
+ @scr.refresh # or else the initial screen is blank, I don't know why yet
12
+
13
+ @content_area = MenuWindow.new("Your Feeds", @scr, @all_feeds, @current_feed_index || 0)
14
+ @command_window.help_prompt
15
+
16
+ loop do
17
+ parse_feeds_list_command
18
+ end
19
+ end
20
+ end
21
+
22
+ def parse_feeds_list_command
23
+ c = @scr.getch
24
+ # store number in buffer, vim style
25
+ c, buffer = key_buffer(c)
26
+ multiplier = buffer.empty? ? 1 : buffer.join.to_i
27
+ LOGGER.debug("Char: #{c}")
28
+ LOGGER.debug("Multiplier: #{multiplier}")
29
+
30
+ case c
31
+ when ?\? # help
32
+ show_help(CursesController::FEEDS_MENU_COMMANDS)
33
+ # Any key restores the menu
34
+ redraw
35
+
36
+ when ?\u # UPDATE FEEDS
37
+
38
+ @current_feed = @items[@content_area.current_index]
39
+ num = @command_window.feed_update_progress(@current_feed)
40
+
41
+ if num > 0
42
+ @scr.getch
43
+ redraw
44
+ end
45
+
46
+ when ?\U # UPDATE FEEDS, forcing update even if soon after last update
47
+
48
+ @current_feed = @items[@content_area.current_index]
49
+ num = @command_window.feed_update_progress(@current_feed, true)
50
+
51
+ if num > 0
52
+ @scr.getch
53
+ redraw
54
+ end
55
+
56
+ when control_key('U') # UPDATE ALL FEEDS
57
+
58
+ num = 0
59
+ @all_feeds.each do |feed|
60
+ num += (@command_window.feed_update_progress(feed) || 0)
61
+ end
62
+ @command_window.message("#{num} new items.")
63
+
64
+ when ?\d # delete feed
65
+
66
+ @current_feed = @items[@content_area.current_index]
67
+ if @current_feed.is_a?(VirtualFeed)
68
+ @command_window.command_line = "You can't delete a virtual feed!"
69
+ @command_window.draw
70
+ return
71
+ end
72
+
73
+ if @command_window.confirm_delete_feed(@current_feed) == ?\y
74
+ title = @current_feed.title
75
+ @current_feed.destroy
76
+ @command_window.command_line = "The #{title} feed has been deleted"
77
+
78
+ @current_index = @content_area.current_index - 1
79
+ feeds = Feed.feeds_list
80
+ redraw(feeds)
81
+ else
82
+ @command_window.command_line = "Canceled."
83
+ end
84
+ @command_window.draw
85
+
86
+ when 97 # ?\a # add feed
87
+
88
+ if (new_feed = @command_window.add_feed)
89
+
90
+ # redraw feeds list
91
+ feed_list = Feed.feeds_list
92
+ @current_index = feed_list.index(new_feed)
93
+ redraw(feed_list)
94
+ end
95
+ @command_window.draw
96
+
97
+ when ?\q
98
+ # cancel search
99
+ # TODO change
100
+ if @command_window && @command_window.command_line == 'Press q to cancel search.'
101
+ cancel_search
102
+ else
103
+ exit
104
+ end
105
+ # SEARCH. This is a global search that generates a virtual feed
106
+ when ?\/
107
+
108
+ @command_window ||= CommandWindow.new( @scr )
109
+
110
+ @global_search_string = @command_window.get_search_string
111
+ if @global_search_string == '' || @global_search_string =~ /^\W*$/
112
+ @command_window.command_line = "Found no matches"
113
+ end
114
+
115
+ matches = search_feeds(@global_search_string.strip)
116
+ if matches.nil? || matches.empty?
117
+ @command_window.command_line = "Found no matches"
118
+ end
119
+
120
+ if matches
121
+ # create a virtual feed
122
+ @items = @current_entries = matches
123
+
124
+ @entries_list_matches = nil
125
+
126
+ @current_entry_index = 0
127
+ @content_area.close
128
+ @content_area = nil
129
+ show_entries
130
+
131
+ else
132
+ @command_window.draw
133
+ end
134
+
135
+ when Key::LEFT, Key::UP, ?k, ?p
136
+ @content_area.prev_item(multiplier)
137
+ when Key::DOWN, ?j, ?\s, ?n
138
+ @content_area.next_item(multiplier)
139
+ when Key::RESIZE
140
+ redraw(false)
141
+ when Key::RIGHT, ?l, 10 # Enter key
142
+
143
+ # You can type a number and press enter or right arrow to select a feed by
144
+ # number.
145
+ unless buffer && buffer.empty?
146
+ selection = buffer.join.to_i
147
+ # set feed
148
+ @current_feed = @items[selection - 1]
149
+ @current_feed_index = selection - 1
150
+ @content_area.current_index = selection - 1
151
+
152
+ else
153
+ @current_feed = @items[@content_area.current_index]
154
+ @current_feed_index = @content_area.current_index
155
+ end
156
+ # set current entry index to 0
157
+ @current_entry_index = 0
158
+
159
+ LOGGER.debug("Selected feed #{@current_feed.title}")
160
+
161
+ # Regular Feeds and Virtual Feeds have same interface
162
+ @items = @current_entries = @current_feed.is_a?(VirtualFeed) ? @current_feed.entries :
163
+ @current_feed.entries.find(:all, :include => :feed)
164
+
165
+ # Show the entry content
166
+
167
+ @entries_list_matches = nil
168
+ @current_entry_index = 0
169
+ @content_area.close
170
+ @content_area = nil
171
+ show_entries
172
+
173
+ when 'M'[0] # middle of screen
174
+ @content_area.middle
175
+ when 'L'[0] # bottom of screen
176
+ @content_area.bottom
177
+ when 'H'[0] # top of screen
178
+ @content_area.top
179
+ when 'G'[0] # beginning or end of list
180
+ if buffer.last == 1
181
+ @content_area.end
182
+ else
183
+ @content_area.beginning
184
+ end
185
+ when control_key('F')
186
+ @content_area.next_page
187
+ when control_key('B')
188
+ @content_area.prev_page
189
+ else
190
+ # don't do anything
191
+ end
192
+ end
193
+
194
+ # This reloads the feeds (if items are provided) and redraws the content window
195
+ def redraw(new_items =nil)
196
+ @items = new_items ? (@all_feeds = new_items) : @content_area.items
197
+ unless new_items
198
+ @current_index = @content_area.current_index
199
+ end
200
+ @content_area.close
201
+ @content_area = MenuWindow.new("Your Feeds", @scr, @items, @current_index)
202
+ end
203
+
204
+ # This is a global search of all feed entries
205
+ def search_feeds(global_search_string)
206
+ if global_search_string == ''
207
+ @command_window.clear
208
+ return
209
+ end
210
+
211
+ # maybe change this to ferret or something later
212
+ # TODO
213
+ LOGGER.debug("search query: title LIKE '%#{global_search_string}%' or content LIKE '%#{global_search_string}%'")
214
+ matches = Entry.find(:all,
215
+ :include => :feed,
216
+ :conditions => "title LIKE '%#{global_search_string}%'",
217
+ :order => "id desc")
218
+
219
+ # We give priority to title matches
220
+ matches.concat Entry.find(:all,
221
+ :include => :feed,
222
+ :conditions => "content LIKE '%#{global_search_string}%'",
223
+ :order => "id desc")
224
+ # There might very well be duplicates, so compress
225
+ matches.uniq!
226
+ if matches.empty?
227
+ @command_window.command_line = "Found no matches for search string '#{global_search_string}'"
228
+ @command_window.draw
229
+ nil
230
+ else
231
+ @command_window.command_line = "Found #{matches.size} entries for search string '#{global_search_string}'. Displaying..."
232
+ @command_window.draw
233
+ sleep 1
234
+
235
+ matches
236
+ end
237
+ end
238
+
239
+ end
240
+
@@ -0,0 +1,165 @@
1
+ require 'htmlentities/legacy'
2
+
3
+ #
4
+ # HTML entity encoding and decoding for Ruby
5
+ #
6
+
7
+ class HTMLEntities
8
+
9
+ VERSION = '4.0.0'
10
+ FLAVORS = %w[html4 xhtml1]
11
+ INSTRUCTIONS = [:basic, :named, :decimal, :hexadecimal]
12
+
13
+ class InstructionError < RuntimeError
14
+ end
15
+ class UnknownFlavor < RuntimeError
16
+ end
17
+
18
+ #
19
+ # Create a new HTMLEntities coder for the specified flavor.
20
+ # Available flavors are 'html4' and 'xhtml1' (the default).
21
+ # The only difference in functionality between the two is in the handling of the apos
22
+ # (apostrophe) named entity, which is not defined in HTML4.
23
+ #
24
+ def initialize(flavor='xhtml1')
25
+ @flavor = flavor.to_s.downcase
26
+ raise UnknownFlavor, "Unknown flavor #{flavor}" unless FLAVORS.include?(@flavor)
27
+ end
28
+
29
+ #
30
+ # Decode entities in a string into their UTF-8
31
+ # equivalents. Obviously, if your string is not already in UTF-8, you'd
32
+ # better convert it before using this method, or the output will be mixed
33
+ # up.
34
+ #
35
+ # Unknown named entities will not be converted
36
+ #
37
+ def decode(source)
38
+ return source.to_s.gsub(named_entity_regexp) {
39
+ (cp = map[$1]) ? [cp].pack('U') : $&
40
+ }.gsub(/&#([0-9]{1,7});|&#x([0-9a-f]{1,6});/i) {
41
+ $1 ? [$1.to_i].pack('U') : [$2.to_i(16)].pack('U')
42
+ }
43
+ end
44
+
45
+ #
46
+ # Encode codepoints into their corresponding entities. Various operations
47
+ # are possible, and may be specified in order:
48
+ #
49
+ # :basic :: Convert the five XML entities ('"<>&)
50
+ # :named :: Convert non-ASCII characters to their named HTML 4.01 equivalent
51
+ # :decimal :: Convert non-ASCII characters to decimal entities (e.g. &#1234;)
52
+ # :hexadecimal :: Convert non-ASCII characters to hexadecimal entities (e.g. # &#x12ab;)
53
+ #
54
+ # You can specify the commands in any order, but they will be executed in
55
+ # the order listed above to ensure that entity ampersands are not
56
+ # clobbered and that named entities are replaced before numeric ones.
57
+ #
58
+ # If no instructions are specified, :basic will be used.
59
+ #
60
+ # Examples:
61
+ # encode_entities(str) - XML-safe
62
+ # encode_entities(str, :basic, :decimal) - XML-safe and 7-bit clean
63
+ # encode_entities(str, :basic, :named, :decimal) - 7-bit clean, with all
64
+ # non-ASCII characters replaced with their named entity where possible, and
65
+ # decimal equivalents otherwise.
66
+ #
67
+ # Note: It is the program's responsibility to ensure that the source
68
+ # contains valid UTF-8 before calling this method.
69
+ #
70
+ def encode(source, *instructions)
71
+ string = source.to_s.dup
72
+ if (instructions.empty?)
73
+ instructions = [:basic]
74
+ elsif (unknown_instructions = instructions - INSTRUCTIONS) != []
75
+ raise InstructionError,
76
+ "unknown encode_entities command(s): #{unknown_instructions.inspect}"
77
+ end
78
+
79
+ basic_entity_encoder =
80
+ if instructions.include?(:basic) || instructions.include?(:named)
81
+ :encode_named
82
+ elsif instructions.include?(:decimal)
83
+ :encode_decimal
84
+ else instructions.include?(:hexadecimal)
85
+ :encode_hexadecimal
86
+ end
87
+ string.gsub!(basic_entity_regexp){ __send__(basic_entity_encoder, $&) }
88
+
89
+ extended_entity_encoders = []
90
+ if instructions.include?(:named)
91
+ extended_entity_encoders << :encode_named
92
+ end
93
+ if instructions.include?(:decimal)
94
+ extended_entity_encoders << :encode_decimal
95
+ elsif instructions.include?(:hexadecimal)
96
+ extended_entity_encoders << :encode_hexadecimal
97
+ end
98
+ unless extended_entity_encoders.empty?
99
+ string.gsub!(extended_entity_regexp){
100
+ encode_extended(extended_entity_encoders, $&)
101
+ }
102
+ end
103
+
104
+ return string
105
+ end
106
+
107
+ private
108
+
109
+ def map
110
+ @map ||= (require "htmlentities/#{@flavor}"; HTMLEntities::MAPPINGS[@flavor])
111
+ end
112
+
113
+ def basic_entity_regexp
114
+ @basic_entity_regexp ||= (
115
+ case @flavor
116
+ when /^html/
117
+ /[<>"&]/
118
+ else
119
+ /[<>'"&]/
120
+ end
121
+ )
122
+ end
123
+
124
+ def extended_entity_regexp
125
+ @extended_entity_regexp ||= (
126
+ regexp = '[\x00-\x1f]|[\xc0-\xfd][\x80-\xbf]+'
127
+ regexp += "|'" if @flavor == 'html4'
128
+ Regexp.new(regexp)
129
+ )
130
+ end
131
+
132
+ def named_entity_regexp
133
+ @named_entity_regexp ||= (
134
+ min_length = map.keys.map{ |a| a.length }.min
135
+ max_length = map.keys.map{ |a| a.length }.max
136
+ /&([a-z][a-z0-9]{#{min_length-1},#{max_length-1}});/i
137
+ )
138
+ end
139
+
140
+ def reverse_map
141
+ @reverse_map ||= map.invert
142
+ end
143
+
144
+ def encode_named(char)
145
+ cp = char.unpack('U')[0]
146
+ (e = reverse_map[cp]) && "&#{e};"
147
+ end
148
+
149
+ def encode_decimal(char)
150
+ "&##{char.unpack('U')[0]};"
151
+ end
152
+
153
+ def encode_hexadecimal(char)
154
+ "&#x#{char.unpack('U')[0].to_s(16)};"
155
+ end
156
+
157
+ def encode_extended(encoders, char)
158
+ encoders.each do |encoder|
159
+ encoded = __send__(encoder, char)
160
+ return encoded if encoded
161
+ end
162
+ return char
163
+ end
164
+
165
+ end
@@ -0,0 +1,257 @@
1
+ class HTMLEntities
2
+ MAPPINGS = {} unless defined? MAPPINGS
3
+ MAPPINGS['html4'] = {
4
+ 'Aacute' => 193,
5
+ 'aacute' => 225,
6
+ 'Acirc' => 194,
7
+ 'acirc' => 226,
8
+ 'acute' => 180,
9
+ 'AElig' => 198,
10
+ 'aelig' => 230,
11
+ 'Agrave' => 192,
12
+ 'agrave' => 224,
13
+ 'alefsym' => 8501,
14
+ 'Alpha' => 913,
15
+ 'alpha' => 945,
16
+ 'amp' => 38,
17
+ 'and' => 8743,
18
+ 'ang' => 8736,
19
+ 'Aring' => 197,
20
+ 'aring' => 229,
21
+ 'asymp' => 8776,
22
+ 'Atilde' => 195,
23
+ 'atilde' => 227,
24
+ 'Auml' => 196,
25
+ 'auml' => 228,
26
+ 'bdquo' => 8222,
27
+ 'Beta' => 914,
28
+ 'beta' => 946,
29
+ 'brvbar' => 166,
30
+ 'bull' => 8226,
31
+ 'cap' => 8745,
32
+ 'Ccedil' => 199,
33
+ 'ccedil' => 231,
34
+ 'cedil' => 184,
35
+ 'cent' => 162,
36
+ 'Chi' => 935,
37
+ 'chi' => 967,
38
+ 'circ' => 710,
39
+ 'clubs' => 9827,
40
+ 'cong' => 8773,
41
+ 'copy' => 169,
42
+ 'crarr' => 8629,
43
+ 'cup' => 8746,
44
+ 'curren' => 164,
45
+ 'Dagger' => 8225,
46
+ 'dagger' => 8224,
47
+ 'dArr' => 8659,
48
+ 'darr' => 8595,
49
+ 'deg' => 176,
50
+ 'Delta' => 916,
51
+ 'delta' => 948,
52
+ 'diams' => 9830,
53
+ 'divide' => 247,
54
+ 'Eacute' => 201,
55
+ 'eacute' => 233,
56
+ 'Ecirc' => 202,
57
+ 'ecirc' => 234,
58
+ 'Egrave' => 200,
59
+ 'egrave' => 232,
60
+ 'empty' => 8709,
61
+ 'emsp' => 8195,
62
+ 'ensp' => 8194,
63
+ 'Epsilon' => 917,
64
+ 'epsilon' => 949,
65
+ 'equiv' => 8801,
66
+ 'Eta' => 919,
67
+ 'eta' => 951,
68
+ 'ETH' => 208,
69
+ 'eth' => 240,
70
+ 'Euml' => 203,
71
+ 'euml' => 235,
72
+ 'euro' => 8364,
73
+ 'exist' => 8707,
74
+ 'fnof' => 402,
75
+ 'forall' => 8704,
76
+ 'frac12' => 189,
77
+ 'frac14' => 188,
78
+ 'frac34' => 190,
79
+ 'frasl' => 8260,
80
+ 'Gamma' => 915,
81
+ 'gamma' => 947,
82
+ 'ge' => 8805,
83
+ 'gt' => 62,
84
+ 'hArr' => 8660,
85
+ 'harr' => 8596,
86
+ 'hearts' => 9829,
87
+ 'hellip' => 8230,
88
+ 'Iacute' => 205,
89
+ 'iacute' => 237,
90
+ 'Icirc' => 206,
91
+ 'icirc' => 238,
92
+ 'iexcl' => 161,
93
+ 'Igrave' => 204,
94
+ 'igrave' => 236,
95
+ 'image' => 8465,
96
+ 'infin' => 8734,
97
+ 'int' => 8747,
98
+ 'Iota' => 921,
99
+ 'iota' => 953,
100
+ 'iquest' => 191,
101
+ 'isin' => 8712,
102
+ 'Iuml' => 207,
103
+ 'iuml' => 239,
104
+ 'Kappa' => 922,
105
+ 'kappa' => 954,
106
+ 'Lambda' => 923,
107
+ 'lambda' => 955,
108
+ 'lang' => 9001,
109
+ 'laquo' => 171,
110
+ 'lArr' => 8656,
111
+ 'larr' => 8592,
112
+ 'lceil' => 8968,
113
+ 'ldquo' => 8220,
114
+ 'le' => 8804,
115
+ 'lfloor' => 8970,
116
+ 'lowast' => 8727,
117
+ 'loz' => 9674,
118
+ 'lrm' => 8206,
119
+ 'lsaquo' => 8249,
120
+ 'lsquo' => 8216,
121
+ 'lt' => 60,
122
+ 'macr' => 175,
123
+ 'mdash' => 8212,
124
+ 'micro' => 181,
125
+ 'middot' => 183,
126
+ 'minus' => 8722,
127
+ 'Mu' => 924,
128
+ 'mu' => 956,
129
+ 'nabla' => 8711,
130
+ 'nbsp' => 160,
131
+ 'ndash' => 8211,
132
+ 'ne' => 8800,
133
+ 'ni' => 8715,
134
+ 'not' => 172,
135
+ 'notin' => 8713,
136
+ 'nsub' => 8836,
137
+ 'Ntilde' => 209,
138
+ 'ntilde' => 241,
139
+ 'Nu' => 925,
140
+ 'nu' => 957,
141
+ 'Oacute' => 211,
142
+ 'oacute' => 243,
143
+ 'Ocirc' => 212,
144
+ 'ocirc' => 244,
145
+ 'OElig' => 338,
146
+ 'oelig' => 339,
147
+ 'Ograve' => 210,
148
+ 'ograve' => 242,
149
+ 'oline' => 8254,
150
+ 'Omega' => 937,
151
+ 'omega' => 969,
152
+ 'Omicron' => 927,
153
+ 'omicron' => 959,
154
+ 'oplus' => 8853,
155
+ 'or' => 8744,
156
+ 'ordf' => 170,
157
+ 'ordm' => 186,
158
+ 'Oslash' => 216,
159
+ 'oslash' => 248,
160
+ 'Otilde' => 213,
161
+ 'otilde' => 245,
162
+ 'otimes' => 8855,
163
+ 'Ouml' => 214,
164
+ 'ouml' => 246,
165
+ 'para' => 182,
166
+ 'part' => 8706,
167
+ 'permil' => 8240,
168
+ 'perp' => 8869,
169
+ 'Phi' => 934,
170
+ 'phi' => 966,
171
+ 'Pi' => 928,
172
+ 'pi' => 960,
173
+ 'piv' => 982,
174
+ 'plusmn' => 177,
175
+ 'pound' => 163,
176
+ 'Prime' => 8243,
177
+ 'prime' => 8242,
178
+ 'prod' => 8719,
179
+ 'prop' => 8733,
180
+ 'Psi' => 936,
181
+ 'psi' => 968,
182
+ 'quot' => 34,
183
+ 'radic' => 8730,
184
+ 'rang' => 9002,
185
+ 'raquo' => 187,
186
+ 'rArr' => 8658,
187
+ 'rarr' => 8594,
188
+ 'rceil' => 8969,
189
+ 'rdquo' => 8221,
190
+ 'real' => 8476,
191
+ 'reg' => 174,
192
+ 'rfloor' => 8971,
193
+ 'Rho' => 929,
194
+ 'rho' => 961,
195
+ 'rlm' => 8207,
196
+ 'rsaquo' => 8250,
197
+ 'rsquo' => 8217,
198
+ 'sbquo' => 8218,
199
+ 'Scaron' => 352,
200
+ 'scaron' => 353,
201
+ 'sdot' => 8901,
202
+ 'sect' => 167,
203
+ 'shy' => 173,
204
+ 'Sigma' => 931,
205
+ 'sigma' => 963,
206
+ 'sigmaf' => 962,
207
+ 'sim' => 8764,
208
+ 'spades' => 9824,
209
+ 'sub' => 8834,
210
+ 'sube' => 8838,
211
+ 'sum' => 8721,
212
+ 'sup' => 8835,
213
+ 'sup1' => 185,
214
+ 'sup2' => 178,
215
+ 'sup3' => 179,
216
+ 'supe' => 8839,
217
+ 'szlig' => 223,
218
+ 'Tau' => 932,
219
+ 'tau' => 964,
220
+ 'there4' => 8756,
221
+ 'Theta' => 920,
222
+ 'theta' => 952,
223
+ 'thetasym' => 977,
224
+ 'thinsp' => 8201,
225
+ 'THORN' => 222,
226
+ 'thorn' => 254,
227
+ 'tilde' => 732,
228
+ 'times' => 215,
229
+ 'trade' => 8482,
230
+ 'Uacute' => 218,
231
+ 'uacute' => 250,
232
+ 'uArr' => 8657,
233
+ 'uarr' => 8593,
234
+ 'Ucirc' => 219,
235
+ 'ucirc' => 251,
236
+ 'Ugrave' => 217,
237
+ 'ugrave' => 249,
238
+ 'uml' => 168,
239
+ 'upsih' => 978,
240
+ 'Upsilon' => 933,
241
+ 'upsilon' => 965,
242
+ 'Uuml' => 220,
243
+ 'uuml' => 252,
244
+ 'weierp' => 8472,
245
+ 'Xi' => 926,
246
+ 'xi' => 958,
247
+ 'Yacute' => 221,
248
+ 'yacute' => 253,
249
+ 'yen' => 165,
250
+ 'Yuml' => 376,
251
+ 'yuml' => 255,
252
+ 'Zeta' => 918,
253
+ 'zeta' => 950,
254
+ 'zwj' => 8205,
255
+ 'zwnj' => 8204
256
+ }
257
+ end