aranha-parsers 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5685f5cd07ae6f7ff6adb9f03c7ebd286a0279f508157706460617feb377937e
4
+ data.tar.gz: 3e02eb80a205bd5b26397728d6a680a3ac4bf65afdb9cd45a3e831e75b5193e2
5
+ SHA512:
6
+ metadata.gz: 468f57861a6182901b9aca6361115ae0e9bbdb192e91f9c10d34b99e4af11338b3f727a17734a70c85493495ddde7f633078560d3f52e0b4677a013fc58cd234
7
+ data.tar.gz: 56fafe12e79122cc78dafc92d8bde7721b3f1daad69ca959fc36c58e7c314af326f8cf017516ce102fb3109a1c652c86102a2d37859192849f39b104ba3037e9
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
@@ -0,0 +1,9 @@
1
+ module Aranha
2
+ module Parsers
3
+ require 'aranha/parsers/base'
4
+ require 'aranha/parsers/html'
5
+ require 'aranha/parsers/invalid_state_exception'
6
+ require 'aranha/parsers/source_address'
7
+ require 'aranha/parsers/version'
8
+ end
9
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open-uri'
4
+ require 'fileutils'
5
+ require 'aranha/parsers/source_address'
6
+
7
+ module Aranha
8
+ module Parsers
9
+ class Base
10
+ LOG_DIR_ENVVAR = 'ARANHA_PARSERS_LOG_DIR'
11
+
12
+ attr_reader :source_address
13
+
14
+ def initialize(url)
15
+ @source_address = ::Aranha::Parsers::SourceAddress.new(url)
16
+ log_content(source_address.serialize, '-source-address')
17
+ end
18
+
19
+ delegate :url, to: :source_address
20
+
21
+ def content
22
+ s = source_address.content
23
+ log_content(s)
24
+ s
25
+ end
26
+
27
+ private
28
+
29
+ def log_content(content, suffix = '')
30
+ path = log_file(suffix)
31
+
32
+ return unless path
33
+ File.open(path, 'wb') { |file| file.write(content) }
34
+ end
35
+
36
+ def log_file(suffix)
37
+ dir = log_parsers_dir
38
+ return nil unless dir
39
+ f = ::File.join(dir, "#{self.class.name.parameterize}#{suffix}.log")
40
+ FileUtils.mkdir_p(File.dirname(f))
41
+ f
42
+ end
43
+
44
+ def log_parsers_dir
45
+ return ENV[LOG_DIR_ENVVAR] if ENV[LOG_DIR_ENVVAR]
46
+ return ::Rails.root.join('log', 'parsers') if rails_root_exist?
47
+ nil
48
+ end
49
+
50
+ def rails_root_exist?
51
+ ::Rails.root
52
+ true
53
+ rescue NameError
54
+ return false
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ module Aranha
3
+ module Parsers
4
+ module Html
5
+ require 'aranha/parsers/html/base'
6
+ require 'aranha/parsers/html/item'
7
+ require 'aranha/parsers/html/item_list'
8
+ require 'aranha/parsers/html/node'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ require 'aranha/parsers/base'
5
+ require 'aranha/parsers/html/node/default'
6
+
7
+ module Aranha
8
+ module Parsers
9
+ module Html
10
+ class Base < ::Aranha::Parsers::Base
11
+ class << self
12
+ def fields
13
+ @fields ||= []
14
+ @fields.dup
15
+ end
16
+
17
+ def field(name, type, xpath)
18
+ @fields ||= []
19
+ @fields << Field.new(name, type, xpath)
20
+ end
21
+
22
+ Field = Struct.new(:name, :type, :xpath)
23
+ end
24
+
25
+ def nokogiri
26
+ @nokogiri ||= Nokogiri::HTML(content, &:noblanks)
27
+ end
28
+
29
+ protected
30
+
31
+ def node_parser_class
32
+ ::Aranha::Parsers::Html::Node::Default
33
+ end
34
+
35
+ private
36
+
37
+ def node_parser
38
+ @node_parser ||= node_parser_class.new(fields)
39
+ end
40
+
41
+ def fields
42
+ self.class.fields.map { |f| [f.name, f.type, f.xpath] }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aranha/parsers/html/base'
4
+
5
+ module Aranha
6
+ module Parsers
7
+ module Html
8
+ class Item < Base
9
+ def data
10
+ @data ||= node_parser.parse(item_node)
11
+ end
12
+
13
+ def item_node
14
+ @item_node ||= begin
15
+ r = item_xpath ? nokogiri.at_xpath(item_xpath) : nokogiri
16
+ raise "Item node not found (Item xpath: #{item_xpath})" unless r
17
+ r
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aranha/parsers/html/base'
4
+
5
+ module Aranha
6
+ module Parsers
7
+ module Html
8
+ class ItemList < Base
9
+ def data
10
+ count = 0
11
+ @data ||= nokogiri.xpath(items_xpath).map do |m|
12
+ count += 1
13
+ node_parser.parse(m)
14
+ end
15
+ rescue StandardError => e
16
+ raise StandardError, "#{e.message} (Count: #{count})"
17
+ end
18
+
19
+ def items_xpath
20
+ raise "Class #{self.class} has no method \"item_xpath\". Implement it"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ module Aranha
3
+ module Parsers
4
+ module Html
5
+ module Node
6
+ require 'aranha/parsers/html/node/base'
7
+ require 'aranha/parsers/html/node/default'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aranha
4
+ module Parsers
5
+ module Html
6
+ module Node
7
+ class Base
8
+ attr_reader :fields
9
+
10
+ def initialize(fields)
11
+ @fields = fields
12
+ end
13
+
14
+ def parse(node)
15
+ Hash[fields.map { |f| [f[0], parse_field(node, f[2], f[1])] }]
16
+ end
17
+
18
+ private
19
+
20
+ def parse_field(node, xpath, parser_method)
21
+ value_method = "#{parser_method}_value"
22
+ return send(value_method, node, xpath) if respond_to?(value_method)
23
+
24
+ raise "Method \"#{value_method}\" not found in #{self.class}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aranha/parsers/html/node/base'
4
+
5
+ module Aranha
6
+ module Parsers
7
+ module Html
8
+ module Node
9
+ class Default < ::Aranha::Parsers::Html::Node::Base
10
+ def string_value(node, xpath)
11
+ if node.at_xpath(xpath)
12
+ node.at_xpath(xpath).text.to_s.tr("\u00A0", ' ').strip
13
+ else
14
+ ''
15
+ end
16
+ end
17
+
18
+ def quoted_value(node, xpath)
19
+ s = string_value(node, xpath)
20
+ return '' unless s
21
+
22
+ m = /\"([^\"]+)\"/.match(s)
23
+ return m[1] if m
24
+
25
+ ''
26
+ end
27
+
28
+ def integer_value(node, xpath)
29
+ r = string_value(node, xpath)
30
+ return nil if r.blank?
31
+
32
+ m = /\d+/.match(r)
33
+ raise "Integer not found in \"#{r}\"" unless m
34
+
35
+ m[0].to_i
36
+ end
37
+
38
+ def integer_optional_value(node, xpath)
39
+ r = string_value(node, xpath)
40
+ m = /\d+/.match(r)
41
+ m ? m[0].to_i : nil
42
+ end
43
+
44
+ def float_value(node, xpath)
45
+ parse_float(node, xpath, true)
46
+ end
47
+
48
+ def float_optional_value(node, xpath)
49
+ parse_float(node, xpath, false)
50
+ end
51
+
52
+ def array_value(node, xpath)
53
+ r = node.xpath(xpath).map { |n| n.text.strip }
54
+ r.join('|')
55
+ end
56
+
57
+ def join_value(node, xpath)
58
+ m = ''
59
+ node.xpath(xpath).each do |n|
60
+ m << n.text.strip
61
+ end
62
+ m
63
+ end
64
+
65
+ def duration_value(node, xpath)
66
+ m = /(\d+) m/.match(join_value(node, xpath))
67
+ m ? m[1].to_i : nil
68
+ end
69
+
70
+ def regxep(node, xpath, pattern)
71
+ s = string_value(node, xpath)
72
+ m = pattern.match(s)
73
+ return m if m
74
+
75
+ raise "Pattern \"#{pattern}\" not found in string \"#{s}\""
76
+ end
77
+
78
+ private
79
+
80
+ def parse_float(node, xpath, required)
81
+ s = string_value(node, xpath)
82
+ m = /\d+(?:[\.\,](\d+))?/.match(s)
83
+ if m
84
+ m[0].sub(',', '.').to_f
85
+ elsif required
86
+ raise "Float value not found in \"#{s}\""
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aranha
4
+ module Parsers
5
+ class InvalidStateException < StandardError
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'active_support/core_ext/module/delegation'
5
+ require 'aranha/parsers/source_address/hash_http_get'
6
+ require 'aranha/parsers/source_address/hash_http_post'
7
+ require 'aranha/parsers/source_address/http_get'
8
+ require 'aranha/parsers/source_address/file'
9
+
10
+ module Aranha
11
+ module Parsers
12
+ class SourceAddress
13
+ class << self
14
+ SUBS = [
15
+ ::Aranha::Parsers::SourceAddress::HashHttpGet,
16
+ ::Aranha::Parsers::SourceAddress::HashHttpPost,
17
+ ::Aranha::Parsers::SourceAddress::HttpGet,
18
+ ::Aranha::Parsers::SourceAddress::File
19
+ ].freeze
20
+
21
+ def detect_sub(source)
22
+ return source.sub if source.is_a?(self)
23
+ SUBS.each do |sub|
24
+ return sub.new(source) if sub.valid_source?(source)
25
+ end
26
+ raise "No content fetcher found for source \"#{source}\""
27
+ end
28
+
29
+ def deserialize(string)
30
+ new(string =~ %r{\A[a-z]+://} ? string.strip : ::YAML.load(string))
31
+ end
32
+
33
+ def from_file(path)
34
+ deserialize(::File.read(path))
35
+ end
36
+ end
37
+
38
+ attr_reader :sub
39
+
40
+ def initialize(source)
41
+ @sub = self.class.detect_sub(source)
42
+ end
43
+
44
+ delegate :content, :url, to: :sub
45
+
46
+ def to_s
47
+ sub.url
48
+ end
49
+
50
+ def serialize
51
+ sub.serialize.strip + "\n"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aranha/parsers/source_address/http_get'
4
+
5
+ module Aranha
6
+ module Parsers
7
+ class SourceAddress
8
+ class File < ::Aranha::Parsers::SourceAddress::HttpGet
9
+ SCHEME = 'file://'
10
+
11
+ class << self
12
+ def valid_source?(source)
13
+ source.to_s.start_with?(SCHEME + '/', '/')
14
+ end
15
+ end
16
+
17
+ def initialize(source)
18
+ super source.to_s.gsub(/\A#{Regexp.quote(SCHEME)}/, '')
19
+ end
20
+
21
+ def url
22
+ "#{SCHEME}#{source}"
23
+ end
24
+
25
+ def content
26
+ ::File.open(source, &:read)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aranha/parsers/source_address/hash_http_post'
4
+
5
+ module Aranha
6
+ module Parsers
7
+ class SourceAddress
8
+ class HashHttpGet < ::Aranha::Parsers::SourceAddress::HashHttpPost
9
+ class << self
10
+ def valid_source?(source)
11
+ source.is_a?(::Hash) &&
12
+ source.with_indifferent_access[:method].to_s.downcase.strip == 'get'
13
+ end
14
+ end
15
+
16
+ def content
17
+ HTTPClient.new.get_content(
18
+ source[:url],
19
+ source[:params]
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/hash/indifferent_access'
4
+ require 'httpclient'
5
+ require 'yaml'
6
+
7
+ module Aranha
8
+ module Parsers
9
+ class SourceAddress
10
+ class HashHttpPost
11
+ class << self
12
+ def valid_source?(source)
13
+ source.is_a?(::Hash) &&
14
+ source.with_indifferent_access[:method].to_s.downcase.strip == 'post'
15
+ end
16
+ end
17
+
18
+ attr_reader :source
19
+
20
+ def initialize(source)
21
+ @source = source.with_indifferent_access
22
+ end
23
+
24
+ def ==(other)
25
+ self.class == other.class && source == other.source
26
+ end
27
+
28
+ def url
29
+ source.fetch(:url)
30
+ end
31
+
32
+ def serialize
33
+ source.to_yaml
34
+ end
35
+
36
+ def content
37
+ HTTPClient.new.post_content(
38
+ source[:url],
39
+ source[:params].merge(follow_redirect: true)
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'addressable'
4
+ require 'net/http'
5
+
6
+ module Aranha
7
+ module Parsers
8
+ class SourceAddress
9
+ class HttpGet
10
+ class << self
11
+ def location_uri(source_uri, location)
12
+ ::Addressable::URI.join(source_uri, location).to_s
13
+ end
14
+
15
+ def valid_source?(source)
16
+ source.to_s =~ %r{\Ahttps?://}
17
+ end
18
+ end
19
+
20
+ attr_reader :source
21
+
22
+ def initialize(source)
23
+ @source = source.to_s
24
+ end
25
+
26
+ def ==(other)
27
+ self.class == other.class && source == other.source
28
+ end
29
+
30
+ def url
31
+ source
32
+ end
33
+
34
+ def content
35
+ content_fetch(url)
36
+ end
37
+
38
+ def serialize
39
+ url
40
+ end
41
+
42
+ private
43
+
44
+ def content_fetch(uri, limit = 10)
45
+ raise 'too many HTTP redirects' if limit.zero?
46
+
47
+ response = Net::HTTP.get_response(URI(uri))
48
+
49
+ case response
50
+ when Net::HTTPSuccess then
51
+ response.body
52
+ when Net::HTTPRedirection then
53
+ content_fetch(self.class.location_uri(uri, response['location']), limit - 1)
54
+ else
55
+ response.value
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,67 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'yaml'
5
+
6
+ module Aranha
7
+ module Spec
8
+ # Lists pairs of source/target files in a directory.
9
+ class SourceTargetFixtures
10
+ class << self
11
+ def source_target_basename(file)
12
+ m = /^(.+)\.(?:source|target)(?:\..+)?$/.match(File.basename(file))
13
+ m ? m[1] : nil
14
+ end
15
+ end
16
+
17
+ attr_reader :fixtures_directory
18
+
19
+ def initialize(fixtures_directory)
20
+ @fixtures_directory = fixtures_directory
21
+ end
22
+
23
+ def source_target_files
24
+ sources_targets_basenames.map do |basename|
25
+ OpenStruct.new(source: source_file(basename), target: target_file(basename))
26
+ end
27
+ end
28
+
29
+ def source_files
30
+ r = []
31
+ source_target_files.each do |st|
32
+ r << st.source if st.source
33
+ end
34
+ r
35
+ end
36
+
37
+ def target_file(basename)
38
+ fixture_file(basename, 'target')
39
+ end
40
+
41
+ def source_file(basename)
42
+ fixture_file(basename, 'source')
43
+ end
44
+
45
+ private
46
+
47
+ def fixture_file(basename, suffix)
48
+ prefix = "#{basename}.#{suffix}"
49
+ Dir.foreach(fixtures_directory) do |item|
50
+ next if item == '.' || item == '..'
51
+ return File.expand_path(item, fixtures_directory) if item.starts_with?(prefix)
52
+ end
53
+ nil
54
+ end
55
+
56
+ def sources_targets_basenames
57
+ basenames = Set.new
58
+ Dir.foreach(fixtures_directory) do |item|
59
+ next if item == '.' || item == '..'
60
+ b = self.class.source_target_basename(item)
61
+ basenames << b if b.present?
62
+ end
63
+ basenames
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'source_target_fixtures'
4
+
5
+ RSpec.shared_examples 'source_target_fixtures' do |spec_file| # rubocop:disable Metrics/BlockLength
6
+ let(:spec_file) { spec_file }
7
+
8
+ it 'fixtures directory should exist' do
9
+ expect(::File.directory?(fixtures_dir)).to be true
10
+ end
11
+
12
+ context 'in fixtures directory' do
13
+ it 'should have at least one file' do
14
+ expect(source_target_fixtures.source_target_files.count).to be > 0
15
+ end
16
+
17
+ if ENV['WRITE_TARGET_FIXTURES']
18
+ it 'should write target data for all files' do
19
+ source_target_fixtures.source_files.each do |source_file|
20
+ sd = sort_results(source_data(source_file))
21
+ basename = ::Aranha::Spec::SourceTargetFixtures.source_target_basename(source_file)
22
+ target_file = File.expand_path("../#{basename}.target.yaml", source_file)
23
+ File.write(target_file, sd.to_yaml)
24
+ end
25
+ end
26
+ else
27
+ it 'should parse data for all files' do
28
+ source_target_fixtures.source_target_files.each do |st|
29
+ assert_source_target_complete(st)
30
+ sd = source_data(st.source)
31
+ td = YAML.load_file(st.target)
32
+ expect(sort_results(sd)).to eq(sort_results(td))
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def source_target_fixtures
39
+ @source_target_fixtures ||= ::Aranha::Spec::SourceTargetFixtures.new(fixtures_dir)
40
+ end
41
+
42
+ def assert_source_target_complete(st)
43
+ expect(st.source).to(be_truthy, "Source not found (Target: #{st.target})")
44
+ expect(st.target).to(be_truthy, "Target not found (Source: #{st.source})")
45
+ end
46
+
47
+ def source_data(source_file)
48
+ described_class.new(source_file).data
49
+ end
50
+
51
+ def fixtures_dir
52
+ ::File.join(
53
+ ::File.dirname(spec_file),
54
+ ::File.basename(spec_file, '.*') + '_files'
55
+ )
56
+ end
57
+
58
+ def sort_results(r)
59
+ r
60
+ end
61
+ end
@@ -0,0 +1,5 @@
1
+ module Aranha
2
+ module Parsers
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aranha-parsers
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Esquilo Azul Company
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-09-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 4.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 4.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: addressable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: httpclient
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.8'
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 2.8.3
51
+ type: :runtime
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - "~>"
56
+ - !ruby/object:Gem::Version
57
+ version: '2.8'
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 2.8.3
61
+ - !ruby/object:Gem::Dependency
62
+ name: rspec
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.8'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.8'
75
+ description:
76
+ email:
77
+ executables: []
78
+ extensions: []
79
+ extra_rdoc_files: []
80
+ files:
81
+ - Gemfile
82
+ - lib/aranha/parsers.rb
83
+ - lib/aranha/parsers/base.rb
84
+ - lib/aranha/parsers/html.rb
85
+ - lib/aranha/parsers/html/base.rb
86
+ - lib/aranha/parsers/html/item.rb
87
+ - lib/aranha/parsers/html/item_list.rb
88
+ - lib/aranha/parsers/html/node.rb
89
+ - lib/aranha/parsers/html/node/base.rb
90
+ - lib/aranha/parsers/html/node/default.rb
91
+ - lib/aranha/parsers/invalid_state_exception.rb
92
+ - lib/aranha/parsers/source_address.rb
93
+ - lib/aranha/parsers/source_address/file.rb
94
+ - lib/aranha/parsers/source_address/hash_http_get.rb
95
+ - lib/aranha/parsers/source_address/hash_http_post.rb
96
+ - lib/aranha/parsers/source_address/http_get.rb
97
+ - lib/aranha/parsers/spec/source_target_fixtures.rb
98
+ - lib/aranha/parsers/spec/source_target_fixtures_example.rb
99
+ - lib/aranha/parsers/version.rb
100
+ homepage:
101
+ licenses: []
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.7.7
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: Parsers' utilities for Ruby.
123
+ test_files: []