mhtml 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/README.md +97 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/mhtml/document.rb +135 -0
- data/lib/mhtml/http_header.rb +103 -0
- data/lib/mhtml/root_document.rb +123 -0
- data/lib/mhtml/version.rb +3 -0
- data/lib/mhtml.rb +11 -0
- data/lib/string.rb +64 -0
- data/mhtml.gemspec +30 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7bd65b32102f1862282c6980695730474a60011f
|
4
|
+
data.tar.gz: 96c49f4a7a9e1132d93c89dc7cb27b03f746b179
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c2c2effc9cac20e3c6284f5c61137b553c8b0115bbc6540bd9c15ce97f2cefd541cba9cd9e1a0ede97010e2f7465074897233ee2d3e736172839f27dd29fc5d1
|
7
|
+
data.tar.gz: e4015b53919c0d6a29caf8c11df08ef6a8188eaea2459ca07460ff127df0d287b40eadc4e39e3c5985ef7c1d938ae35c69d5b8b7a0a5897ecf01d437f9167c68
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Mhtml
|
2
|
+
A ruby gem for parsing MHTML.
|
3
|
+
|
4
|
+
Uses the NodeJS C HTTP Parser under the hood (thanks to @cotag for the gem).
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'mhtml'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install mhtml
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
Two interfaces are provided - all at once, or chunked.
|
23
|
+
|
24
|
+
### All at once
|
25
|
+
For when you have all of the data in memory.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
source = File.open('/file/path.mht').read
|
29
|
+
doc = Mhtml::RootDocument.new(source)
|
30
|
+
|
31
|
+
doc.headers.each { |h| puts h }
|
32
|
+
|
33
|
+
# body is decoded from printed quotable, and encoded according to charset header
|
34
|
+
puts doc.body
|
35
|
+
|
36
|
+
doc.sub_docs.each { |s| puts subdoc }
|
37
|
+
```
|
38
|
+
|
39
|
+
### Chunked
|
40
|
+
For when source data is being streamed, or when concerned about memory usage.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
doc = Mhtml::RootDocument.new
|
44
|
+
|
45
|
+
doc.on_header { |h| handle_header(h) } # yields each header
|
46
|
+
|
47
|
+
# yields body, possibly in parts
|
48
|
+
doc.on_body do |b|
|
49
|
+
encoding = doc.encoding
|
50
|
+
handle_body(b)
|
51
|
+
end
|
52
|
+
|
53
|
+
doc.on_subdoc_begin { handle_subdoc_begin } # yields nil on each subdoc begin
|
54
|
+
doc.on_subdoc_header { |h| handle_subdoc_header(h) } # yields each subdoc header
|
55
|
+
doc.on_subdoc_body { |b| handle_subdoc_body(b) } # yields each subdoc's body, possibly in parts
|
56
|
+
doc.on_subdoc_complete { handle_subdoc_begin } # yields nil on each subdoc complete
|
57
|
+
|
58
|
+
File.open('/file/path.mht').read.scan(/.{128}/).each do |chunk|
|
59
|
+
doc << chunk
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
### Headers
|
64
|
+
The header class looks like this (portayed as a hash):
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
# Content-Type: multipart/related; charset="windows-1252"; boundary="----=_NextPart_01C74319.B7EA56A0"
|
68
|
+
{
|
69
|
+
key: 'Content-Type',
|
70
|
+
values: [
|
71
|
+
{ key: nil, value: 'multipart/related' },
|
72
|
+
{ key: 'charset', value: 'windows-1252' },
|
73
|
+
{ key: 'boundary', value: '----=_NextPart_01C74319.B7EA56A0' }
|
74
|
+
]
|
75
|
+
}
|
76
|
+
```
|
77
|
+
|
78
|
+
## TODO
|
79
|
+
- Revisit spec fixtures - either use existing solution or break out to separate
|
80
|
+
gem
|
81
|
+
- Build up body of fixtures using MHTML from various sources
|
82
|
+
|
83
|
+
## Development
|
84
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
85
|
+
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
86
|
+
prompt that will allow you to experiment.
|
87
|
+
|
88
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
89
|
+
To release a new version, update the version number in `version.rb`, and then
|
90
|
+
run `bundle exec rake release`, which will create a git tag for the version,
|
91
|
+
push git commits and tags, and push the `.gem` file to
|
92
|
+
[rubygems.org](https://rubygems.org).
|
93
|
+
|
94
|
+
|
95
|
+
## Contributing
|
96
|
+
Bug reports and pull requests are welcome on GitHub at
|
97
|
+
https://github.com/benjineering/mhtml_rb.
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'http-parser'
|
2
|
+
|
3
|
+
module Mhtml
|
4
|
+
class Document
|
5
|
+
attr_reader :chunked, :parser
|
6
|
+
attr_accessor :headers, :body, :is_quoted_printable, :encoding
|
7
|
+
|
8
|
+
def initialize(str = nil)
|
9
|
+
@chunked = !str.is_a?(String)
|
10
|
+
@header_key = nil
|
11
|
+
@header_value_lines = nil
|
12
|
+
@is_quoted_printable = false
|
13
|
+
@encoding = nil
|
14
|
+
|
15
|
+
@request = HttpParser::Parser.new_instance { |inst| inst.type = :response }
|
16
|
+
|
17
|
+
@parser = HttpParser::Parser.new do |parser|
|
18
|
+
parser.on_header_field { |inst, data| handle_header_field(inst, data) }
|
19
|
+
parser.on_header_value { |inst, data| handle_header_value(inst, data) }
|
20
|
+
parser.on_body { |inst, data| handle_body(inst, data) }
|
21
|
+
parser.on_message_begin { |inst| handle_message_begin(inst) }
|
22
|
+
parser.on_message_complete { |inst| handle_message_complete(inst) }
|
23
|
+
end
|
24
|
+
|
25
|
+
@parser.parse(@request, Mhtml::STATUS_LINE)
|
26
|
+
|
27
|
+
unless @chunked
|
28
|
+
@headers = []
|
29
|
+
@body = ''
|
30
|
+
@parser.parse(@request, str)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def <<(chunk)
|
35
|
+
@parser.parse(@request, chunk)
|
36
|
+
end
|
37
|
+
|
38
|
+
def ==(other)
|
39
|
+
@headers == other.headers &&
|
40
|
+
@body.gsub(/\r\n/, "\n").strip == other.body.gsub(/\r\n/, "\n").strip
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_header
|
44
|
+
@headers_proc = Proc.new
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_body
|
48
|
+
@body_proc = Proc.new
|
49
|
+
end
|
50
|
+
|
51
|
+
def header(key)
|
52
|
+
header = nil
|
53
|
+
|
54
|
+
@headers.each do |h|
|
55
|
+
if h.key == key
|
56
|
+
header = h
|
57
|
+
break
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
header
|
62
|
+
end
|
63
|
+
|
64
|
+
# for testing only = no spec implemented
|
65
|
+
def to_s
|
66
|
+
@headers.join(LINE_BREAK) + Mhtml::DOUBLE_LINE_BREAK + @body
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def handle_header_field(inst, data)
|
72
|
+
maybe_create_header
|
73
|
+
@header_key = data
|
74
|
+
@header_value_lines = []
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle_header_value(inst, data)
|
78
|
+
@header_value_lines << data
|
79
|
+
end
|
80
|
+
|
81
|
+
def handle_body(inst, data)
|
82
|
+
maybe_create_header
|
83
|
+
decoded = decode(data)
|
84
|
+
|
85
|
+
if @chunked
|
86
|
+
@body_proc.call(decoded) unless @body_proc.nil?
|
87
|
+
else
|
88
|
+
@body.force_encoding(@encoding) if @body.empty? && !@encoding.nil?
|
89
|
+
@body += decoded
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def handle_message_begin(inst)
|
94
|
+
end
|
95
|
+
|
96
|
+
def handle_message_complete(inst)
|
97
|
+
end
|
98
|
+
|
99
|
+
def maybe_create_header
|
100
|
+
unless @header_key.nil?
|
101
|
+
header = HttpHeader.new(@header_key, @header_value_lines)
|
102
|
+
@headers << header unless @chunked
|
103
|
+
|
104
|
+
if header.key == 'Content-Type'
|
105
|
+
boundary = header.value('boundary')
|
106
|
+
@boundary = boundary.value unless boundary.nil?
|
107
|
+
|
108
|
+
charset = header.value('charset')
|
109
|
+
|
110
|
+
unless charset.nil?
|
111
|
+
@encoding = Encoding.find(charset.value) rescue nil
|
112
|
+
end
|
113
|
+
|
114
|
+
elsif header.key == 'Content-Transfer-Encoding'
|
115
|
+
value = header.values.first
|
116
|
+
|
117
|
+
if !value.nil? && value.value == 'quoted-printable'
|
118
|
+
@is_quoted_printable = true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
@headers_proc.call(header) unless @headers_proc.nil?
|
123
|
+
|
124
|
+
@header_key = nil
|
125
|
+
@header_value_lines = []
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def decode(str)
|
130
|
+
str = str.unpack1('M*') if @is_quoted_printable
|
131
|
+
str = str.force_encoding(@encoding) unless @encoding.nil?
|
132
|
+
str
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Mhtml
|
2
|
+
class HttpHeader
|
3
|
+
require 'string'
|
4
|
+
|
5
|
+
attr_accessor :key, :values
|
6
|
+
|
7
|
+
KEY_VALUE_SEP = ':'.freeze
|
8
|
+
VALUE_SEP = ';'.freeze
|
9
|
+
|
10
|
+
def initialize(key_or_hash, value_lines = nil)
|
11
|
+
if key_or_hash.is_a?(Hash)
|
12
|
+
@key = key_or_hash[:key]
|
13
|
+
@values = key_or_hash[:values]
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
@key = key_or_hash
|
18
|
+
@values = []
|
19
|
+
values_str = value_lines.join('')
|
20
|
+
|
21
|
+
values_str.split(VALUE_SEP).each do |val_str|
|
22
|
+
val_str.strip!
|
23
|
+
val = Value.new(val_str)
|
24
|
+
|
25
|
+
if val.nil?
|
26
|
+
raise "Invalid value:\n#{val_str}\n\nFrom string:\n#{val_str}"
|
27
|
+
end
|
28
|
+
|
29
|
+
@values << val
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def ==(other)
|
34
|
+
@key == other.key && @values == other.values
|
35
|
+
end
|
36
|
+
|
37
|
+
def value(key)
|
38
|
+
value = nil
|
39
|
+
|
40
|
+
@values.each do |v|
|
41
|
+
if v.key == key
|
42
|
+
value = v
|
43
|
+
break
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
value
|
48
|
+
end
|
49
|
+
|
50
|
+
# following methods are for debugging only - no spec implemented
|
51
|
+
def to_s
|
52
|
+
"#{@key}#{KEY_VALUE_SEP} #{@values.join(VALUE_SEP + ' ')}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def clone
|
56
|
+
vals = @values.collect { |v| v.clone }
|
57
|
+
HttpHeader.new(key: @key.clone, values: vals)
|
58
|
+
end
|
59
|
+
|
60
|
+
class Value
|
61
|
+
attr_reader :key, :value
|
62
|
+
|
63
|
+
# str examples:
|
64
|
+
# value
|
65
|
+
# key="value"
|
66
|
+
def initialize(str_or_hash)
|
67
|
+
if str_or_hash.is_a?(Hash)
|
68
|
+
@key = str_or_hash[:key]
|
69
|
+
@value = str_or_hash[:value]
|
70
|
+
return
|
71
|
+
end
|
72
|
+
|
73
|
+
str = str_or_hash
|
74
|
+
split_i = str.index('=')
|
75
|
+
@key = str[0, split_i].strip unless split_i.nil?
|
76
|
+
|
77
|
+
@value =
|
78
|
+
if split_i.nil?
|
79
|
+
str.strip
|
80
|
+
else
|
81
|
+
str[split_i + 1, str.length - 1].strip.strip_other('"')
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def ==(other)
|
86
|
+
@key == other.key && @value == other.value
|
87
|
+
end
|
88
|
+
|
89
|
+
# following methods are for debugging only - no spec implemented
|
90
|
+
def to_s
|
91
|
+
if @key.nil?
|
92
|
+
@value
|
93
|
+
else
|
94
|
+
%Q[#{@key}="#{@value}"]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def clone
|
99
|
+
Value.new(key: @key.clone, value: @value.clone)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Mhtml
|
2
|
+
class RootDocument < Document
|
3
|
+
BOUNDARY_PREFIX = '--'.freeze
|
4
|
+
|
5
|
+
attr_accessor :boundary, :sub_docs
|
6
|
+
|
7
|
+
def initialize(str = nil)
|
8
|
+
@sub_docs = []
|
9
|
+
super(str)
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
super(other) && @boundary == other.boundary && @sub_docs == other.sub_docs
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_subdoc_begin
|
17
|
+
@subdoc_begin_proc = Proc.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_subdoc_header
|
21
|
+
@subdoc_header_proc = Proc.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_subdoc_body
|
25
|
+
@subdoc_body_proc = Proc.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_subdoc_complete
|
29
|
+
@subdoc_complete_proc = Proc.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def boundary_str
|
33
|
+
"#{Mhtml::LINE_BREAK}#{BOUNDARY_PREFIX}#{@boundary}#{Mhtml::LINE_BREAK}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def last_boundary_str
|
37
|
+
"#{Mhtml::LINE_BREAK}#{BOUNDARY_PREFIX}#{@boundary}#{BOUNDARY_PREFIX}#{Mhtml::LINE_BREAK}"
|
38
|
+
end
|
39
|
+
|
40
|
+
# for testing only = no spec implemented
|
41
|
+
def to_s
|
42
|
+
doc_sep = Mhtml::DOUBLE_LINE_BREAK + BOUNDARY_PREFIX + @boundary +
|
43
|
+
Mhtml::LINE_BREAK
|
44
|
+
super + doc_sep + @sub_docs.join(doc_sep)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def handle_body(inst, data)
|
50
|
+
maybe_create_header
|
51
|
+
boundary = boundary_str
|
52
|
+
|
53
|
+
unless @split.nil?
|
54
|
+
data = @split + data
|
55
|
+
@split = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
parts = data.split(boundary)
|
59
|
+
|
60
|
+
unless @body_read
|
61
|
+
@body_read = parts.length > 1
|
62
|
+
super(inst, parts.shift)
|
63
|
+
end
|
64
|
+
|
65
|
+
parts.each_with_index do |part, i|
|
66
|
+
end_boundary_pos = part.rindex(last_boundary_str)
|
67
|
+
is_last_subdoc = !end_boundary_pos.nil?
|
68
|
+
part = part[0..(end_boundary_pos - 1)] if is_last_subdoc
|
69
|
+
|
70
|
+
if @chunked
|
71
|
+
is_last_part = i + 1 == parts.length
|
72
|
+
handle_chunked_body(part, is_last_part, is_last_subdoc)
|
73
|
+
else
|
74
|
+
@sub_docs << Document.new(part)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def handle_chunked_body(chunk, is_last_part, is_last_subdoc)
|
80
|
+
if @chunked_sub_doc.nil?
|
81
|
+
create_chunked_subdoc
|
82
|
+
@subdoc_begin_proc.call unless @subdoc_begin_proc.nil?
|
83
|
+
end
|
84
|
+
|
85
|
+
if is_last_part
|
86
|
+
split_idx = chunk.rindex_of_split(boundary_str)
|
87
|
+
|
88
|
+
if split_idx.nil?
|
89
|
+
quoted_matches = chunk.match(/=[0-9A-F\r\n]{0,2}\Z/)
|
90
|
+
|
91
|
+
unless quoted_matches.nil?
|
92
|
+
split_idx = chunk.length - quoted_matches[0].length + 1
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
unless split_idx.nil?
|
97
|
+
@split = chunk[split_idx..(chunk.length - 1)]
|
98
|
+
chunk = chunk[0..(split_idx - 1)]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
@chunked_sub_doc << chunk
|
103
|
+
|
104
|
+
unless is_last_part && !is_last_subdoc
|
105
|
+
@sub_docs << @chunked_sub_doc
|
106
|
+
@chunked_sub_doc = nil
|
107
|
+
@subdoc_complete_proc.call unless @subdoc_complete_proc.nil?
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def create_chunked_subdoc
|
112
|
+
@chunked_sub_doc = Document.new
|
113
|
+
|
114
|
+
@chunked_sub_doc.on_header do |header|
|
115
|
+
@subdoc_header_proc.call(header) unless @subdoc_header_proc.nil?
|
116
|
+
end
|
117
|
+
|
118
|
+
@chunked_sub_doc.on_body do |body|
|
119
|
+
@subdoc_body_proc.call(body) unless @subdoc_body_proc.nil?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/mhtml.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
|
2
|
+
module Mhtml
|
3
|
+
LINE_BREAK = "\r\n".freeze
|
4
|
+
DOUBLE_LINE_BREAK = "#{LINE_BREAK}#{LINE_BREAK}".freeze
|
5
|
+
STATUS_LINE = "HTTP/1.1 200 OK#{LINE_BREAK}".freeze
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'mhtml/document'
|
9
|
+
require 'mhtml/http_header'
|
10
|
+
require 'mhtml/root_document'
|
11
|
+
require 'mhtml/version'
|
data/lib/string.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
def each_index(x)
|
4
|
+
raise 'Block required' unless block_given?
|
5
|
+
return if empty? || x.nil?
|
6
|
+
|
7
|
+
i = 0
|
8
|
+
while true
|
9
|
+
i = index(x, i)
|
10
|
+
return if i.nil?
|
11
|
+
|
12
|
+
yield i
|
13
|
+
i += 1
|
14
|
+
|
15
|
+
return if i + 1 == length
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def strip_other(str)
|
20
|
+
start_i = 0
|
21
|
+
new_length = length
|
22
|
+
|
23
|
+
if start_with?(str)
|
24
|
+
start_i += str.length
|
25
|
+
new_length -= str.length
|
26
|
+
end
|
27
|
+
|
28
|
+
if end_with?(str)
|
29
|
+
new_length -= str.length
|
30
|
+
end
|
31
|
+
|
32
|
+
self[start_i, new_length]
|
33
|
+
end
|
34
|
+
|
35
|
+
def underscore
|
36
|
+
self.gsub(/::/, '/').
|
37
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
38
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
39
|
+
tr("-", "_").
|
40
|
+
downcase
|
41
|
+
end
|
42
|
+
|
43
|
+
def index_of_split(other)
|
44
|
+
last_idx = (other.length - 1)
|
45
|
+
|
46
|
+
(0..last_idx).step do |i|
|
47
|
+
part = other[i..last_idx]
|
48
|
+
return part.length - 1 if start_with?(part)
|
49
|
+
end
|
50
|
+
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def rindex_of_split(other)
|
55
|
+
last_idx = (other.length - 1)
|
56
|
+
|
57
|
+
(0..last_idx).step do |i|
|
58
|
+
part = other[0..(last_idx - i)]
|
59
|
+
return length - part.length if end_with?(part)
|
60
|
+
end
|
61
|
+
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
end
|
data/mhtml.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'mhtml/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'mhtml'
|
7
|
+
spec.version = Mhtml::VERSION
|
8
|
+
spec.authors = [ 'Ben Williams' ]
|
9
|
+
spec.email = [ '8enwilliams@gmail.com' ]
|
10
|
+
|
11
|
+
spec.summary = 'A Ruby gem for reading and extracting MHTML files'
|
12
|
+
spec.description = 'A Ruby gem for reading and extracting MHTML files'
|
13
|
+
spec.homepage = 'https://github.com/benjineering/mhtml_rb'
|
14
|
+
spec.licenses = [ 'MIT', 'GPL-2' ]
|
15
|
+
|
16
|
+
spec.metadata[ 'allowed_push_host' ] = 'https://rubygems.org'
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
19
|
+
f.match(%r{^(test|spec|features)/})
|
20
|
+
end
|
21
|
+
|
22
|
+
spec.require_paths = [ 'lib' ]
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.14'
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
27
|
+
spec.add_development_dependency 'byebug'
|
28
|
+
|
29
|
+
spec.add_dependency 'http-parser'
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mhtml
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ben Williams
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.14'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: byebug
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: http-parser
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: A Ruby gem for reading and extracting MHTML files
|
84
|
+
email:
|
85
|
+
- 8enwilliams@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".travis.yml"
|
93
|
+
- Gemfile
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- bin/console
|
97
|
+
- bin/setup
|
98
|
+
- lib/mhtml.rb
|
99
|
+
- lib/mhtml/document.rb
|
100
|
+
- lib/mhtml/http_header.rb
|
101
|
+
- lib/mhtml/root_document.rb
|
102
|
+
- lib/mhtml/version.rb
|
103
|
+
- lib/string.rb
|
104
|
+
- mhtml.gemspec
|
105
|
+
homepage: https://github.com/benjineering/mhtml_rb
|
106
|
+
licenses:
|
107
|
+
- MIT
|
108
|
+
- GPL-2
|
109
|
+
metadata:
|
110
|
+
allowed_push_host: https://rubygems.org
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
requirements: []
|
126
|
+
rubyforge_project:
|
127
|
+
rubygems_version: 2.6.11
|
128
|
+
signing_key:
|
129
|
+
specification_version: 4
|
130
|
+
summary: A Ruby gem for reading and extracting MHTML files
|
131
|
+
test_files: []
|