mdn_query 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,114 @@
1
+ module MdnQuery
2
+ # A symmetric table with a nicely aligned textual representation.
3
+ #
4
+ # The table automatically adjusts rows that are too small by padding them with
5
+ # empty strings.
6
+ class Table
7
+ # @return [Array<Array<String>>] the body (list of rows) of the table
8
+ attr_reader :body
9
+
10
+ # @return [Array<String>] the heading of the table
11
+ attr_reader :heading
12
+
13
+ # @return [Fixnum] the number of columns in the table
14
+ attr_reader :size
15
+
16
+ # Creates a new table.
17
+ #
18
+ # @param heading [Array<String>] the heading of the table
19
+ # @param rows [Array<String>] rows to add to the body
20
+ # @return [MdnQuery::Table]
21
+ def initialize(heading, *rows)
22
+ @heading = heading
23
+ @size = heading.size
24
+ @body = []
25
+ rows.each { |row| add_row(row) }
26
+ end
27
+
28
+ # Adds a row to the body of the table.
29
+ #
30
+ # When the size of the row is smaller than the current table size, it is
31
+ # padded with empty strings to that size.
32
+ # When the size is greater than the current table size, the entire table is
33
+ # padded with empty strings to that size.
34
+ #
35
+ # @param row [Array<String>] the row to add
36
+ # @return [void]
37
+ def add_row(row)
38
+ if row.size < @size
39
+ row.fill('', row.size...@size)
40
+ elsif row.size > @size
41
+ @heading.fill('', @size...row.size)
42
+ @body.each { |r| r.fill('', @size...row.size) }
43
+ @size = row.size
44
+ end
45
+ @body << row
46
+ end
47
+
48
+ # Returns the number of columns in the table.
49
+ #
50
+ # @return [Fixnum]
51
+ def cols
52
+ @size
53
+ end
54
+
55
+ # Returns the number of rows in the body of the table.
56
+ #
57
+ # @return [Fixnum]
58
+ def rows
59
+ @body.size
60
+ end
61
+
62
+ # Returns the string representation of the table.
63
+ #
64
+ # This representation is a Markdown table that is aligned nicely, so that it
65
+ # is also easy to read in its raw format.
66
+ #
67
+ # @example Small table
68
+ # | Title | Description |
69
+ # | ----- | ---------------- |
70
+ # | One | |
71
+ # | Two | Long description |
72
+ #
73
+ # @return [String]
74
+ def to_s
75
+ return '' if cols < 1
76
+ col_sizes = max_col_sizes
77
+ str = heading_str(col_sizes)
78
+ str << separator(col_sizes)
79
+ str << body_str(col_sizes)
80
+ str
81
+ end
82
+
83
+ private
84
+
85
+ def max_col_sizes
86
+ max_sizes = @heading.map { |col| col.size > 3 ? col.size : 3 }
87
+ @body.each do |row|
88
+ row.each.with_index do |str, index|
89
+ next unless str.size > max_sizes[index]
90
+ max_sizes[index] = str.size
91
+ end
92
+ end
93
+ max_sizes
94
+ end
95
+
96
+ def pad_cols(row, col_sizes)
97
+ row.map.with_index { |col, index| col.ljust(col_sizes[index]) }
98
+ end
99
+
100
+ def heading_str(col_sizes)
101
+ "| #{pad_cols(@heading, col_sizes).join(' | ')} |\n"
102
+ end
103
+
104
+ def body_str(col_sizes)
105
+ return '' if @body.empty?
106
+ rows = @body.map { |row| "| #{pad_cols(row, col_sizes).join(' | ')} |" }
107
+ "#{rows.join("\n")}\n"
108
+ end
109
+
110
+ def separator(col_sizes)
111
+ "| #{col_sizes.map { |size| '-' * size }.join(' | ')} |\n"
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,176 @@
1
+ module MdnQuery
2
+ # A DOM traverser that extracts relevant elements.
3
+ class TraverseDom
4
+ # @return [MdnQuery::Section] the current section
5
+ attr_reader :current_section
6
+
7
+ # @return [Nokogiri::HTML::Document] the DOM that is traversed
8
+ attr_reader :dom
9
+
10
+ # @return [MdnQuery::Document] the document that contains the extracted text
11
+ attr_reader :document
12
+
13
+ # Sections that are blacklisted and excluded from the document.
14
+ BLACKLIST = %w(Specifications Browser_compatibility).freeze
15
+
16
+ # Creates a new document with the extracted text.
17
+ #
18
+ # @param dom [Nokogiri::HTML::Document] the DOM that is traversed
19
+ # @param title [String] the title of the document
20
+ # @param url [String] the URL to the document on the web
21
+ # @return [MdnQuery::Document] the document with the extracted text
22
+ def self.create_document(dom, title, url)
23
+ document = MdnQuery::Document.new(title, url)
24
+ fill_document(dom, document)
25
+ end
26
+
27
+ # Fills a document with the extracted text.
28
+ #
29
+ # @param dom [Nokogiri::HTML::Document] the DOM that is traversed
30
+ # @param document [MdnQuery::Document] the document to be filled
31
+ # @return [MdnQuery::Document] the document with the extracted text
32
+ def self.fill_document(dom, document)
33
+ traverser = new(dom, document: document)
34
+ traverser.traverse
35
+ traverser.document
36
+ end
37
+
38
+ # Creates a new DOM traverser.
39
+ #
40
+ # The given document is used to save the extracted text. If no document is
41
+ # given, a new one is created with the generic title 'root' and the given
42
+ # url.
43
+ #
44
+ # The DOM is not automatically traversed (use {#traverse}).
45
+ #
46
+ # @param dom [Nokogiri::HTML::Document] the DOM that is traversed
47
+ # @param document [MdnQuery::Document] the document to be filled
48
+ # @param url [String] the URL for the new document if none was provided
49
+ # @return [MdnQuery::TraverseDom]
50
+ def initialize(dom, document: nil, url: nil)
51
+ @dom = dom
52
+ @document = document || MdnQuery::Document.new('root', url)
53
+ @current_section = @document.section
54
+ end
55
+
56
+ # Creates a new child section on the appropriate parent section.
57
+ #
58
+ # @param desired_level [Fixnum] the desired level for the child section
59
+ # @param name [String] the name and title of the child section
60
+ # @return [MdnQuery::Section] the newly created child section
61
+ def create_child(desired_level, name)
62
+ until @current_section.level < desired_level ||
63
+ @current_section.parent.nil?
64
+ @current_section = @current_section.parent
65
+ end
66
+ @current_section = @current_section.create_child(name)
67
+ end
68
+
69
+ # Traverses the DOM and extracts relevant informations into the document.
70
+ #
71
+ # @return [void]
72
+ def traverse
73
+ unless @dom.css('div.nonStandard').empty?
74
+ @current_section.append_text("\n> ***Non-standard***\n")
75
+ end
76
+ blacklist_level = nil
77
+ @dom.children.each do |child|
78
+ if child_blacklisted?(child, blacklist_level)
79
+ if blacklist_level.nil?
80
+ blacklist_level = child.name.match(/\Ah(?<level>\d)\z/)[:level]
81
+ end
82
+ next
83
+ end
84
+ blacklist_level = nil
85
+ case child.name
86
+ when 'p'
87
+ @current_section.append_text(child.text)
88
+ when 'ul'
89
+ @current_section.append_text(convert_list(child))
90
+ when 'dl'
91
+ @current_section.append_text(convert_description(child))
92
+ when 'pre'
93
+ @current_section.append_code(child.text, language: 'javascript')
94
+ when /\Ah(?<level>\d)\z/
95
+ level = $LAST_MATCH_INFO[:level].to_i
96
+ create_child(level, child[:id].tr('_', ' '))
97
+ when 'table'
98
+ @current_section.append_text(convert_table(child))
99
+ when 'div'
100
+ next if child[:class].nil?
101
+ if child[:class].include?('note') || child[:class].include?('warning')
102
+ @current_section.append_text("\n> #{child.text}\n")
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ # Returns whether the id is blacklisted.
109
+ #
110
+ # @param id [String] the id to be tested
111
+ # @return [Boolean]
112
+ def blacklisted?(id)
113
+ BLACKLIST.include?(id)
114
+ end
115
+
116
+ private
117
+
118
+ def child_blacklisted?(child, blacklist_level)
119
+ match = child.name.match(/\Ah(?<level>\d)\z/)
120
+ if match.nil?
121
+ !blacklist_level.nil?
122
+ else
123
+ blacklisted?(child[:id]) ||
124
+ (!blacklist_level.nil? && match[:level] > blacklist_level)
125
+ end
126
+ end
127
+
128
+ def convert_list(ul)
129
+ lines = ul.children.map do |child|
130
+ if child.name == 'ul'
131
+ convert_list(child)
132
+ elsif child.name == 'li'
133
+ "- #{child.text}\n"
134
+ else
135
+ child.text
136
+ end
137
+ end
138
+ lines.join
139
+ end
140
+
141
+ def convert_description(dl)
142
+ lines = dl.children.map do |child|
143
+ if child.name == 'dl'
144
+ convert_description(child)
145
+ elsif child.name == 'dt'
146
+ "\n**#{child.text}**\n"
147
+ else
148
+ "\n#{child.text}\n"
149
+ end
150
+ end
151
+ lines.join
152
+ end
153
+
154
+ def convert_table(table)
155
+ body = table.css('tbody > tr').map { |tr| extract_table_row(tr) }
156
+ head_row = table.css('thead > tr').first
157
+ head = if head_row.nil?
158
+ # Make first row in body the table heading
159
+ body.shift
160
+ else
161
+ extract_table_row(head_row)
162
+ end
163
+ table = MdnQuery::Table.new(head, *body)
164
+ table.to_s
165
+ end
166
+
167
+ def extract_table_row(tr)
168
+ cols = []
169
+ tr.children.each do |child|
170
+ next unless child.name == 'th' || child.name == 'td'
171
+ cols << child.text
172
+ end
173
+ cols
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,4 @@
1
+ module MdnQuery
2
+ # The version of the gem
3
+ VERSION = '0.1.0'.freeze
4
+ end
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mdn_query/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'mdn_query'
8
+ spec.version = MdnQuery::VERSION
9
+ spec.authors = ['Michael Jungo']
10
+ spec.email = ['michaeljungo92@gmail.com']
11
+
12
+ spec.summary = 'Query the MDN docs'
13
+ spec.description = 'Query the MDN docs'
14
+ spec.homepage = 'https://github.com/jungomi/mdn_query'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = 'bin'
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_dependency 'launchy', '~> 2.4'
25
+ spec.add_dependency 'nokogiri', '~> 1.6'
26
+ spec.add_dependency 'rest-client', '~> 1.8'
27
+ spec.add_dependency 'slop', '~> 4.4'
28
+ spec.add_dependency 'tty-pager', '~> 0.4'
29
+ spec.add_dependency 'tty-prompt', '~> 0.7'
30
+
31
+ spec.add_development_dependency 'bundler', '~> 1.13'
32
+ spec.add_development_dependency 'rake', '~> 10.0'
33
+ spec.add_development_dependency 'minitest', '~> 5.0'
34
+ spec.add_development_dependency 'minitest-reporters', '~> 1.1'
35
+ spec.add_development_dependency 'rubocop', '~> 0.43'
36
+ spec.add_development_dependency 'simplecov', '~> 0.12'
37
+ end
Binary file
metadata ADDED
@@ -0,0 +1,234 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mdn_query
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael Jungo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: launchy
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: nokogiri
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rest-client
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.8'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: slop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.4'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: tty-pager
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.4'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: tty-prompt
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.7'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.7'
97
+ - !ruby/object:Gem::Dependency
98
+ name: bundler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.13'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.13'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '10.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '10.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: minitest
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '5.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '5.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: minitest-reporters
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1.1'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1.1'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubocop
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.43'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.43'
167
+ - !ruby/object:Gem::Dependency
168
+ name: simplecov
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '0.12'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '0.12'
181
+ description: Query the MDN docs
182
+ email:
183
+ - michaeljungo92@gmail.com
184
+ executables:
185
+ - mdn-query
186
+ extensions: []
187
+ extra_rdoc_files: []
188
+ files:
189
+ - ".gitignore"
190
+ - ".rubocop.yml"
191
+ - ".travis.yml"
192
+ - Gemfile
193
+ - LICENSE.md
194
+ - README.md
195
+ - Rakefile
196
+ - bin/mdn-query
197
+ - lib/mdn_query.rb
198
+ - lib/mdn_query/document.rb
199
+ - lib/mdn_query/entry.rb
200
+ - lib/mdn_query/errors.rb
201
+ - lib/mdn_query/list.rb
202
+ - lib/mdn_query/search.rb
203
+ - lib/mdn_query/search_result.rb
204
+ - lib/mdn_query/section.rb
205
+ - lib/mdn_query/table.rb
206
+ - lib/mdn_query/traverse_dom.rb
207
+ - lib/mdn_query/version.rb
208
+ - mdn_query.gemspec
209
+ - screenshots/demo.gif
210
+ homepage: https://github.com/jungomi/mdn_query
211
+ licenses:
212
+ - MIT
213
+ metadata: {}
214
+ post_install_message:
215
+ rdoc_options: []
216
+ require_paths:
217
+ - lib
218
+ required_ruby_version: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
223
+ required_rubygems_version: !ruby/object:Gem::Requirement
224
+ requirements:
225
+ - - ">="
226
+ - !ruby/object:Gem::Version
227
+ version: '0'
228
+ requirements: []
229
+ rubyforge_project:
230
+ rubygems_version: 2.5.1
231
+ signing_key:
232
+ specification_version: 4
233
+ summary: Query the MDN docs
234
+ test_files: []