feedtxt 0.2.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Manifest.txt +9 -0
- data/README.md +63 -0
- data/Rakefile +4 -0
- data/lib/feedtxt.rb +4 -1
- data/lib/feedtxt/parser.rb +18 -70
- data/lib/feedtxt/parser/ini.rb +106 -0
- data/lib/feedtxt/parser/json.rb +110 -0
- data/lib/feedtxt/parser/yaml.rb +106 -0
- data/lib/feedtxt/version.rb +2 -2
- data/test/feeds/spec/example.ini.txt +15 -0
- data/test/feeds/spec/example.json.txt +15 -0
- data/test/feeds/spec/podcast.ini.txt +26 -0
- data/test/feeds/spec/podcast.json.txt +29 -0
- data/test/test_ini.rb +62 -0
- data/test/test_json.rb +63 -0
- data/test/test_yaml.rb +2 -2
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a63195357fa98e1b02d6513762ae80282a69fda1
|
4
|
+
data.tar.gz: 6d95a5daca55ba51b167689c19cad486259ca9ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e261cd043984b29ff7ed8532939a972f9e1738142477c5ab025017a13cc96f799a0f96f942d1d9a9a815c1332066ec6ef6f0b263028066f717415b785d085b62
|
7
|
+
data.tar.gz: c3bed556bd29fd1379ecd703422b1d7f427e6c57723f0b87f755331dfd1580a0d71cc44cefc4b11595c266c1b68db8513c44d36440ceb3eff42c74514bd736eb
|
data/Manifest.txt
CHANGED
@@ -4,9 +4,18 @@ README.md
|
|
4
4
|
Rakefile
|
5
5
|
lib/feedtxt.rb
|
6
6
|
lib/feedtxt/parser.rb
|
7
|
+
lib/feedtxt/parser/ini.rb
|
8
|
+
lib/feedtxt/parser/json.rb
|
9
|
+
lib/feedtxt/parser/yaml.rb
|
7
10
|
lib/feedtxt/version.rb
|
11
|
+
test/feeds/spec/example.ini.txt
|
12
|
+
test/feeds/spec/example.json.txt
|
8
13
|
test/feeds/spec/example.yaml.txt
|
14
|
+
test/feeds/spec/podcast.ini.txt
|
15
|
+
test/feeds/spec/podcast.json.txt
|
9
16
|
test/feeds/spec/podcast.yaml.txt
|
10
17
|
test/helper.rb
|
18
|
+
test/test_ini.rb
|
19
|
+
test/test_json.rb
|
11
20
|
test/test_scanner.rb
|
12
21
|
test/test_yaml.rb
|
data/README.md
CHANGED
@@ -202,6 +202,69 @@ item_content
|
|
202
202
|
...
|
203
203
|
```
|
204
204
|
|
205
|
+
## Alternative Meta Data Formats
|
206
|
+
|
207
|
+
Note: Feed.TXT supports alternative formats / styles for meta data blocks.
|
208
|
+
For now YAML, JSON and INI style
|
209
|
+
are built-in and shipping with the `feedtxt` gem.
|
210
|
+
|
211
|
+
|
212
|
+
### JSON Example
|
213
|
+
|
214
|
+
```
|
215
|
+
|{
|
216
|
+
"title": "My Example Feed",
|
217
|
+
"home_page_url": "https://example.org/",
|
218
|
+
"feed_url": "https://example.org/feed.txt"
|
219
|
+
}/{
|
220
|
+
"id": "2",
|
221
|
+
"url": "https://example.org/second-item"
|
222
|
+
}-{
|
223
|
+
This is a second item.
|
224
|
+
}/{
|
225
|
+
"id": "1",
|
226
|
+
"url": "https://example.org/initial-post"
|
227
|
+
}-{
|
228
|
+
Hello, world!
|
229
|
+
}|
|
230
|
+
```
|
231
|
+
|
232
|
+
Note: Use `|{` and `}|` to begin and end your Feed.TXT.
|
233
|
+
Use `}/{` for first or next item
|
234
|
+
and `}-{` for meta blocks inside items.
|
235
|
+
|
236
|
+
|
237
|
+
(Source: [`feeds/spec/example.json.txt`](https://github.com/feedtxt/feedtxt/blob/master/test/feeds/spec/example.json.txt))
|
238
|
+
|
239
|
+
|
240
|
+
### INI Example
|
241
|
+
|
242
|
+
```
|
243
|
+
[>>>
|
244
|
+
title = My Example Feed
|
245
|
+
home_page_url = https://example.org/
|
246
|
+
feed_url = https://example.org/feed.txt
|
247
|
+
</>
|
248
|
+
id = 2
|
249
|
+
url = https://example.org/second-item
|
250
|
+
---
|
251
|
+
This is a second item.
|
252
|
+
</>
|
253
|
+
id = 1
|
254
|
+
url = https://example.org/initial-post
|
255
|
+
---
|
256
|
+
Hello, world!
|
257
|
+
<<<]
|
258
|
+
```
|
259
|
+
|
260
|
+
(Source: [`feeds/spec/example.ini.txt`](https://github.com/feedtxt/feedtxt/blob/master/test/feeds/spec/example.ini.txt))
|
261
|
+
|
262
|
+
|
263
|
+
Note: Use `[>>>` and `<<<]` to begin and end your Feed.TXT.
|
264
|
+
Use `</>` for first or next item
|
265
|
+
and `---` for meta blocks inside items.
|
266
|
+
|
267
|
+
|
205
268
|
|
206
269
|
## License
|
207
270
|
|
data/Rakefile
CHANGED
data/lib/feedtxt.rb
CHANGED
@@ -14,12 +14,15 @@ require 'pp'
|
|
14
14
|
|
15
15
|
# 3rd party gems/libs
|
16
16
|
require 'logutils'
|
17
|
+
require 'props' ## used for IniFile.parse
|
17
18
|
|
18
19
|
|
19
20
|
# our own code
|
20
21
|
require 'feedtxt/version' # let it always go first
|
21
22
|
require 'feedtxt/parser'
|
22
|
-
|
23
|
+
require 'feedtxt/parser/json'
|
24
|
+
require 'feedtxt/parser/yaml'
|
25
|
+
require 'feedtxt/parser/ini'
|
23
26
|
|
24
27
|
|
25
28
|
|
data/lib/feedtxt/parser.rb
CHANGED
@@ -19,87 +19,35 @@ class Parser
|
|
19
19
|
end
|
20
20
|
|
21
21
|
|
22
|
-
|
23
|
-
## note:
|
24
|
-
## regex excape pipe: | to \|
|
25
|
-
## \\ needs to get escaped twice e.g. (\\ becomes \)
|
26
|
-
## e.g. |>>> or |>>>>>
|
27
|
-
FEED_BEGIN = %{^[ ]*\\|>>>+[ ]*$} ## note: allow leading n trailing spaces; allow 3 or more brackets
|
28
|
-
## e.g. <<<| or <<<<<<|
|
29
|
-
FEED_END = %{^[ ]*<<<+\\|[ ]*$} ## note: allow leading n trailing spaces; allow 3 or more brackets
|
30
|
-
|
31
|
-
## e.g.</> or <<</>>>
|
32
|
-
FEED_NEXT = %{^[ ]*<+/>+[ ]*$} ## pass 1: split/break up blocks
|
33
|
-
## e.g. --- or -----
|
34
|
-
FEED_META = %{^[ ]*---+[ ]*$} ## pass 2: break up item into metadata and content block
|
35
|
-
|
36
|
-
|
37
|
-
|
38
22
|
def parse
|
23
|
+
## auto-detect format
|
24
|
+
## use "best" matching format (e.g. first match by pos(ition))
|
39
25
|
|
40
|
-
##
|
41
|
-
##
|
42
|
-
## allow spaces before and after
|
26
|
+
klass = YamlParser ## default to yamlparser for now
|
27
|
+
pos = 9999999 ## todo:use MAX INTEGER or something!!
|
43
28
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
feed_begin = s.scan( /#{FEED_BEGIN}/ )
|
50
|
-
if feed_begin.empty? ## use blank? why? why not??
|
51
|
-
## nothing found return empty array for now; return nil - why? why not?
|
52
|
-
puts "warn !!! no begin marker found e.g. |>>>"
|
53
|
-
return []
|
29
|
+
json = @text.index( /#{JsonParser::FEED_BEGIN}/ )
|
30
|
+
if json # found e.g. not nil? incl. 0
|
31
|
+
pos = json
|
32
|
+
klass = JsonParser
|
54
33
|
end
|
55
34
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
feed_end = s.scan( /#{FEED_END}/ )
|
61
|
-
if feed_end.empty? ## use blank? why? why not??
|
62
|
-
## nothing found return empty array for now; return nil - why? why not?
|
63
|
-
puts "warn !!! no end marker found e.g. <<<|"
|
64
|
-
return []
|
35
|
+
ini = @text.index( /#{IniParser::FEED_BEGIN}/ )
|
36
|
+
if ini && ini < pos # found e.g. not nil? and match before last?
|
37
|
+
pos = ini
|
38
|
+
klass = IniParser
|
65
39
|
end
|
66
40
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
blocks = buf.split( /#{FEED_NEXT}/ )
|
73
|
-
## pp blocks
|
74
|
-
|
75
|
-
## 1st block is feed meta data
|
76
|
-
block1st = blocks.shift ## get/remove 1st block from blocks
|
77
|
-
feed_metadata = YAML.load( block1st.strip )
|
78
|
-
|
79
|
-
feed_items = []
|
80
|
-
blocks.each do |block|
|
81
|
-
### note: do NOT use split e.g.--- is used by markdown
|
82
|
-
## only search for first --- to split (all others get ignored)
|
83
|
-
## todo: make three dashes --- (3) not hard-coded (allow more)
|
84
|
-
|
85
|
-
s2 = StringScanner.new( block )
|
86
|
-
|
87
|
-
item_metadata = s2.scan_until( /(?=#{FEED_META})/ )
|
88
|
-
item_metadata = item_metadata.strip # remove leading and trailing whitespace
|
89
|
-
item_metadata = YAML.load( item_metadata ) ## convert to hash with yaml
|
90
|
-
|
91
|
-
feed_meta = s2.scan( /#{FEED_META}/ )
|
92
|
-
|
93
|
-
item_content = s2.rest
|
94
|
-
item_content = item_content.strip # remove leading and trailing whitespace
|
95
|
-
|
96
|
-
feed_items << [item_metadata, item_content]
|
41
|
+
yaml = @text.index( /#{YamlParser::FEED_BEGIN}/ )
|
42
|
+
if yaml && yaml < pos # found e.g. not nil? and match before last?
|
43
|
+
pos = yaml
|
44
|
+
klass = YamlParser
|
97
45
|
end
|
98
46
|
|
99
|
-
|
47
|
+
feed = klass.parse( @text )
|
48
|
+
feed
|
100
49
|
end # method parse
|
101
50
|
|
102
|
-
|
103
51
|
end # class Parser
|
104
52
|
|
105
53
|
end # module Feedtxt
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Feedtxt
|
4
|
+
|
5
|
+
|
6
|
+
class IniParser
|
7
|
+
|
8
|
+
include LogUtils::Logging
|
9
|
+
|
10
|
+
|
11
|
+
### convenience class/factory method
|
12
|
+
def self.parse( text, opts={} )
|
13
|
+
self.new( text ).parse
|
14
|
+
end
|
15
|
+
|
16
|
+
### Note: lets keep/use same API as RSS::Parser for now
|
17
|
+
def initialize( text )
|
18
|
+
@text = text
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
## note:
|
24
|
+
## regex excape bracket: [ to \[
|
25
|
+
## \\ needs to get escaped twice e.g. (\\ becomes \)
|
26
|
+
## e.g. [>>> or [>>>>>
|
27
|
+
FEED_BEGIN = "^[ ]*\\[>>>+[ ]*$" ## note: allow leading n trailing spaces; allow 3 or more brackets
|
28
|
+
## e.g. <<<] or <<<<<<]
|
29
|
+
FEED_END = "^[ ]*<<<+\\][ ]*$" ## note: allow leading n trailing spaces; allow 3 or more brackets
|
30
|
+
|
31
|
+
## e.g.</> or <<</>>>
|
32
|
+
FEED_NEXT = "^[ ]*<+/>+[ ]*$" ## pass 1: split/break up blocks
|
33
|
+
## e.g. --- or -----
|
34
|
+
FEED_META = "^[ ]*---+[ ]*$" ## pass 2: break up item into metadata and content block
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
def parse
|
39
|
+
|
40
|
+
## find start marker e.g. [>>>
|
41
|
+
## use regex - allow three or more >>>>>> or <<<<<<
|
42
|
+
## allow spaces before and after
|
43
|
+
|
44
|
+
s = StringScanner.new( @text )
|
45
|
+
|
46
|
+
prolog = s.scan_until( /(?=#{FEED_BEGIN})/ )
|
47
|
+
## pp prolog
|
48
|
+
|
49
|
+
feed_begin = s.scan( /#{FEED_BEGIN}/ )
|
50
|
+
if feed_begin.empty? ## use blank? why? why not??
|
51
|
+
## nothing found return empty array for now; return nil - why? why not?
|
52
|
+
puts "warn !!! no begin marker found e.g. |>>>"
|
53
|
+
return []
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
buf = s.scan_until( /(?=#{FEED_END})/ )
|
58
|
+
buf = buf.strip # remove leading and trailing whitespace
|
59
|
+
|
60
|
+
feed_end = s.scan( /#{FEED_END}/ )
|
61
|
+
if feed_end.empty? ## use blank? why? why not??
|
62
|
+
## nothing found return empty array for now; return nil - why? why not?
|
63
|
+
puts "warn !!! no end marker found e.g. <<<|"
|
64
|
+
return []
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
####
|
69
|
+
## pass 1: split blocks by </>
|
70
|
+
### note: allows <<<</>>>>
|
71
|
+
|
72
|
+
blocks = buf.split( /#{FEED_NEXT}/ )
|
73
|
+
## pp blocks
|
74
|
+
|
75
|
+
## 1st block is feed meta data
|
76
|
+
block1st = blocks.shift ## get/remove 1st block from blocks
|
77
|
+
block1st = block1st.strip ## strip leading and trailing whitespace
|
78
|
+
feed_metadata = INI.load( block1st )
|
79
|
+
|
80
|
+
feed_items = []
|
81
|
+
blocks.each do |block|
|
82
|
+
### note: do NOT use split e.g.--- is used by markdown
|
83
|
+
## only search for first --- to split (all others get ignored)
|
84
|
+
## todo: make three dashes --- (3) not hard-coded (allow more)
|
85
|
+
|
86
|
+
s2 = StringScanner.new( block )
|
87
|
+
|
88
|
+
item_metadata = s2.scan_until( /(?=#{FEED_META})/ )
|
89
|
+
item_metadata = item_metadata.strip # remove leading and trailing whitespace
|
90
|
+
item_metadata = INI.load( item_metadata ) ## convert to hash with inifile parser
|
91
|
+
|
92
|
+
feed_meta = s2.scan( /#{FEED_META}/ )
|
93
|
+
|
94
|
+
item_content = s2.rest
|
95
|
+
item_content = item_content.strip # remove leading and trailing whitespace
|
96
|
+
|
97
|
+
feed_items << [item_metadata, item_content]
|
98
|
+
end
|
99
|
+
|
100
|
+
[ feed_metadata, feed_items ]
|
101
|
+
end # method parse
|
102
|
+
|
103
|
+
|
104
|
+
end # class IniParser
|
105
|
+
|
106
|
+
end # module Feedtxt
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Feedtxt
|
4
|
+
|
5
|
+
|
6
|
+
class JsonParser
|
7
|
+
|
8
|
+
include LogUtils::Logging
|
9
|
+
|
10
|
+
|
11
|
+
### convenience class/factory method
|
12
|
+
def self.parse( text, opts={} )
|
13
|
+
self.new( text ).parse
|
14
|
+
end
|
15
|
+
|
16
|
+
### Note: lets keep/use same API as RSS::Parser for now
|
17
|
+
def initialize( text )
|
18
|
+
@text = text
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
## note:
|
24
|
+
## regex excape pipe: | to \|
|
25
|
+
## \\ needs to get escaped twice e.g. (\\ becomes \)
|
26
|
+
## e.g. |{ or |{{{
|
27
|
+
FEED_BEGIN = "^[ ]*\\|{+[ ]*$" ## note: allow leading n trailing spaces; allow 3 or more brackets
|
28
|
+
## e.g. }| or }}}|
|
29
|
+
FEED_END = "^[ ]*}+\\|[ ]*$" ## note: allow leading n trailing spaces; allow 3 or more brackets
|
30
|
+
|
31
|
+
## e.g.}/{ or }}}/{{{
|
32
|
+
## todo/check: also allow }///{ or } /// { why,why not?
|
33
|
+
FEED_NEXT = "^[ ]*}+/{+[ ]*$" ## pass 1: split/break up blocks
|
34
|
+
|
35
|
+
## e.g. }---{ or }}}---{{{ or }-{
|
36
|
+
## todo/check: also allow }.{ with dot why? why not?
|
37
|
+
## also allow } - { or } ---- { why? why not?
|
38
|
+
FEED_META = "^[ ]*}+-+{+[ ]*$" ## pass 2: break up item into metadata and content block
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
def parse
|
43
|
+
|
44
|
+
## find start marker e.g. |>>>
|
45
|
+
## use regex - allow three or more >>>>>> or <<<<<<
|
46
|
+
## allow spaces before and after
|
47
|
+
|
48
|
+
s = StringScanner.new( @text )
|
49
|
+
|
50
|
+
prolog = s.scan_until( /(?=#{FEED_BEGIN})/ )
|
51
|
+
## pp prolog
|
52
|
+
|
53
|
+
feed_begin = s.scan( /#{FEED_BEGIN}/ )
|
54
|
+
if feed_begin.empty? ## use blank? why? why not??
|
55
|
+
## nothing found return empty array for now; return nil - why? why not?
|
56
|
+
puts "warn !!! no begin marker found e.g. |>>>"
|
57
|
+
return []
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
buf = s.scan_until( /(?=#{FEED_END})/ )
|
62
|
+
buf = buf.strip # remove leading and trailing whitespace
|
63
|
+
|
64
|
+
feed_end = s.scan( /#{FEED_END}/ )
|
65
|
+
if feed_end.empty? ## use blank? why? why not??
|
66
|
+
## nothing found return empty array for now; return nil - why? why not?
|
67
|
+
puts "warn !!! no end marker found e.g. <<<|"
|
68
|
+
return []
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
####
|
73
|
+
## pass 1: split blocks by }/{
|
74
|
+
### note: allows }}}/{{{
|
75
|
+
|
76
|
+
blocks = buf.split( /#{FEED_NEXT}/ )
|
77
|
+
## pp blocks
|
78
|
+
|
79
|
+
## 1st block is feed meta data
|
80
|
+
block1st = blocks.shift ## get/remove 1st block from blocks
|
81
|
+
block1st = block1st.strip # remove leading and trailing whitespaces
|
82
|
+
feed_metadata = JSON.parse( "{ #{block1st} }" )
|
83
|
+
|
84
|
+
feed_items = []
|
85
|
+
blocks.each do |block|
|
86
|
+
### note: do NOT use split e.g.--- is used by markdown
|
87
|
+
## only search for first --- to split (all others get ignored)
|
88
|
+
## todo: make three dashes --- (3) not hard-coded (allow more)
|
89
|
+
|
90
|
+
s2 = StringScanner.new( block )
|
91
|
+
|
92
|
+
item_metadata = s2.scan_until( /(?=#{FEED_META})/ )
|
93
|
+
item_metadata = item_metadata.strip # remove leading and trailing whitespace
|
94
|
+
item_metadata = JSON.parse( "{ #{item_metadata} }" ) ## convert to hash with yaml
|
95
|
+
|
96
|
+
feed_meta = s2.scan( /#{FEED_META}/ )
|
97
|
+
|
98
|
+
item_content = s2.rest
|
99
|
+
item_content = item_content.strip # remove leading and trailing whitespace
|
100
|
+
|
101
|
+
feed_items << [item_metadata, item_content]
|
102
|
+
end
|
103
|
+
|
104
|
+
[ feed_metadata, feed_items ]
|
105
|
+
end # method parse
|
106
|
+
|
107
|
+
|
108
|
+
end # class JsonParser
|
109
|
+
|
110
|
+
end # module Feedtxt
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Feedtxt
|
4
|
+
|
5
|
+
|
6
|
+
class YamlParser
|
7
|
+
|
8
|
+
include LogUtils::Logging
|
9
|
+
|
10
|
+
|
11
|
+
### convenience class/factory method
|
12
|
+
def self.parse( text, opts={} )
|
13
|
+
self.new( text ).parse
|
14
|
+
end
|
15
|
+
|
16
|
+
### Note: lets keep/use same API as RSS::Parser for now
|
17
|
+
def initialize( text )
|
18
|
+
@text = text
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
## note:
|
24
|
+
## regex excape pipe: | to \|
|
25
|
+
## \\ needs to get escaped twice e.g. (\\ becomes \)
|
26
|
+
## e.g. |>>> or |>>>>>
|
27
|
+
FEED_BEGIN = "^[ ]*\\|>>>+[ ]*$" ## note: allow leading n trailing spaces; allow 3 or more brackets
|
28
|
+
## e.g. <<<| or <<<<<<|
|
29
|
+
FEED_END = "^[ ]*<<<+\\|[ ]*$" ## note: allow leading n trailing spaces; allow 3 or more brackets
|
30
|
+
|
31
|
+
## e.g.</> or <<</>>>
|
32
|
+
FEED_NEXT = "^[ ]*<+/>+[ ]*$" ## pass 1: split/break up blocks
|
33
|
+
## e.g. --- or -----
|
34
|
+
FEED_META = "^[ ]*---+[ ]*$" ## pass 2: break up item into metadata and content block
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
def parse
|
39
|
+
|
40
|
+
## find start marker e.g. |>>>
|
41
|
+
## use regex - allow three or more >>>>>> or <<<<<<
|
42
|
+
## allow spaces before and after
|
43
|
+
|
44
|
+
s = StringScanner.new( @text )
|
45
|
+
|
46
|
+
prolog = s.scan_until( /(?=#{FEED_BEGIN})/ )
|
47
|
+
## pp prolog
|
48
|
+
|
49
|
+
feed_begin = s.scan( /#{FEED_BEGIN}/ )
|
50
|
+
if feed_begin.empty? ## use blank? why? why not??
|
51
|
+
## nothing found return empty array for now; return nil - why? why not?
|
52
|
+
puts "warn !!! no begin marker found e.g. |>>>"
|
53
|
+
return []
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
buf = s.scan_until( /(?=#{FEED_END})/ )
|
58
|
+
buf = buf.strip # remove leading and trailing whitespace
|
59
|
+
|
60
|
+
feed_end = s.scan( /#{FEED_END}/ )
|
61
|
+
if feed_end.empty? ## use blank? why? why not??
|
62
|
+
## nothing found return empty array for now; return nil - why? why not?
|
63
|
+
puts "warn !!! no end marker found e.g. <<<|"
|
64
|
+
return []
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
####
|
69
|
+
## pass 1: split blocks by </>
|
70
|
+
### note: allows <<<</>>>>
|
71
|
+
|
72
|
+
blocks = buf.split( /#{FEED_NEXT}/ )
|
73
|
+
## pp blocks
|
74
|
+
|
75
|
+
## 1st block is feed meta data
|
76
|
+
block1st = blocks.shift ## get/remove 1st block from blocks
|
77
|
+
block1st = block1st.strip ## strip leading and trailing whitespace
|
78
|
+
feed_metadata = YAML.load( block1st )
|
79
|
+
|
80
|
+
feed_items = []
|
81
|
+
blocks.each do |block|
|
82
|
+
### note: do NOT use split e.g.--- is used by markdown
|
83
|
+
## only search for first --- to split (all others get ignored)
|
84
|
+
## todo: make three dashes --- (3) not hard-coded (allow more)
|
85
|
+
|
86
|
+
s2 = StringScanner.new( block )
|
87
|
+
|
88
|
+
item_metadata = s2.scan_until( /(?=#{FEED_META})/ )
|
89
|
+
item_metadata = item_metadata.strip # remove leading and trailing whitespace
|
90
|
+
item_metadata = YAML.load( item_metadata ) ## convert to hash with yaml
|
91
|
+
|
92
|
+
feed_meta = s2.scan( /#{FEED_META}/ )
|
93
|
+
|
94
|
+
item_content = s2.rest
|
95
|
+
item_content = item_content.strip # remove leading and trailing whitespace
|
96
|
+
|
97
|
+
feed_items << [item_metadata, item_content]
|
98
|
+
end
|
99
|
+
|
100
|
+
[ feed_metadata, feed_items ]
|
101
|
+
end # method parse
|
102
|
+
|
103
|
+
|
104
|
+
end # class YamlParser
|
105
|
+
|
106
|
+
end # module Feedtxt
|
data/lib/feedtxt/version.rb
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
[>>>
|
2
|
+
title = My Example Feed
|
3
|
+
home_page_url = https://example.org/
|
4
|
+
feed_url = https://example.org/feed.txt
|
5
|
+
</>
|
6
|
+
id = 2
|
7
|
+
url = https://example.org/second-item
|
8
|
+
---
|
9
|
+
This is a second item.
|
10
|
+
</>
|
11
|
+
id = 1
|
12
|
+
url = https://example.org/initial-post
|
13
|
+
---
|
14
|
+
Hello, world!
|
15
|
+
<<<]
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|{
|
2
|
+
"title": "My Example Feed",
|
3
|
+
"home_page_url": "https://example.org/",
|
4
|
+
"feed_url": "https://example.org/feed.txt"
|
5
|
+
}/{
|
6
|
+
"id": "2",
|
7
|
+
"url": "https://example.org/second-item"
|
8
|
+
}-{
|
9
|
+
This is a second item.
|
10
|
+
}/{
|
11
|
+
"id": "1",
|
12
|
+
"url": "https://example.org/initial-post"
|
13
|
+
}-{
|
14
|
+
Hello, world!
|
15
|
+
}|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
[>>>
|
2
|
+
comment = This is a podcast feed. You can add this feed to your podcast client using the following URL: http://therecord.co/feed.json
|
3
|
+
title = The Record
|
4
|
+
home_page_url = http://therecord.co/
|
5
|
+
feed_url = http://therecord.co/feed.txt
|
6
|
+
</>
|
7
|
+
id = http://therecord.co/chris-parrish
|
8
|
+
title = Special #1 - Chris Parrish
|
9
|
+
url = http://therecord.co/chris-parrish
|
10
|
+
summary = Brent interviews Chris Parrish, co-host of The Record and one-half of Aged & Distilled.
|
11
|
+
published = 2014-05-09T14:04:00-07:00
|
12
|
+
[attachments]
|
13
|
+
url = http://therecord.co/downloads/The-Record-sp1e1-ChrisParrish.m4a
|
14
|
+
mime_type = audio/x-m4a
|
15
|
+
size_in_bytes = 89970236
|
16
|
+
duration_in_seconds = 6629
|
17
|
+
---
|
18
|
+
Chris has worked at [Adobe][1] and as a founder of Rogue Sheep, which won an Apple Design Award for Postage.
|
19
|
+
Chris's new company is Aged & Distilled with Guy English - which shipped [Napkin](2),
|
20
|
+
a Mac app for visual collaboration. Chris is also the co-host of The Record.
|
21
|
+
He lives on [Bainbridge Island][3], a quick ferry ride from Seattle.
|
22
|
+
|
23
|
+
[1]: http://adobe.com/
|
24
|
+
[2]: http://aged-and-distilled.com/napkin/
|
25
|
+
[3]: http://www.ci.bainbridge-isl.wa.us/
|
26
|
+
<<<]
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|{
|
2
|
+
"comment": "This is a podcast feed. You can add this feed to your podcast client using the following URL: http://therecord.co/feed.json",
|
3
|
+
"title": "The Record",
|
4
|
+
"home_page_url": "http://therecord.co/",
|
5
|
+
"feed_url": "http://therecord.co/feed.txt"
|
6
|
+
}/{
|
7
|
+
"id": "http://therecord.co/chris-parrish",
|
8
|
+
"title": "Special #1 - Chris Parrish",
|
9
|
+
"url": "http://therecord.co/chris-parrish",
|
10
|
+
"summary": "Brent interviews Chris Parrish, co-host of The Record and one-half of Aged & Distilled.",
|
11
|
+
"published": "2014-05-09T14:04:00-07:00",
|
12
|
+
"attachments": [
|
13
|
+
{
|
14
|
+
"url": "http://therecord.co/downloads/The-Record-sp1e1-ChrisParrish.m4a",
|
15
|
+
"mime_type": "audio/x-m4a",
|
16
|
+
"size_in_bytes": 89970236,
|
17
|
+
"duration_in_seconds": 6629
|
18
|
+
}
|
19
|
+
]
|
20
|
+
}-{
|
21
|
+
Chris has worked at [Adobe][1] and as a founder of Rogue Sheep, which won an Apple Design Award for Postage.
|
22
|
+
Chris's new company is Aged & Distilled with Guy English - which shipped [Napkin](2),
|
23
|
+
a Mac app for visual collaboration. Chris is also the co-host of The Record.
|
24
|
+
He lives on [Bainbridge Island][3], a quick ferry ride from Seattle.
|
25
|
+
|
26
|
+
[1]: http://adobe.com/
|
27
|
+
[2]: http://aged-and-distilled.com/napkin/
|
28
|
+
[3]: http://www.ci.bainbridge-isl.wa.us/
|
29
|
+
}|
|
data/test/test_ini.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
###
|
2
|
+
# to run use
|
3
|
+
# ruby -I ./lib -I ./test test/test_ini.rb
|
4
|
+
# or better
|
5
|
+
# rake test
|
6
|
+
|
7
|
+
require 'helper'
|
8
|
+
|
9
|
+
|
10
|
+
class TestIni < MiniTest::Test
|
11
|
+
|
12
|
+
def test_example
|
13
|
+
|
14
|
+
text = read_text( 'spec/example.ini' )
|
15
|
+
pp text
|
16
|
+
|
17
|
+
exp = [
|
18
|
+
{"title"=>"My Example Feed",
|
19
|
+
"home_page_url"=>"https://example.org/",
|
20
|
+
"feed_url"=>"https://example.org/feed.txt"},
|
21
|
+
[[
|
22
|
+
{"id"=>"2", "url"=>"https://example.org/second-item"},
|
23
|
+
"This is a second item."
|
24
|
+
],
|
25
|
+
[
|
26
|
+
{"id"=>"1", "url"=>"https://example.org/initial-post"},
|
27
|
+
"Hello, world!"
|
28
|
+
]]]
|
29
|
+
|
30
|
+
assert_equal exp, Feedtxt::IniParser.parse( text )
|
31
|
+
assert_equal exp, Feedtxt.parse( text ) ## try shortcut alias too
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_podcast
|
35
|
+
|
36
|
+
text = read_text( 'spec/podcast.ini' )
|
37
|
+
pp text
|
38
|
+
|
39
|
+
exp = [{"comment"=>
|
40
|
+
"This is a podcast feed. You can add this feed to your podcast client using the following URL: http://therecord.co/feed.json",
|
41
|
+
"title"=>"The Record",
|
42
|
+
"home_page_url"=>"http://therecord.co/",
|
43
|
+
"feed_url"=>"http://therecord.co/feed.txt"},
|
44
|
+
[[{"id"=>"http://therecord.co/chris-parrish",
|
45
|
+
"title"=>"Special",
|
46
|
+
"url"=>"http://therecord.co/chris-parrish",
|
47
|
+
"summary"=>
|
48
|
+
"Brent interviews Chris Parrish, co-host of The Record and one-half of Aged & Distilled.",
|
49
|
+
"published"=>"2014-05-09T14:04:00-07:00",
|
50
|
+
"attachments"=>
|
51
|
+
{"url"=>"http://therecord.co/downloads/The-Record-sp1e1-ChrisParrish.m4a",
|
52
|
+
"mime_type"=>"audio/x-m4a",
|
53
|
+
"size_in_bytes"=>"89970236",
|
54
|
+
"duration_in_seconds"=>"6629"}},
|
55
|
+
"Chris has worked at [Adobe][1] and as a founder of Rogue Sheep, which won an Apple Design Award for Postage.\nChris's new company is Aged & Distilled with Guy English - which shipped [Napkin](2),\na Mac app for visual collaboration. Chris is also the co-host of The Record.\nHe lives on [Bainbridge Island][3], a quick ferry ride from Seattle.\n\n[1]: http://adobe.com/\n[2]: http://aged-and-distilled.com/napkin/\n[3]: http://www.ci.bainbridge-isl.wa.us/"]]]
|
56
|
+
|
57
|
+
assert_equal exp, Feedtxt::IniParser.parse( text )
|
58
|
+
assert_equal exp, Feedtxt.parse( text ) ## try shortcut alias too
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
end # class TestIni
|
data/test/test_json.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
###
|
2
|
+
# to run use
|
3
|
+
# ruby -I ./lib -I ./test test/test_json.rb
|
4
|
+
# or better
|
5
|
+
# rake test
|
6
|
+
|
7
|
+
require 'helper'
|
8
|
+
|
9
|
+
|
10
|
+
class TestJson < MiniTest::Test
|
11
|
+
|
12
|
+
def test_example
|
13
|
+
|
14
|
+
text = read_text( 'spec/example.json' )
|
15
|
+
pp text
|
16
|
+
|
17
|
+
exp = [
|
18
|
+
{"title"=>"My Example Feed",
|
19
|
+
"home_page_url"=>"https://example.org/",
|
20
|
+
"feed_url"=>"https://example.org/feed.txt"},
|
21
|
+
[[
|
22
|
+
{"id"=>"2", "url"=>"https://example.org/second-item"},
|
23
|
+
"This is a second item."
|
24
|
+
],
|
25
|
+
[
|
26
|
+
{"id"=>"1", "url"=>"https://example.org/initial-post"},
|
27
|
+
"Hello, world!"
|
28
|
+
]]]
|
29
|
+
|
30
|
+
assert_equal exp, Feedtxt::JsonParser.parse( text )
|
31
|
+
assert_equal exp, Feedtxt.parse( text ) ## try shortcut alias too
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_podcast
|
35
|
+
|
36
|
+
text = read_text( 'spec/podcast.json' )
|
37
|
+
pp text
|
38
|
+
|
39
|
+
exp =[{"comment"=>
|
40
|
+
"This is a podcast feed. You can add this feed to your podcast client using the following URL: http://therecord.co/feed.json",
|
41
|
+
"title"=>"The Record",
|
42
|
+
"home_page_url"=>"http://therecord.co/",
|
43
|
+
"feed_url"=>"http://therecord.co/feed.txt"},
|
44
|
+
[[{"id"=>"http://therecord.co/chris-parrish",
|
45
|
+
"title"=>"Special #1 - Chris Parrish",
|
46
|
+
"url"=>"http://therecord.co/chris-parrish",
|
47
|
+
"summary"=>
|
48
|
+
"Brent interviews Chris Parrish, co-host of The Record and one-half of Aged & Distilled.",
|
49
|
+
"published"=> "2014-05-09T14:04:00-07:00",
|
50
|
+
"attachments"=>
|
51
|
+
[{"url"=>
|
52
|
+
"http://therecord.co/downloads/The-Record-sp1e1-ChrisParrish.m4a",
|
53
|
+
"mime_type"=>"audio/x-m4a",
|
54
|
+
"size_in_bytes"=>89970236,
|
55
|
+
"duration_in_seconds"=>6629}]},
|
56
|
+
"Chris has worked at [Adobe][1] and as a founder of Rogue Sheep, which won an Apple Design Award for Postage.\nChris's new company is Aged & Distilled with Guy English - which shipped [Napkin](2),\na Mac app for visual collaboration. Chris is also the co-host of The Record.\nHe lives on [Bainbridge Island][3], a quick ferry ride from Seattle.\n\n[1]: http://adobe.com/\n[2]: http://aged-and-distilled.com/napkin/\n[3]: http://www.ci.bainbridge-isl.wa.us/"]]]
|
57
|
+
|
58
|
+
assert_equal exp, Feedtxt::JsonParser.parse( text )
|
59
|
+
assert_equal exp, Feedtxt.parse( text ) ## try shortcut alias too
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
end # class TestYaml
|
data/test/test_yaml.rb
CHANGED
@@ -27,7 +27,7 @@ class TestYaml < MiniTest::Test
|
|
27
27
|
"Hello, world!"
|
28
28
|
]]]
|
29
29
|
|
30
|
-
assert_equal exp, Feedtxt::
|
30
|
+
assert_equal exp, Feedtxt::YamlParser.parse( text )
|
31
31
|
assert_equal exp, Feedtxt.parse( text ) ## try shortcut alias too
|
32
32
|
end
|
33
33
|
|
@@ -55,7 +55,7 @@ class TestYaml < MiniTest::Test
|
|
55
55
|
"duration_in_seconds"=>6629}]},
|
56
56
|
"Chris has worked at [Adobe][1] and as a founder of Rogue Sheep, which won an Apple Design Award for Postage.\nChris's new company is Aged & Distilled with Guy English - which shipped [Napkin](2),\na Mac app for visual collaboration. Chris is also the co-host of The Record.\nHe lives on [Bainbridge Island][3], a quick ferry ride from Seattle.\n\n[1]: http://adobe.com/\n[2]: http://aged-and-distilled.com/napkin/\n[3]: http://www.ci.bainbridge-isl.wa.us/"]]]
|
57
57
|
|
58
|
-
assert_equal exp, Feedtxt::
|
58
|
+
assert_equal exp, Feedtxt::YamlParser.parse( text )
|
59
59
|
assert_equal exp, Feedtxt.parse( text ) ## try shortcut alias too
|
60
60
|
end
|
61
61
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: feedtxt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-06-
|
11
|
+
date: 2017-06-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rdoc
|
@@ -54,10 +54,19 @@ files:
|
|
54
54
|
- Rakefile
|
55
55
|
- lib/feedtxt.rb
|
56
56
|
- lib/feedtxt/parser.rb
|
57
|
+
- lib/feedtxt/parser/ini.rb
|
58
|
+
- lib/feedtxt/parser/json.rb
|
59
|
+
- lib/feedtxt/parser/yaml.rb
|
57
60
|
- lib/feedtxt/version.rb
|
61
|
+
- test/feeds/spec/example.ini.txt
|
62
|
+
- test/feeds/spec/example.json.txt
|
58
63
|
- test/feeds/spec/example.yaml.txt
|
64
|
+
- test/feeds/spec/podcast.ini.txt
|
65
|
+
- test/feeds/spec/podcast.json.txt
|
59
66
|
- test/feeds/spec/podcast.yaml.txt
|
60
67
|
- test/helper.rb
|
68
|
+
- test/test_ini.rb
|
69
|
+
- test/test_json.rb
|
61
70
|
- test/test_scanner.rb
|
62
71
|
- test/test_yaml.rb
|
63
72
|
homepage: https://github.com/feedtxt/feedtxt
|