infoboxer 0.2.8 → 0.3.0.pre
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/.yardopts +1 -0
- data/CHANGELOG.md +18 -0
- data/infoboxer.gemspec +0 -1
- data/lib/infoboxer.rb +4 -6
- data/lib/infoboxer/media_wiki.rb +26 -16
- data/lib/infoboxer/media_wiki/traits.rb +66 -20
- data/lib/infoboxer/navigation.rb +16 -1
- data/lib/infoboxer/navigation/lookup.rb +11 -1
- data/lib/infoboxer/navigation/sections.rb +30 -6
- data/lib/infoboxer/navigation/selector.rb +14 -5
- data/lib/infoboxer/navigation/wikipath.rb +32 -0
- data/lib/infoboxer/parser.rb +0 -1
- data/lib/infoboxer/parser/inline.rb +42 -2
- data/lib/infoboxer/parser/paragraphs.rb +1 -1
- data/lib/infoboxer/parser/template.rb +8 -3
- data/lib/infoboxer/parser/util.rb +1 -0
- data/lib/infoboxer/templates/base.rb +4 -2
- data/lib/infoboxer/tree.rb +1 -1
- data/lib/infoboxer/tree/compound.rb +2 -3
- data/lib/infoboxer/tree/gallery.rb +12 -0
- data/lib/infoboxer/tree/image.rb +3 -3
- data/lib/infoboxer/tree/inline.rb +2 -2
- data/lib/infoboxer/tree/linkable.rb +4 -1
- data/lib/infoboxer/tree/math.rb +0 -3
- data/lib/infoboxer/tree/node.rb +6 -4
- data/lib/infoboxer/tree/nodes.rb +32 -4
- data/lib/infoboxer/tree/paragraphs.rb +1 -1
- data/lib/infoboxer/tree/table.rb +9 -17
- data/lib/infoboxer/tree/template.rb +15 -5
- data/lib/infoboxer/tree/text.rb +1 -1
- data/lib/infoboxer/tree/wikilink.rb +14 -7
- data/lib/infoboxer/version.rb +4 -3
- data/lib/infoboxer/wiki_path.rb +94 -0
- metadata +7 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67b07bcee67c15aeb316e2eccf75e85a26cb43ae
|
4
|
+
data.tar.gz: 91f3253151ff816a83b4ffb8f7937c878a5a3ed8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f7dd9aa6545853dbf827c1b0b7857b2b5654caf33dcbfcaf4c2194709379f7e73d0978776675fd6265cbf83d88a814332626945db1b45c9562e54a01fa486a55
|
7
|
+
data.tar.gz: fa70f671da0d9acd7a0a131d8119a775c1ff762d7d2b640096f448678b7547477fd0fe93df74c438cd846fa550e33e530999c811bb4b0d4afe347bf058c3321b
|
data/.yardopts
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,23 @@
|
|
1
1
|
# Infoboxer's change log
|
2
2
|
|
3
|
+
## 0.3.1.pre (2017-09-16)
|
4
|
+
|
5
|
+
* Introduce interwiki links following (and proper handling of interwikis, in general);
|
6
|
+
* Add `<gallery>` tag support;
|
7
|
+
* Introduce `Navigation::Selector#===`;
|
8
|
+
* Much more `Enumerable`'s methods supported by `Nodes`;
|
9
|
+
* Lot of small simplifications, cleanups and bugfixes.
|
10
|
+
|
11
|
+
TBH, it should be 0.4.0 or more, but it would be a shame to change versions so fast :) So, at least
|
12
|
+
until it is `-pre`, let it be 0.3.1.
|
13
|
+
|
14
|
+
## 0.3.0 (2017-07-23)
|
15
|
+
|
16
|
+
* Change logic of navigation through templates; now templates contents aren't hidden from global
|
17
|
+
lookups. While sometimes leading to less impressive demos, this approach proved itself to be more
|
18
|
+
useful for production.
|
19
|
+
* Introduce WikiPath query language as an alternative to series of lookups.
|
20
|
+
|
3
21
|
## 0.2.8 (2017-05-11)
|
4
22
|
|
5
23
|
* Switch to MediaWiktory 0.1.0 + some subsequent cleanup of internal logic;
|
data/infoboxer.gemspec
CHANGED
data/lib/infoboxer.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require 'procme'
|
4
|
-
|
5
3
|
# Main client module for entire infoboxer functionality. If you're lucky,
|
6
4
|
# there's no other classes/modules you need to instantiate or call
|
7
5
|
# directly. You just do:
|
@@ -74,8 +72,8 @@ module Infoboxer
|
|
74
72
|
end
|
75
73
|
|
76
74
|
# Includeable version of {Infoboxer.wiki}
|
77
|
-
def wiki(api_url, options
|
78
|
-
wikis[api_url] ||= MediaWiki.new(api_url, options
|
75
|
+
def wiki(api_url, **options)
|
76
|
+
wikis[api_url] ||= MediaWiki.new(api_url, options)
|
79
77
|
end
|
80
78
|
|
81
79
|
class << self
|
@@ -170,7 +168,7 @@ module Infoboxer
|
|
170
168
|
end
|
171
169
|
|
172
170
|
WIKIMEDIA_PROJECTS.each do |name, domain|
|
173
|
-
define_method name do |lang = 'en', options
|
171
|
+
define_method name do |lang = 'en', **options|
|
174
172
|
lang, options = 'en', lang if lang.is_a?(Hash)
|
175
173
|
|
176
174
|
wiki("https://#{lang}.#{domain}/w/api.php", options)
|
@@ -180,7 +178,7 @@ module Infoboxer
|
|
180
178
|
alias_method :wp, :wikipedia
|
181
179
|
|
182
180
|
WIKIMEDIA_COMMONS.each do |name, domain|
|
183
|
-
define_method name do
|
181
|
+
define_method name do |**options|
|
184
182
|
wiki("https://#{domain}/w/api.php", options)
|
185
183
|
end
|
186
184
|
end
|
data/lib/infoboxer/media_wiki.rb
CHANGED
@@ -47,15 +47,14 @@ module Infoboxer
|
|
47
47
|
# for it, as well as shortcuts for some well-known wikis, like
|
48
48
|
# {Infoboxer.wikipedia}.
|
49
49
|
#
|
50
|
-
# @param api_base_url URL of `api.php` file in your MediaWiki
|
50
|
+
# @param api_base_url [String] URL of `api.php` file in your MediaWiki
|
51
51
|
# installation. Typically, its `<domain>/w/api.php`, but can vary
|
52
52
|
# in different wikis.
|
53
|
-
# @param
|
54
|
-
|
55
|
-
def initialize(api_base_url, options = {})
|
53
|
+
# @param user_agent [String] (also aliased as `:ua`) Custom User-Agent header.
|
54
|
+
def initialize(api_base_url, ua: nil, user_agent: ua)
|
56
55
|
@api_base_url = Addressable::URI.parse(api_base_url)
|
57
|
-
@client = MediaWiktory::Wikipedia::Api.new(api_base_url, user_agent: user_agent(
|
58
|
-
@traits = Traits.get(@api_base_url.host,
|
56
|
+
@client = MediaWiktory::Wikipedia::Api.new(api_base_url, user_agent: user_agent(user_agent))
|
57
|
+
@traits = Traits.get(@api_base_url.host, siteinfo)
|
59
58
|
end
|
60
59
|
|
61
60
|
# Receive "raw" data from Wikipedia (without parsing or wrapping in
|
@@ -123,7 +122,9 @@ module Infoboxer
|
|
123
122
|
# and obtain meaningful results instead of `NoMethodError` or
|
124
123
|
# `SomethingNotFound`.
|
125
124
|
#
|
126
|
-
def get(*titles, prop: [])
|
125
|
+
def get(*titles, prop: [], interwiki: nil)
|
126
|
+
return interwikis(interwiki).get(*titles, prop: prop) if interwiki
|
127
|
+
|
127
128
|
pages = get_h(*titles, prop: prop).values.compact
|
128
129
|
titles.count == 1 ? pages.first : Tree::Nodes[*pages]
|
129
130
|
end
|
@@ -251,17 +252,26 @@ module Infoboxer
|
|
251
252
|
[namespace, titl].join(':')
|
252
253
|
end
|
253
254
|
|
254
|
-
def user_agent(
|
255
|
-
|
255
|
+
def user_agent(custom)
|
256
|
+
custom || self.class.user_agent || UA
|
257
|
+
end
|
258
|
+
|
259
|
+
def siteinfo
|
260
|
+
@siteinfo ||= @client.query.meta(:siteinfo).prop(:namespaces, :namespacealiases, :interwikimap).response.to_h
|
256
261
|
end
|
257
262
|
|
258
|
-
def
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
263
|
+
def interwikis(prefix)
|
264
|
+
@interwikis ||= Hash.new { |h, pre|
|
265
|
+
interwiki = siteinfo['interwikimap'].detect { |iw| iw['prefix'] == prefix } or
|
266
|
+
fail ArgumentError, "Undefined interwiki: #{prefix}"
|
267
|
+
|
268
|
+
# FIXME: fragile, but what can we do?..
|
269
|
+
m = interwiki['url'].match(%r{^(.+)/wiki/\$1$}) or
|
270
|
+
fail ArgumentError, "Interwiki #{interwiki} seems not to be a MediaWiki instance"
|
271
|
+
h[pre] = self.class.new("#{m[1]}/w/api.php") # TODO: copy useragent
|
272
|
+
}
|
273
|
+
|
274
|
+
@interwikis[prefix]
|
265
275
|
end
|
266
276
|
end
|
267
277
|
end
|
@@ -34,9 +34,8 @@ module Infoboxer
|
|
34
34
|
end
|
35
35
|
|
36
36
|
# @private
|
37
|
-
def get(domain,
|
38
|
-
|
39
|
-
cls ? cls.new(options) : Traits.new(options)
|
37
|
+
def get(domain, site_info = {})
|
38
|
+
(Traits.domains[domain] || Traits).new(site_info)
|
40
39
|
end
|
41
40
|
|
42
41
|
# @private
|
@@ -68,18 +67,27 @@ module Infoboxer
|
|
68
67
|
alias_method :default, :new
|
69
68
|
end
|
70
69
|
|
71
|
-
def initialize(
|
72
|
-
@
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
70
|
+
def initialize(site_info = {})
|
71
|
+
@site_info = site_info
|
72
|
+
end
|
73
|
+
|
74
|
+
def namespace?(prefix)
|
75
|
+
known_namespaces.include?(prefix)
|
76
|
+
end
|
77
|
+
|
78
|
+
def interwiki?(prefix)
|
79
|
+
known_interwikis.key?(prefix)
|
80
|
+
end
|
81
|
+
|
82
|
+
# @private
|
83
|
+
def file_namespace
|
84
|
+
@file_namespace ||= ns_aliases('File')
|
79
85
|
end
|
80
86
|
|
81
87
|
# @private
|
82
|
-
|
88
|
+
def category_namespace
|
89
|
+
@category_namespace ||= ns_aliases('Category')
|
90
|
+
end
|
83
91
|
|
84
92
|
# @private
|
85
93
|
def templates
|
@@ -88,16 +96,54 @@ module Infoboxer
|
|
88
96
|
|
89
97
|
private
|
90
98
|
|
91
|
-
def
|
92
|
-
|
93
|
-
|
94
|
-
|
99
|
+
def known_namespaces
|
100
|
+
@known_namespaces ||=
|
101
|
+
if @site_info.empty?
|
102
|
+
STANDARD_NAMESPACES
|
103
|
+
else
|
104
|
+
(@site_info['namespaces'].values + @site_info['namespacealiases']).map { |n| n['*'] }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def known_interwikis
|
109
|
+
@known_interwikis ||=
|
110
|
+
if @site_info.empty?
|
111
|
+
{}
|
112
|
+
else
|
113
|
+
@site_info['interwikimap'].map { |iw| [iw['prefix'], iw] }.to_h
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def ns_aliases(base)
|
118
|
+
return [base] if @site_info.empty?
|
119
|
+
main = @site_info['namespaces'].values.detect { |n| n['canonical'] == base }
|
120
|
+
[base, main['*']] +
|
121
|
+
@site_info['namespacealiases']
|
122
|
+
.select { |a| a['id'] == main['id'] }.flat_map { |n| n['*'] }
|
123
|
+
.compact.uniq
|
95
124
|
end
|
96
125
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
126
|
+
# See https://www.mediawiki.org/wiki/Help:Namespaces#Standard_namespaces
|
127
|
+
STANDARD_NAMESPACES = [
|
128
|
+
'Media', # Direct linking to media files.
|
129
|
+
'Special', # Special (non-editable) pages.
|
130
|
+
'', # (Main)
|
131
|
+
'Talk', # Article discussion.
|
132
|
+
'User', #
|
133
|
+
'User talk', #
|
134
|
+
'Project', # Meta-discussions related to the operation and development of the wiki.
|
135
|
+
'Project talk', #
|
136
|
+
'File', # Metadata for images, videos, sound files and other media.
|
137
|
+
'File talk', #
|
138
|
+
'MediaWiki', # System messages and other important content.
|
139
|
+
'MediaWiki talk', #
|
140
|
+
'Template', # Templates: blocks of text or wikicode that are intended to be transcluded.
|
141
|
+
'Template talk', #
|
142
|
+
'Help', # Help files, instructions and "how-to" guides.
|
143
|
+
'Help talk', #
|
144
|
+
'Category', # Categories: dynamic lists of other pages.
|
145
|
+
'Category talk', #
|
146
|
+
].freeze
|
101
147
|
end
|
102
148
|
end
|
103
149
|
end
|
data/lib/infoboxer/navigation.rb
CHANGED
@@ -38,6 +38,19 @@ module Infoboxer
|
|
38
38
|
#
|
39
39
|
# Look into {Shortcuts::Node} documentation for list of shortcuts.
|
40
40
|
#
|
41
|
+
# ## Wikipath
|
42
|
+
#
|
43
|
+
# WikiPath is XPath-alike query language you can use to navigate the tree:
|
44
|
+
#
|
45
|
+
# ```ruby
|
46
|
+
# document.wikipath('//paragraph//wikilink[namespace=Category]')
|
47
|
+
# ```
|
48
|
+
#
|
49
|
+
# It can look more or less verbose than pure-ruby navigation, but the big advantage of WikiPath
|
50
|
+
# is it is pure data: you can store some paths in YAML file, for example.
|
51
|
+
#
|
52
|
+
# Look at {Wikipath#wikipath #wikipath} method docs for full reference.
|
53
|
+
#
|
41
54
|
# ## Logical structure navigation
|
42
55
|
#
|
43
56
|
# MediaWiki page structure is flat, like HTML's (there's just sequence
|
@@ -62,7 +75,7 @@ module Infoboxer
|
|
62
75
|
# {Sections::Node} for upwards.
|
63
76
|
#
|
64
77
|
module Navigation
|
65
|
-
%w[lookup shortcuts sections].each do |nav|
|
78
|
+
%w[lookup shortcuts sections wikipath].each do |nav|
|
66
79
|
require_relative "navigation/#{nav}"
|
67
80
|
end
|
68
81
|
|
@@ -70,12 +83,14 @@ module Infoboxer
|
|
70
83
|
include Navigation::Lookup::Node
|
71
84
|
include Navigation::Shortcuts::Node
|
72
85
|
include Navigation::Sections::Node
|
86
|
+
include Navigation::Wikipath
|
73
87
|
end
|
74
88
|
|
75
89
|
class Tree::Nodes
|
76
90
|
include Navigation::Lookup::Nodes
|
77
91
|
include Navigation::Shortcuts::Nodes
|
78
92
|
include Navigation::Sections::Nodes
|
93
|
+
include Navigation::Wikipath
|
79
94
|
end
|
80
95
|
|
81
96
|
class Tree::Document
|
@@ -98,9 +98,13 @@ module Infoboxer
|
|
98
98
|
# Selects matching nodes from current node's siblings, which
|
99
99
|
# are above current node in parents children list.
|
100
100
|
|
101
|
+
# @!method lookup_prev_sibling(*selectors, &block)
|
102
|
+
# Selects first matching nodes from current node's siblings, which
|
103
|
+
# are above current node in parents children list.
|
104
|
+
|
101
105
|
# Underscored version of {#matches?}
|
102
106
|
def _matches?(selector)
|
103
|
-
selector
|
107
|
+
selector === self
|
104
108
|
end
|
105
109
|
|
106
110
|
# Underscored version of {#lookup}
|
@@ -136,6 +140,11 @@ module Infoboxer
|
|
136
140
|
prev_siblings._find(selector)
|
137
141
|
end
|
138
142
|
|
143
|
+
# Underscored version of {#lookup_prev_sibling}
|
144
|
+
def _lookup_prev_sibling(selector)
|
145
|
+
prev_siblings.reverse.detect { |n| selector === n }
|
146
|
+
end
|
147
|
+
|
139
148
|
# Underscored version of {#lookup_next_siblings}
|
140
149
|
def _lookup_next_siblings(selector)
|
141
150
|
next_siblings._find(selector)
|
@@ -146,6 +155,7 @@ module Infoboxer
|
|
146
155
|
lookup lookup_children lookup_parents
|
147
156
|
lookup_siblings
|
148
157
|
lookup_next_siblings lookup_prev_siblings
|
158
|
+
lookup_prev_sibling
|
149
159
|
]
|
150
160
|
.map { |sym| [sym, :"_#{sym}"] }
|
151
161
|
.each do |sym, underscored|
|
@@ -81,6 +81,14 @@ module Infoboxer
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
+
def lookup_children(*arg)
|
85
|
+
if arg.include?(:Section)
|
86
|
+
sections.find(*(arg - [:Section]))
|
87
|
+
else
|
88
|
+
super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
84
92
|
private
|
85
93
|
|
86
94
|
def make_sections
|
@@ -115,21 +123,25 @@ module Infoboxer
|
|
115
123
|
#
|
116
124
|
# @return {Tree::Nodes<Section>}
|
117
125
|
def in_sections
|
118
|
-
|
126
|
+
return parent.in_sections unless parent.is_a?(Tree::Document)
|
127
|
+
return @in_sections if @in_sections
|
119
128
|
|
120
129
|
heading =
|
121
|
-
if
|
122
|
-
|
130
|
+
if is_a?(Tree::Heading)
|
131
|
+
lookup_prev_sibling(Tree::Heading, level: level - 1)
|
123
132
|
else
|
124
|
-
|
133
|
+
lookup_prev_sibling(Tree::Heading)
|
125
134
|
end
|
126
|
-
|
135
|
+
unless heading
|
136
|
+
@in_sections = Tree::Nodes[]
|
137
|
+
return @in_sections
|
138
|
+
end
|
127
139
|
|
128
140
|
body = heading.next_siblings
|
129
141
|
.take_while { |n| !n.is_a?(Tree::Heading) || n.level < heading.level }
|
130
142
|
|
131
143
|
section = Section.new(heading, body)
|
132
|
-
Tree::Nodes[section, *heading.in_sections]
|
144
|
+
@in_sections = Tree::Nodes[section, *heading.in_sections]
|
133
145
|
end
|
134
146
|
end
|
135
147
|
|
@@ -145,6 +157,14 @@ module Infoboxer
|
|
145
157
|
make_nodes(map { |n| n.send(sym, *args) })
|
146
158
|
end
|
147
159
|
end
|
160
|
+
|
161
|
+
def lookup_children(*arg)
|
162
|
+
if arg.include?(:Section)
|
163
|
+
sections.find(*(arg - [:Section]))
|
164
|
+
else
|
165
|
+
super
|
166
|
+
end
|
167
|
+
end
|
148
168
|
end
|
149
169
|
|
150
170
|
# Virtual node, representing logical section of the document.
|
@@ -175,6 +195,10 @@ module Infoboxer
|
|
175
195
|
false
|
176
196
|
end
|
177
197
|
|
198
|
+
def inspect
|
199
|
+
"#<#{descr}: #{children.count} nodes>"
|
200
|
+
end
|
201
|
+
|
178
202
|
include Container
|
179
203
|
end
|
180
204
|
end
|
@@ -7,8 +7,6 @@ module Infoboxer
|
|
7
7
|
#
|
8
8
|
# See {Lookup::Node Lookup::Node} for detailed explanation of available selectors.
|
9
9
|
class Selector
|
10
|
-
include ProcMe
|
11
|
-
|
12
10
|
def initialize(*arg, &block)
|
13
11
|
@arg = [arg, block].flatten.compact.map(&method(:sym_to_class))
|
14
12
|
@arg.each do |a|
|
@@ -26,8 +24,8 @@ module Infoboxer
|
|
26
24
|
"#<Selector(#{@arg.map(&:to_s).join(', ')})>"
|
27
25
|
end
|
28
26
|
|
29
|
-
def
|
30
|
-
@arg.all? { |a| arg_matches?(a,
|
27
|
+
def ===(other)
|
28
|
+
@arg.all? { |a| arg_matches?(a, other) }
|
31
29
|
end
|
32
30
|
|
33
31
|
private
|
@@ -45,13 +43,24 @@ module Infoboxer
|
|
45
43
|
when Proc
|
46
44
|
check.call(node)
|
47
45
|
when Hash
|
48
|
-
check.all? { |attr, value|
|
46
|
+
check.all? { |attr, value|
|
47
|
+
node.respond_to?(attr) && value_matches?(value, node.send(attr)) ||
|
48
|
+
node.params.key?(attr) && value_matches?(value, node.params[attr])
|
49
|
+
}
|
49
50
|
when Symbol
|
50
51
|
node.respond_to?(check) && node.send(check)
|
51
52
|
else
|
52
53
|
check === node
|
53
54
|
end
|
54
55
|
end
|
56
|
+
|
57
|
+
def value_matches?(matcher, value)
|
58
|
+
if matcher.is_a?(String) && value.is_a?(String)
|
59
|
+
matcher.casecmp(value).zero?
|
60
|
+
else
|
61
|
+
matcher === value
|
62
|
+
end
|
63
|
+
end
|
55
64
|
end
|
56
65
|
end
|
57
66
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative '../wiki_path'
|
4
|
+
|
5
|
+
module Infoboxer
|
6
|
+
module Navigation
|
7
|
+
module Wikipath
|
8
|
+
# Search nodes inside current by XPath alike query language.
|
9
|
+
#
|
10
|
+
# This feature is experimental, but should work for most of the useful cases.
|
11
|
+
#
|
12
|
+
# Examples of WikiPath:
|
13
|
+
#
|
14
|
+
# ```
|
15
|
+
# /paragraph # direct child of current node, being paragraph
|
16
|
+
# //paragraph # any node in current node's subtree, being paragraph
|
17
|
+
# //template[name=Infobox] # template node in subtree, with name attribute equal to Infobox
|
18
|
+
# //template[name="Infobox country"] # optional quotes are allowed
|
19
|
+
# //template[name=/^Infobox/] # regexes are supported
|
20
|
+
# //wikilink[italic] # node predicates are supported (the same as `lookup(:Wikilink, :italic?)`
|
21
|
+
# //*[italic] # type wildcards are supported
|
22
|
+
# //template[name=/^Infobox/]/var[name=birthday] # series of lookups work
|
23
|
+
# ```
|
24
|
+
#
|
25
|
+
# @param string [String] WikiPath to lookup
|
26
|
+
# @return [Nodes]
|
27
|
+
def wikipath(string)
|
28
|
+
Infoboxer::WikiPath.parse(string).call(self)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/infoboxer/parser.rb
CHANGED
@@ -83,7 +83,7 @@ module Infoboxer
|
|
83
83
|
|
84
84
|
private
|
85
85
|
|
86
|
-
def inline_formatting(match)
|
86
|
+
def inline_formatting(match) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
|
87
87
|
case match
|
88
88
|
when "'''''"
|
89
89
|
BoldItalic.new(short_inline(/'''''/))
|
@@ -109,6 +109,8 @@ module Infoboxer
|
|
109
109
|
reference(Regexp.last_match(1))
|
110
110
|
when /<math>/
|
111
111
|
math
|
112
|
+
when /<gallery([^>]*)>/
|
113
|
+
gallery(Regexp.last_match(1))
|
112
114
|
when '<'
|
113
115
|
html || Text.new(match) # it was not HTML, just accidental <
|
114
116
|
else
|
@@ -126,8 +128,18 @@ module Infoboxer
|
|
126
128
|
caption = inline(/\]\]/)
|
127
129
|
@context.pop_eol_sign
|
128
130
|
end
|
131
|
+
name, namespace = link.split(':', 2).reverse
|
132
|
+
lnk, params =
|
133
|
+
if @context.traits.namespace?(namespace)
|
134
|
+
[link, {namespace: namespace}]
|
135
|
+
elsif @context.traits.interwiki?(namespace)
|
136
|
+
[name, {interwiki: namespace}]
|
137
|
+
else
|
138
|
+
[link, {}]
|
139
|
+
end
|
129
140
|
|
130
|
-
|
141
|
+
puts @context.rest if lnk.nil?
|
142
|
+
Wikilink.new(lnk, caption, **params)
|
131
143
|
end
|
132
144
|
|
133
145
|
# http://en.wikipedia.org/wiki/Help:Link#External_links
|
@@ -159,6 +171,34 @@ module Infoboxer
|
|
159
171
|
Text.new(@context.scan_continued_until(%r{</nowiki>}))
|
160
172
|
end
|
161
173
|
end
|
174
|
+
|
175
|
+
def gallery(tag_rest)
|
176
|
+
params = parse_params(tag_rest)
|
177
|
+
images = []
|
178
|
+
guarded_loop do
|
179
|
+
@context.next! if @context.eol?
|
180
|
+
path = @context.scan_until(%r{</gallery>|\||$})
|
181
|
+
attrs = @context.matched == '|' ? gallery_image_attrs : {}
|
182
|
+
unless path.empty?
|
183
|
+
images << Tree::Image.new(path.sub(/^#{re.file_namespace}/, ''), attrs)
|
184
|
+
end
|
185
|
+
break if @context.matched == '</gallery>'
|
186
|
+
end
|
187
|
+
Gallery.new(images, params)
|
188
|
+
end
|
189
|
+
|
190
|
+
def gallery_image_attrs
|
191
|
+
nodes = []
|
192
|
+
|
193
|
+
guarded_loop do
|
194
|
+
nodes << short_inline(%r{\||</gallery>})
|
195
|
+
break if @context.eol? || @context.matched?(%r{</gallery>})
|
196
|
+
end
|
197
|
+
|
198
|
+
nodes.map(&method(:image_attr))
|
199
|
+
.inject(&:merge)
|
200
|
+
.reject { |_k, v| v.nil? || v.empty? }
|
201
|
+
end
|
162
202
|
end
|
163
203
|
|
164
204
|
require_relative 'image'
|
@@ -29,8 +29,8 @@ module Infoboxer
|
|
29
29
|
|
30
30
|
guarded_loop do
|
31
31
|
@context.next! while @context.eol?
|
32
|
-
if @context.check(/\s*([
|
33
|
-
name = @context.scan(/\s*([
|
32
|
+
if @context.check(/\s*([^=}|<]+)\s*=\s*/)
|
33
|
+
name = @context.scan(/\s*([^=]+)/).strip
|
34
34
|
@context.skip(/\s*=\s*/)
|
35
35
|
else
|
36
36
|
name = num
|
@@ -38,7 +38,7 @@ module Infoboxer
|
|
38
38
|
end
|
39
39
|
log "Variable #{name} found"
|
40
40
|
|
41
|
-
value = long_inline(/\||}}/)
|
41
|
+
value = sanitize_value(long_inline(/\||}}/))
|
42
42
|
|
43
43
|
# it was just empty line otherwise
|
44
44
|
res << Var.new(name.to_s, value) unless value.empty? && name.is_a?(Numeric)
|
@@ -50,6 +50,11 @@ module Infoboxer
|
|
50
50
|
end
|
51
51
|
res
|
52
52
|
end
|
53
|
+
|
54
|
+
def sanitize_value(nodes)
|
55
|
+
nodes.pop if (nodes.last.is_a?(Pre) || nodes.last.is_a?(Text)) && nodes.last.text =~ /^\s*$/ # FIXME: dirty!
|
56
|
+
nodes
|
57
|
+
end
|
53
58
|
end
|
54
59
|
end
|
55
60
|
end
|
@@ -35,9 +35,11 @@ module Infoboxer
|
|
35
35
|
#
|
36
36
|
# Used for {Set} definitions.
|
37
37
|
class Show < Base
|
38
|
-
|
38
|
+
def text
|
39
|
+
unnamed_variables.map(&:text).join(children_separator)
|
40
|
+
end
|
39
41
|
|
40
|
-
|
42
|
+
private
|
41
43
|
|
42
44
|
def children_separator
|
43
45
|
' '
|
data/lib/infoboxer/tree.rb
CHANGED
@@ -63,7 +63,7 @@ module Infoboxer
|
|
63
63
|
require_relative 'tree/nodes'
|
64
64
|
|
65
65
|
%w[text compound inline
|
66
|
-
image html paragraphs list template table ref math
|
66
|
+
image gallery html paragraphs list template table ref math
|
67
67
|
document].each do |type|
|
68
68
|
require_relative "tree/#{type}"
|
69
69
|
end
|
@@ -4,7 +4,7 @@ module Infoboxer
|
|
4
4
|
module Tree
|
5
5
|
# Base class for all nodes with children.
|
6
6
|
class Compound < Node
|
7
|
-
def initialize(children = Nodes.new, params
|
7
|
+
def initialize(children = Nodes.new, **params)
|
8
8
|
super(params)
|
9
9
|
@children = Nodes[*children]
|
10
10
|
@children.each { |c| c.parent = self }
|
@@ -40,8 +40,7 @@ module Infoboxer
|
|
40
40
|
if children.count == 1 && children.first.is_a?(Text)
|
41
41
|
"#{indent(level)}#{children.first.text} <#{descr}>\n"
|
42
42
|
else
|
43
|
-
"#{indent(level)}<#{descr}>\n" +
|
44
|
-
children.map(&call(to_tree: level + 1)).join
|
43
|
+
"#{indent(level)}<#{descr}>\n" + children.map { |c| c.to_tree(level + 1) }.join
|
45
44
|
end
|
46
45
|
end
|
47
46
|
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Infoboxer
|
4
|
+
module Tree
|
5
|
+
# Represents gallery of images (contents of `<gallery>` special tag).
|
6
|
+
#
|
7
|
+
# See [Wikipedia Tutorial](https://en.wikipedia.org/wiki/Help:Gallery_tag)
|
8
|
+
# for explanation of attributes.
|
9
|
+
class Gallery < Compound
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/infoboxer/tree/image.rb
CHANGED
@@ -7,8 +7,8 @@ module Infoboxer
|
|
7
7
|
# See [Wikipedia Tutorial](https://en.wikipedia.org/wiki/Wikipedia:Extended_image_syntax)
|
8
8
|
# for explanation of attributes.
|
9
9
|
class Image < Node
|
10
|
-
def initialize(path,
|
11
|
-
@caption =
|
10
|
+
def initialize(path, caption: nil, **params)
|
11
|
+
@caption = caption
|
12
12
|
super({path: path}.merge(params))
|
13
13
|
end
|
14
14
|
|
@@ -44,7 +44,7 @@ module Infoboxer
|
|
44
44
|
super(level) +
|
45
45
|
if caption && !caption.empty?
|
46
46
|
indent(level + 1) + "caption:\n" +
|
47
|
-
caption.children.map(
|
47
|
+
caption.children.map { |c| c.to_tree(level + 2) }.join
|
48
48
|
else
|
49
49
|
''
|
50
50
|
end
|
@@ -17,8 +17,8 @@ module Infoboxer
|
|
17
17
|
|
18
18
|
# Base class for internal/external links,
|
19
19
|
class Link < Compound
|
20
|
-
def initialize(link, label = nil)
|
21
|
-
super(label || Nodes.new([Text.new(link)]), link: link)
|
20
|
+
def initialize(link, label = nil, **attr)
|
21
|
+
super(label || Nodes.new([Text.new(link)]), link: link, **attr)
|
22
22
|
end
|
23
23
|
|
24
24
|
# @!attribute [r] link
|
@@ -15,7 +15,7 @@ module Infoboxer
|
|
15
15
|
# * {Tree::Nodes#follow} for extracting multiple links at once;
|
16
16
|
# * {MediaWiki#get} for basic information on page extraction.
|
17
17
|
def follow
|
18
|
-
client.get(link)
|
18
|
+
client.get(link, interwiki: interwiki)
|
19
19
|
end
|
20
20
|
|
21
21
|
# Human-readable page URL
|
@@ -28,6 +28,9 @@ module Infoboxer
|
|
28
28
|
|
29
29
|
protected
|
30
30
|
|
31
|
+
# redefined in {Wikilink}
|
32
|
+
def interwiki; end
|
33
|
+
|
31
34
|
def page
|
32
35
|
lookup_parents(MediaWiki::Page).first or fail('Not in a page from real source')
|
33
36
|
end
|
data/lib/infoboxer/tree/math.rb
CHANGED
data/lib/infoboxer/tree/node.rb
CHANGED
@@ -11,9 +11,7 @@ module Infoboxer
|
|
11
11
|
# you will receive it from tree and use for navigations.
|
12
12
|
#
|
13
13
|
class Node
|
14
|
-
|
15
|
-
|
16
|
-
def initialize(params = {})
|
14
|
+
def initialize(**params)
|
17
15
|
@params = params
|
18
16
|
end
|
19
17
|
|
@@ -42,6 +40,10 @@ module Infoboxer
|
|
42
40
|
parent ? parent.index_of(self) : 0
|
43
41
|
end
|
44
42
|
|
43
|
+
def first?
|
44
|
+
index.zero?
|
45
|
+
end
|
46
|
+
|
45
47
|
# List of all sibling nodes (children of same parent)
|
46
48
|
def siblings
|
47
49
|
parent ? parent.children - [self] : Nodes[]
|
@@ -152,7 +154,7 @@ module Infoboxer
|
|
152
154
|
end
|
153
155
|
|
154
156
|
def show_params(prms = nil)
|
155
|
-
(prms || params).map { |k, v| "#{k}: #{v.inspect}" }.join(', ')
|
157
|
+
(prms || params).reject { |_, v| v.nil? }.map { |k, v| "#{k}: #{v.inspect}" }.join(', ')
|
156
158
|
end
|
157
159
|
|
158
160
|
def indent(level)
|
data/lib/infoboxer/tree/nodes.rb
CHANGED
@@ -38,10 +38,19 @@ module Infoboxer
|
|
38
38
|
# @!method compact
|
39
39
|
# Just like Array#compact, but returns Nodes
|
40
40
|
|
41
|
+
# @!method grep(pattern)
|
42
|
+
# Just like Array#grep, but returns Nodes
|
43
|
+
|
44
|
+
# @!method grep_v(pattern)
|
45
|
+
# Just like Array#grep_v, but returns Nodes
|
46
|
+
|
41
47
|
# @!method -(other)
|
42
48
|
# Just like Array#-, but returns Nodes
|
43
49
|
|
44
|
-
|
50
|
+
# @!method +(other)
|
51
|
+
# Just like Array#+, but returns Nodes
|
52
|
+
|
53
|
+
%i[select reject sort_by flatten compact grep grep_v - +].each do |sym|
|
45
54
|
define_method(sym) do |*args, &block|
|
46
55
|
Nodes[*super(*args, &block)]
|
47
56
|
end
|
@@ -75,6 +84,21 @@ module Infoboxer
|
|
75
84
|
end
|
76
85
|
end
|
77
86
|
|
87
|
+
# Just like Array#flat_map, but returns Nodes, **if** all map results are Node
|
88
|
+
def flat_map
|
89
|
+
res = super
|
90
|
+
if res.all? { |n| n.is_a?(Node) || n.is_a?(Nodes) }
|
91
|
+
Nodes[*res]
|
92
|
+
else
|
93
|
+
res
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Just like Array#group, but returns hash with `{<grouping variable> => Nodes}`
|
98
|
+
def group_by
|
99
|
+
super.map { |title, group| [title, Nodes[*group]] }.to_h
|
100
|
+
end
|
101
|
+
|
78
102
|
# @!method prev_siblings
|
79
103
|
# Previous siblings (flat list) of all nodes inside.
|
80
104
|
|
@@ -139,12 +163,14 @@ module Infoboxer
|
|
139
163
|
# @return [Nodes<MediaWiki::Page>] It is still `Nodes`, so you
|
140
164
|
# still can process them uniformely.
|
141
165
|
def follow
|
142
|
-
links =
|
166
|
+
links = grep(Linkable)
|
143
167
|
return Nodes[] if links.empty?
|
144
168
|
page = first.lookup_parents(MediaWiki::Page).first or
|
145
169
|
fail('Not in a page from real source')
|
146
170
|
page.client or fail('MediaWiki client not set')
|
147
|
-
|
171
|
+
pages = links.group_by(&:interwiki)
|
172
|
+
.flat_map { |iw, ls| page.client.get(*ls.map(&:link), interwiki: iw) }
|
173
|
+
pages.count == 1 ? pages.first : Nodes[*pages]
|
148
174
|
end
|
149
175
|
|
150
176
|
# @private
|
@@ -173,7 +199,9 @@ module Infoboxer
|
|
173
199
|
# @private
|
174
200
|
# Internal, used by {Parser}
|
175
201
|
def flow_templates
|
176
|
-
|
202
|
+
# TODO: will it be better?..
|
203
|
+
# make_nodes(map { |n| n.is_a?(Paragraph) ? n.to_templates? : n })
|
204
|
+
self
|
177
205
|
end
|
178
206
|
|
179
207
|
private
|
data/lib/infoboxer/tree/table.rb
CHANGED
@@ -13,12 +13,12 @@ module Infoboxer
|
|
13
13
|
|
14
14
|
# All table rows.
|
15
15
|
def rows
|
16
|
-
children.
|
16
|
+
children.grep(TableRow)
|
17
17
|
end
|
18
18
|
|
19
19
|
# Table caption, if exists.
|
20
20
|
def caption
|
21
|
-
children.
|
21
|
+
children.grep(TableCaption).first
|
22
22
|
end
|
23
23
|
|
24
24
|
# For now, returns first table row, if it consists only of
|
@@ -26,12 +26,12 @@ module Infoboxer
|
|
26
26
|
#
|
27
27
|
# FIXME: it can easily be several table heading rows
|
28
28
|
def heading_row
|
29
|
-
rows.first if rows.first && rows.first.children.all?(
|
29
|
+
rows.first if rows.first && rows.first.children.all? { |c| c.is_a?(TableHeading) }
|
30
30
|
end
|
31
31
|
|
32
32
|
# For now, returns all table rows except {#heading_row}
|
33
33
|
def body_rows
|
34
|
-
if rows.first && rows.first.children.all?(
|
34
|
+
if rows.first && rows.first.children.all? { |c| c.is_a?(TableHeading) }
|
35
35
|
rows[1..-1]
|
36
36
|
else
|
37
37
|
rows
|
@@ -39,19 +39,11 @@ module Infoboxer
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def text
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
.map(&call(sub: [/\n+\Z/, '']))
|
48
|
-
end
|
49
|
-
|
50
|
-
table.rows = body_rows.map { |r|
|
51
|
-
r.children.map(&:text)
|
52
|
-
.map(&call(sub: [/\n+\Z/, '']))
|
53
|
-
}
|
54
|
-
table.to_s + "\n\n"
|
42
|
+
Terminal::Table.new.tap { |table|
|
43
|
+
table.title = caption.text.sub(/\n+\Z/, '') if caption
|
44
|
+
table.headings = heading_row.children.map(&:text_) if heading_row
|
45
|
+
table.rows = body_rows.map { |r| r.children.map(&:text_) }
|
46
|
+
}.to_s + "\n\n"
|
55
47
|
end
|
56
48
|
end
|
57
49
|
|
@@ -17,10 +17,15 @@ module Infoboxer
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# Internal, used by {Parser}
|
20
|
+
# Means even children-less Var should not be removed from parser tree.
|
20
21
|
def empty?
|
21
22
|
false
|
22
23
|
end
|
23
24
|
|
25
|
+
def named?
|
26
|
+
name !~ /^\d+$/
|
27
|
+
end
|
28
|
+
|
24
29
|
protected
|
25
30
|
|
26
31
|
def descr
|
@@ -54,6 +59,7 @@ module Infoboxer
|
|
54
59
|
# values.
|
55
60
|
#
|
56
61
|
# ### On variables naming
|
62
|
+
#
|
57
63
|
# MediaWiki templates can contain _named_ and _unnamed_ variables.
|
58
64
|
# Example:
|
59
65
|
#
|
@@ -104,12 +110,16 @@ module Infoboxer
|
|
104
110
|
# See {Var} class to understand what you can do with them.
|
105
111
|
#
|
106
112
|
# @return [Nodes<Var>]
|
107
|
-
attr_reader :variables
|
113
|
+
# attr_reader :variables
|
114
|
+
alias_method :variables, :children
|
108
115
|
|
109
116
|
def initialize(name, variables = Nodes[])
|
110
|
-
super(
|
117
|
+
super(variables, extract_params(variables))
|
111
118
|
@name = name
|
112
|
-
|
119
|
+
end
|
120
|
+
|
121
|
+
def text
|
122
|
+
''
|
113
123
|
end
|
114
124
|
|
115
125
|
# See {Node#to_tree}
|
@@ -133,7 +143,7 @@ module Infoboxer
|
|
133
143
|
#
|
134
144
|
# @return [Nodes<Var>]
|
135
145
|
def unnamed_variables
|
136
|
-
variables.
|
146
|
+
variables.reject(&:named?)
|
137
147
|
end
|
138
148
|
|
139
149
|
# Fetches template variable(s) by name(s) or patterns.
|
@@ -236,7 +246,7 @@ module Infoboxer
|
|
236
246
|
def extract_params(vars)
|
237
247
|
vars
|
238
248
|
.select { |v| v.children.count == 1 && v.children.first.is_a?(Text) }
|
239
|
-
.map { |v| [v.name, v.children.first.raw_text] }.to_h
|
249
|
+
.map { |v| [v.name.to_sym, v.children.first.raw_text] }.to_h
|
240
250
|
end
|
241
251
|
|
242
252
|
def inspect_variables(depth)
|
data/lib/infoboxer/tree/text.rb
CHANGED
@@ -12,14 +12,23 @@ module Infoboxer
|
|
12
12
|
# Note, that Wikilink is {Linkable}, so you can {Linkable#follow #follow}
|
13
13
|
# it to obtain linked pages.
|
14
14
|
class Wikilink < Link
|
15
|
-
def initialize(
|
16
|
-
super
|
17
|
-
|
15
|
+
def initialize(link, label = nil, namespace: nil, interwiki: nil)
|
16
|
+
super(link, label, namespace: namespace, interwiki: interwiki)
|
17
|
+
@namespace = namespace || ''
|
18
|
+
@interwiki = interwiki
|
19
|
+
parse_name!
|
18
20
|
end
|
19
21
|
|
20
22
|
# "Clean" wikilink name, for ex., `Cities` for `[Category:Cities]`
|
21
23
|
attr_reader :name
|
22
24
|
|
25
|
+
# Interwiki identifier. For example, `[[wikt:Argentina]]`
|
26
|
+
# will have `"Argentina"` as its {#name} and `"wikt"` (wiktionary) as an
|
27
|
+
# interwiki. TODO: how to use it.
|
28
|
+
#
|
29
|
+
# See [Wikipedia docs](https://en.wikipedia.org/wiki/Help:Interwiki_linking) for details.
|
30
|
+
attr_reader :interwiki
|
31
|
+
|
23
32
|
# Wikilink namespace, `Category` for `[Category:Cities]`, empty
|
24
33
|
# string (not `nil`!) for just `[Cities]`
|
25
34
|
attr_reader :namespace
|
@@ -46,10 +55,8 @@ module Infoboxer
|
|
46
55
|
|
47
56
|
private
|
48
57
|
|
49
|
-
def
|
50
|
-
@name
|
51
|
-
@namespace ||= ''
|
52
|
-
|
58
|
+
def parse_name!
|
59
|
+
@name = namespace.empty? ? link : link.sub(/^#{namespace}:/, '')
|
53
60
|
@name, @anchor = @name.split('#', 2)
|
54
61
|
@anchor ||= ''
|
55
62
|
|
data/lib/infoboxer/version.rb
CHANGED
@@ -0,0 +1,94 @@
|
|
1
|
+
module Infoboxer
|
2
|
+
# @private
|
3
|
+
class WikiPath
|
4
|
+
ParseError = Class.new(ArgumentError)
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def _parse(string)
|
8
|
+
scanner = StringScanner.new(string)
|
9
|
+
res = []
|
10
|
+
loop do
|
11
|
+
res << scan_step(scanner)
|
12
|
+
break if scanner.eos?
|
13
|
+
end
|
14
|
+
res
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse(string)
|
18
|
+
new(_parse(string))
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def scan_step(scanner) # rubocop:disable Metrics/PerceivedComplexity
|
24
|
+
op = scanner.scan(%r{//?}) or unexpected(scanner, '/')
|
25
|
+
type = scanner.scan(/[A-Za-z_]*/)
|
26
|
+
attrs = {}
|
27
|
+
while scanner.scan(/\[/)
|
28
|
+
attr = scanner.scan(/[-a-z_0-9]+/) or unexpected(scanner, 'attribute name')
|
29
|
+
if scanner.scan(/\]/)
|
30
|
+
(attrs[:predicates] ||= []) << "#{attr}?".to_sym
|
31
|
+
next
|
32
|
+
end
|
33
|
+
scanner.scan(/\s*=\s*/) or unexpected(scanner, '= or ]')
|
34
|
+
value = scanner.scan(/[^\]]+/) # TODO: probably, should do a proper [] counting?..
|
35
|
+
scanner.scan(/\]/) or unexpected(scanner, ']')
|
36
|
+
attrs[attr.to_sym] = process_value(value)
|
37
|
+
end
|
38
|
+
res = op == '//' ? {op: :lookup} : {}
|
39
|
+
res[:type] = process_type(type) unless type.empty?
|
40
|
+
res.merge(attrs) # TODO: raise if empty selector
|
41
|
+
end
|
42
|
+
|
43
|
+
def process_value(value)
|
44
|
+
case value
|
45
|
+
when /^'(.*)'$/, /^"(.*)"$/
|
46
|
+
Regexp.last_match(1)
|
47
|
+
when %r{^/(.+)/$}
|
48
|
+
Regexp.new(Regexp.last_match(1))
|
49
|
+
else
|
50
|
+
value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def process_type(type)
|
55
|
+
type.gsub(/(?:^|_)([a-z])/, &:upcase).tr('_', '').to_sym
|
56
|
+
.tap { |t| valid_type?(t) or fail(ParseError, "Unrecognized node type: #{type}") }
|
57
|
+
end
|
58
|
+
|
59
|
+
def valid_type?(t)
|
60
|
+
t == :Section || Infoboxer::Tree.const_defined?(t)
|
61
|
+
end
|
62
|
+
|
63
|
+
def unexpected(scanner, expected)
|
64
|
+
place = scanner.eos? ? 'end of pattern' : scanner.rest.inspect
|
65
|
+
fail ParseError, "Unexpected #{place}, expecting #{expected}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize(path)
|
70
|
+
@path = path
|
71
|
+
end
|
72
|
+
|
73
|
+
def call(node)
|
74
|
+
@path.inject(node) { |res, step| apply_step(res, step) }
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def apply_step(node, step)
|
80
|
+
# TODO: "compile" the op/args sequences at WikiPath initialization
|
81
|
+
step = step.dup
|
82
|
+
op = step.delete(:op) || :lookup_children
|
83
|
+
args = []
|
84
|
+
if (t = step.delete(:type))
|
85
|
+
args << t
|
86
|
+
end
|
87
|
+
if (pred = step.delete(:predicates))
|
88
|
+
args.concat(pred)
|
89
|
+
end
|
90
|
+
args << step unless step.empty?
|
91
|
+
node.send(op, *args)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: infoboxer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0.pre
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Victor Shepelev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-09-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: htmlentities
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: procme
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: mediawiktory
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -115,6 +101,7 @@ files:
|
|
115
101
|
- lib/infoboxer/navigation/sections.rb
|
116
102
|
- lib/infoboxer/navigation/selector.rb
|
117
103
|
- lib/infoboxer/navigation/shortcuts.rb
|
104
|
+
- lib/infoboxer/navigation/wikipath.rb
|
118
105
|
- lib/infoboxer/parser.rb
|
119
106
|
- lib/infoboxer/parser/context.rb
|
120
107
|
- lib/infoboxer/parser/html.rb
|
@@ -130,6 +117,7 @@ files:
|
|
130
117
|
- lib/infoboxer/tree.rb
|
131
118
|
- lib/infoboxer/tree/compound.rb
|
132
119
|
- lib/infoboxer/tree/document.rb
|
120
|
+
- lib/infoboxer/tree/gallery.rb
|
133
121
|
- lib/infoboxer/tree/html.rb
|
134
122
|
- lib/infoboxer/tree/image.rb
|
135
123
|
- lib/infoboxer/tree/inline.rb
|
@@ -145,6 +133,7 @@ files:
|
|
145
133
|
- lib/infoboxer/tree/text.rb
|
146
134
|
- lib/infoboxer/tree/wikilink.rb
|
147
135
|
- lib/infoboxer/version.rb
|
136
|
+
- lib/infoboxer/wiki_path.rb
|
148
137
|
- profile/out/.gitkeep
|
149
138
|
- profile/pages/argentina.txt
|
150
139
|
- profile/pages/canada.wiki
|
@@ -177,9 +166,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
177
166
|
version: 2.1.0
|
178
167
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
179
168
|
requirements:
|
180
|
-
- - "
|
169
|
+
- - ">"
|
181
170
|
- !ruby/object:Gem::Version
|
182
|
-
version:
|
171
|
+
version: 1.3.1
|
183
172
|
requirements: []
|
184
173
|
rubyforge_project:
|
185
174
|
rubygems_version: 2.6.10
|