mdn_query 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []