feedtxt 0.2.0 → 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.
- 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
|