foxy 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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +8 -0
- data/.travis.yml +4 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +52 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/foxy.gemspec +29 -0
- data/lib/foxy.rb +29 -0
- data/lib/foxy/adverb.rb +87 -0
- data/lib/foxy/client.rb +79 -0
- data/lib/foxy/collection.rb +70 -0
- data/lib/foxy/extensions.rb +60 -0
- data/lib/foxy/file_cache.rb +85 -0
- data/lib/foxy/html.rb +114 -0
- data/lib/foxy/html_response.rb +29 -0
- data/lib/foxy/node.rb +106 -0
- data/lib/foxy/rate_limit.rb +19 -0
- data/lib/foxy/repository.rb +111 -0
- data/lib/foxy/storages/yaml.rb +77 -0
- data/lib/foxy/version.rb +3 -0
- metadata +153 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 023199508c4795ba3684051b2e66b867c3827402
|
4
|
+
data.tar.gz: adaa60aee3b7c730730cb004c8305283d29fdd40
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a5a89839425f54a1ebec774b9acdf38b75fa090832af905e7739387ad7ca75ec9ce1c9336064a68c9f305e4fa3b28626f067a51207ac823c1bb2c91d24eb2fb4
|
7
|
+
data.tar.gz: 6c814a1fd26e59b05eb37f338a8d26ce9c719f94b45b0a400e3961e989c3f8e7ac808fb99d685cd4d029aaa6c6dc89095eedf00a3941d37487299d6732042952
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Manuel Albarrán
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Foxy
|
2
|
+
|
3
|
+
A set of `Foxy` tools for make easy retrieve information for another servers.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'foxy', :git => 'git://github.com/weapp/foxyrb.git'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
require "foxy"
|
21
|
+
require "pp"
|
22
|
+
|
23
|
+
response = Foxy::Client.new.eraw(path: "https://www.w3.org/")
|
24
|
+
|
25
|
+
puts
|
26
|
+
puts "Example1"
|
27
|
+
puts "Way 1:"
|
28
|
+
results = response.foxy.search(cls: "info-wrap")
|
29
|
+
results.each do |result|
|
30
|
+
pp(summary: result.find(cls: "summary").try(:joinedtexts),
|
31
|
+
source: result.find(cls: "source").try(:joinedtexts),
|
32
|
+
where: result.find(cls: "location").try(:joinedtexts))
|
33
|
+
end
|
34
|
+
|
35
|
+
puts "Way 2:"
|
36
|
+
results = response.foxy.css(".info-wrap")
|
37
|
+
results.each do |result|
|
38
|
+
pp(summary: result.css(".summary").first.try(:joinedtexts),
|
39
|
+
source: result.css(".source").first.try(:joinedtexts),
|
40
|
+
where: result.css(".location").first.try(:joinedtexts))
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
## Contributing
|
45
|
+
|
46
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/weapp/foxy.
|
47
|
+
|
48
|
+
|
49
|
+
## License
|
50
|
+
|
51
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
52
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "foxy"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/foxy.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'foxy/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "foxy"
|
8
|
+
spec.version = Foxy::VERSION
|
9
|
+
spec.authors = ["Manuel Albarrán"]
|
10
|
+
spec.email = ["weap88@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Foxy tools for foxy things.}
|
13
|
+
spec.description = %q{A set of foxy tools for make easy retrieve information for another servers.}
|
14
|
+
spec.homepage = "https://github.com/weapp/foxyrb"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "faraday", "~> 0.9.2"
|
23
|
+
spec.add_dependency "faraday_middleware", "~> 0.10.0"
|
24
|
+
spec.add_dependency "patron", "~> 0.6.1"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
29
|
+
end
|
data/lib/foxy.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require "foxy/version"
|
2
|
+
require "foxy/client"
|
3
|
+
require "foxy/html"
|
4
|
+
require "foxy/repository"
|
5
|
+
|
6
|
+
module Foxy
|
7
|
+
class << self
|
8
|
+
attr_accessor :env
|
9
|
+
end
|
10
|
+
|
11
|
+
RE_HTML = %r{
|
12
|
+
(</[a-zA-Z]+[^>]*>) #closetag
|
13
|
+
|(<[a-zA-Z]+(?:[^/>]|/[^>])*/>) #singletag
|
14
|
+
|(<[a-zA-Z]+[^>]*>) #tag
|
15
|
+
|([^<]+) #notag
|
16
|
+
|(<!--.*?-->) #|(<![^>]*>) #comment
|
17
|
+
|(.) #other}imx
|
18
|
+
|
19
|
+
RE_TAG = /<([a-zA-Z]+[0-9]*)/m
|
20
|
+
RE_TAG_ID = /id=(("[^"]*")|('[^']*')|[^\s>]+)/m
|
21
|
+
RE_TAG_CLS = /class=(("[^"]*")|('[^']*')|[^\s>]+)/m
|
22
|
+
RE_CLOSETAG = %r{</([a-zA-Z]+[0-9]*)}m
|
23
|
+
|
24
|
+
SINGLES = %w(meta img link input area base col br hr).freeze
|
25
|
+
ALLOW = %w(alt src href title).freeze
|
26
|
+
INLINE_TAGS = %w(a abbr acronym b br code em font i
|
27
|
+
img ins kbd map samp small span strong
|
28
|
+
sub sup textarea).freeze
|
29
|
+
end
|
data/lib/foxy/adverb.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
module Foxy
|
2
|
+
class Adverb
|
3
|
+
attr_accessor :value
|
4
|
+
|
5
|
+
def self.define(&block)
|
6
|
+
Class.new(self) { define_method(:and_then, &block) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(value)
|
10
|
+
@value = value
|
11
|
+
end
|
12
|
+
|
13
|
+
def and_then
|
14
|
+
yield value
|
15
|
+
end
|
16
|
+
|
17
|
+
def then(&block)
|
18
|
+
self.class.new(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def tap(*args, &block)
|
22
|
+
method_missing(:tap, *args, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_missing(m, *args, &block)
|
26
|
+
and_then { |instance| instance.public_send(m, *args, &block) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Dangerously = Adverb.define do |&block|
|
31
|
+
block.call(value).tap { |result| fail "nil!" if result.nil? }
|
32
|
+
end
|
33
|
+
|
34
|
+
Optional = Adverb.define do |&block|
|
35
|
+
value.nil? ? nil : block.call(value)
|
36
|
+
end
|
37
|
+
|
38
|
+
Mapy = Adverb.define do |&block|
|
39
|
+
value.map { |v| block.call(v) }
|
40
|
+
end
|
41
|
+
|
42
|
+
Many = Adverb.define do |&block|
|
43
|
+
value.flat_map { |v| block.call(v) }
|
44
|
+
end
|
45
|
+
|
46
|
+
Safy = Adverb.define do |&block|
|
47
|
+
begin
|
48
|
+
block.call(value)
|
49
|
+
rescue
|
50
|
+
value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module Monads
|
55
|
+
def safy
|
56
|
+
Safy.new(self)
|
57
|
+
end
|
58
|
+
|
59
|
+
def optionaly
|
60
|
+
Optional.new(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
def mapy
|
64
|
+
Mapy.new(self)
|
65
|
+
end
|
66
|
+
|
67
|
+
def many
|
68
|
+
Many.new(self)
|
69
|
+
end
|
70
|
+
|
71
|
+
def dangerously
|
72
|
+
Dangerously.new(self)
|
73
|
+
end
|
74
|
+
|
75
|
+
def normally
|
76
|
+
Adverb.new(self)
|
77
|
+
end
|
78
|
+
|
79
|
+
def then
|
80
|
+
yield value
|
81
|
+
end
|
82
|
+
|
83
|
+
def and_then
|
84
|
+
self
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/foxy/client.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require "json"
|
2
|
+
require "faraday"
|
3
|
+
require 'faraday_middleware'
|
4
|
+
require "patron"
|
5
|
+
|
6
|
+
require "foxy/extensions"
|
7
|
+
require "foxy/rate_limit"
|
8
|
+
require "foxy/file_cache"
|
9
|
+
require "foxy/html_response"
|
10
|
+
|
11
|
+
module Foxy
|
12
|
+
class Client
|
13
|
+
include RateLimit
|
14
|
+
|
15
|
+
attr_reader :conn, :config, :default_options
|
16
|
+
|
17
|
+
def initialize(config = {})
|
18
|
+
@config = config
|
19
|
+
@default_options = config.fetch(:default_options, {}).recursive_hash
|
20
|
+
|
21
|
+
@conn = Faraday.new(url: url) do |connection|
|
22
|
+
connection.options[:timeout] = config.fetch(:timeout, 120)
|
23
|
+
connection.options[:open_timeout] = config.fetch(:open_timeout, 20)
|
24
|
+
connection.headers[:user_agent] = user_agent
|
25
|
+
|
26
|
+
connection.use(Faraday::Response::Middleware)
|
27
|
+
# connection.response :logger
|
28
|
+
# connection.response :json
|
29
|
+
# connection.use FaradayMiddleware::Gzip
|
30
|
+
# connection.adapter(Faraday.default_adapter)
|
31
|
+
connection.adapter :patron
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def user_agent
|
36
|
+
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
|
37
|
+
end
|
38
|
+
|
39
|
+
def request(options)
|
40
|
+
wait!
|
41
|
+
opts = default_options.deep_merge(options)
|
42
|
+
|
43
|
+
conn.get(opts.fetch(:path), opts.fetch(:params, {}))
|
44
|
+
end
|
45
|
+
|
46
|
+
def json(options)
|
47
|
+
JSON[raw(options)]
|
48
|
+
end
|
49
|
+
|
50
|
+
def raw(options)
|
51
|
+
request(options).body
|
52
|
+
end
|
53
|
+
|
54
|
+
def eraw(options)
|
55
|
+
cacheopts = options.delete(:cache)
|
56
|
+
klass = options.delete(:class) || Foxy::HtmlResponse
|
57
|
+
response_options = options.merge(options.delete(:response_params) || {})
|
58
|
+
klass.new(raw_with_cache(options, cacheopts), response_options)
|
59
|
+
end
|
60
|
+
|
61
|
+
def url
|
62
|
+
"http://www.example.com"
|
63
|
+
end
|
64
|
+
|
65
|
+
def cache
|
66
|
+
@cache ||= FileCache.new(self.class.name.split("::").last.downcase)
|
67
|
+
end
|
68
|
+
|
69
|
+
def fixed(id, legth = 2, fill = "0")
|
70
|
+
id.to_s.rjust(legth, fill)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def raw_with_cache(options, cacheopts)
|
76
|
+
raw(options)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
3
|
+
module Foxy
|
4
|
+
class Collection < SimpleDelegator
|
5
|
+
include Monads
|
6
|
+
|
7
|
+
def attr(name)
|
8
|
+
each_with_object([]) { |node, acc| acc << node.attr(name) if node }
|
9
|
+
end
|
10
|
+
|
11
|
+
def search(**kws)
|
12
|
+
return search(parse_css(kws[:css])) if kws[:css]
|
13
|
+
|
14
|
+
filters = kws.delete(:filters)
|
15
|
+
|
16
|
+
Collection.new(flat_map { |node| node.search(**kws) }).tap do |r|
|
17
|
+
return filters.inject(r) { |_memo, filter| r.public_send(filter) } if filters
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def css(query)
|
22
|
+
query.split(/\s+/).inject(self) { |memo, q| memo.search(css: q) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def texts
|
26
|
+
map(&:texts).__getobj__
|
27
|
+
end
|
28
|
+
|
29
|
+
def joinedtexts
|
30
|
+
each_with_object([]) { |node, acc| acc << node.joinedtexts if node }
|
31
|
+
end
|
32
|
+
|
33
|
+
def map
|
34
|
+
self.class.new(super)
|
35
|
+
end
|
36
|
+
|
37
|
+
def flat_map
|
38
|
+
self.class.new(super)
|
39
|
+
end
|
40
|
+
|
41
|
+
def rebuild
|
42
|
+
map(&:to_s).__getobj__.join
|
43
|
+
end
|
44
|
+
|
45
|
+
def clean(*args)
|
46
|
+
mapy.clean(*args)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# assert Foxy::Html.new.parse_css("tag#id") == {tagname: "tag", id: "id"}
|
52
|
+
# assert Foxy::Html.new.parse_css("#id") == {id: "id"}
|
53
|
+
# assert Foxy::Html.new.parse_css("tag") == {tagname: "tag"}
|
54
|
+
# assert Foxy::Html.new.parse_css("tag.cls") == {tagname: "tag", cls: ["cls"]}
|
55
|
+
# assert Foxy::Html.new.parse_css(".class") == {cls: ["class"]}
|
56
|
+
# assert Foxy::Html.new.parse_css(".class.class") == {cls: ["class", "class"]}
|
57
|
+
# assert Foxy::Html.new.parse_css(".cls.class") == {cls: ["cls", "class"]}
|
58
|
+
def parse_css(css)
|
59
|
+
token = "([^:#\.\s]+)"
|
60
|
+
css
|
61
|
+
.scan(/#{token}|##{token}|\.#{token}|:#{token}/)
|
62
|
+
.each_with_object({}) do |(tagname, id, cls, filter), memo|
|
63
|
+
next memo[:tagname] = tagname if tagname
|
64
|
+
next memo[:id] = id if id
|
65
|
+
memo.fetch(:filters) { memo[:filters] = [] } << filter if filter
|
66
|
+
memo.fetch(:cls) { memo[:cls] = [] } << cls if cls
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "foxy/adverb"
|
2
|
+
|
3
|
+
class Object
|
4
|
+
def deep_symbolize_keys
|
5
|
+
self
|
6
|
+
end
|
7
|
+
|
8
|
+
def try(m, *a, &b)
|
9
|
+
public_send(m, *a, &b) if respond_to?(m)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Hash
|
14
|
+
def deep_symbolize_keys
|
15
|
+
symbolize_keys.tap { |h| h.each { |k, v| h[k] = v.deep_symbolize_keys } }
|
16
|
+
end
|
17
|
+
|
18
|
+
def symbolize_keys
|
19
|
+
Hash[map { |k, v| [k.to_sym, v] }]
|
20
|
+
end
|
21
|
+
|
22
|
+
def deep_merge(second)
|
23
|
+
merger = proc { |_, v1, v2| v1.is_a?(Hash) && v2.is_a?(Hash) ? v1.merge(v2, &merger) : v2 }
|
24
|
+
merge(second, &merger)
|
25
|
+
end
|
26
|
+
|
27
|
+
def recursive_hash
|
28
|
+
tap { self.default_proc = proc { |h, k| h[k] = Hash.new(&h.default_proc) } }
|
29
|
+
end
|
30
|
+
|
31
|
+
def slice(*keys)
|
32
|
+
Hash[keys.zip(values_at(*keys))]
|
33
|
+
end
|
34
|
+
|
35
|
+
def downcase_keys
|
36
|
+
each_with_object({}) { |(k, v), h| h.store(k.downcase, v) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Array
|
41
|
+
def deep_symbolize_keys
|
42
|
+
map(&:deep_symbolize_keys)
|
43
|
+
end
|
44
|
+
|
45
|
+
def mapy
|
46
|
+
Foxy::Mapy.new(self)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class NilClass
|
51
|
+
def try(*_args)
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Enumerator::Yielder
|
57
|
+
def +(enum)
|
58
|
+
enum.each { |it| self << it }
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module Foxy
|
4
|
+
class FileCache
|
5
|
+
class << self
|
6
|
+
attr_accessor :nocache
|
7
|
+
|
8
|
+
def nocache!(reason = nil)
|
9
|
+
self.nocache = true
|
10
|
+
puts "NO CACHE: #{reason}"
|
11
|
+
binding.pry
|
12
|
+
end
|
13
|
+
|
14
|
+
def html(*path, &block)
|
15
|
+
cache path, "html", &block
|
16
|
+
end
|
17
|
+
|
18
|
+
def raw(*path, &block)
|
19
|
+
cache path, "txt", &block
|
20
|
+
end
|
21
|
+
|
22
|
+
def json(*path)
|
23
|
+
JSON[cache(path, "json") { JSON[yield] }]
|
24
|
+
end
|
25
|
+
|
26
|
+
def yaml(*path)
|
27
|
+
YAML.load(cache(path, "yaml") { YAML.dump(yield) })
|
28
|
+
end
|
29
|
+
|
30
|
+
def html!(*path, &block)
|
31
|
+
cache! path, "html", &block
|
32
|
+
end
|
33
|
+
|
34
|
+
def raw!(*path, &block)
|
35
|
+
cache! path, "txt", &block
|
36
|
+
end
|
37
|
+
|
38
|
+
def json!(*path)
|
39
|
+
JSON[cache!(path, "json") { JSON[yield] }]
|
40
|
+
end
|
41
|
+
|
42
|
+
def yaml!(*path)
|
43
|
+
YAML.load(cache!(path, "yaml") { YAML.dump(yield) })
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def cache(path, format, force = false)
|
49
|
+
self.nocache = false
|
50
|
+
path_tokens = path.map { |slice| slice.to_s.gsub(/[^a-z0-9\-]+/i, "_") }
|
51
|
+
.unshift("cache")
|
52
|
+
filepath = path_tokens.join("/") + ".#{format}"
|
53
|
+
|
54
|
+
return File.read(filepath) if File.exist?(filepath) && !force
|
55
|
+
|
56
|
+
makedir_p(path_tokens[0...-1])
|
57
|
+
res = yield.to_s
|
58
|
+
File.write(filepath, res) unless nocache
|
59
|
+
|
60
|
+
res
|
61
|
+
end
|
62
|
+
|
63
|
+
def cache!(path, format, &block)
|
64
|
+
cache(path, format, true, &block)
|
65
|
+
end
|
66
|
+
|
67
|
+
def makedir_p(tokens)
|
68
|
+
1.upto(tokens.size) do |n|
|
69
|
+
dir = tokens[0...n].join("/")
|
70
|
+
Dir.mkdir(dir) unless Dir.exist?(dir)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def initialize(*path)
|
76
|
+
@path = path
|
77
|
+
end
|
78
|
+
|
79
|
+
%i(html raw json yaml html! raw! json! yaml!).each do |format|
|
80
|
+
define_method(format) do |*path, &block|
|
81
|
+
self.class.send(format, *(@path + path), &block)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/foxy/html.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require "delegate"
|
2
|
+
require "foxy/adverb"
|
3
|
+
require "foxy/collection"
|
4
|
+
require "foxy/node"
|
5
|
+
|
6
|
+
module Foxy
|
7
|
+
class Html < SimpleDelegator
|
8
|
+
include Monads
|
9
|
+
|
10
|
+
def try(m, *a, &b)
|
11
|
+
public_send(m, *a, &b) if respond_to?(m)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(html = nil)
|
15
|
+
if html.nil?
|
16
|
+
super([])
|
17
|
+
elsif html.is_a? self.class
|
18
|
+
super(html.__getobj__)
|
19
|
+
elsif html.respond_to?(:to_str)
|
20
|
+
super(html.to_str.scan(RE_HTML).map { |args| Node.build(*args) })
|
21
|
+
elsif html.respond_to?(:read)
|
22
|
+
super(html.read.scan(RE_HTML).map { |args| Node.build(*args) })
|
23
|
+
else
|
24
|
+
super(html)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def clean(**kws)
|
29
|
+
Html.new(map { |node| node.clean(**kws) })
|
30
|
+
end
|
31
|
+
|
32
|
+
def isearch(tagname: nil, id: nil, cls: nil, fun: nil, css: nil)
|
33
|
+
cls = Array(cls)
|
34
|
+
tagname &&= tagname.downcase
|
35
|
+
y = 0
|
36
|
+
buff = []
|
37
|
+
|
38
|
+
close_tagname = nil
|
39
|
+
each do |node| # [1:-1]:
|
40
|
+
# El orden de los if es importante para que devuelva el
|
41
|
+
# primer y el ultimo nodo
|
42
|
+
if y.zero? && node.tag? && (!tagname || node.tagname! == tagname) &&
|
43
|
+
(!id || node.id! == id) && (cls - node.cls!).empty? &&
|
44
|
+
(!fun || fun.call(node))
|
45
|
+
# Guardamos porque pudiera ser que el parametro
|
46
|
+
# tagname fuera nil
|
47
|
+
close_tagname = node.tagname!
|
48
|
+
y += 1
|
49
|
+
|
50
|
+
elsif y && node.tag? && node.tagname! == close_tagname
|
51
|
+
y += 1
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
buff << node if y > 0
|
56
|
+
|
57
|
+
y -= 1 if y > 0 && node.closetag? && node.tagname! == close_tagname
|
58
|
+
|
59
|
+
next unless buff && y.zero?
|
60
|
+
yield Html.new(buff)
|
61
|
+
buff = []
|
62
|
+
close_tagname = nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def search(**kws)
|
67
|
+
return Collection.new([self]).search(kws) if kws[:css]
|
68
|
+
list = []
|
69
|
+
isearch(**kws) { |val| list << val unless val.empty? }
|
70
|
+
Collection.new(list)
|
71
|
+
end
|
72
|
+
|
73
|
+
def css(query)
|
74
|
+
Collection.new([self]).css(query)
|
75
|
+
end
|
76
|
+
|
77
|
+
def find(**kws)
|
78
|
+
isearch(**kws) { |val| return val unless val.empty? }
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def rebuild
|
83
|
+
map(&:content).join
|
84
|
+
end
|
85
|
+
|
86
|
+
def texts
|
87
|
+
each_with_object([]) { |node, acc| acc << node.content if node.type == :notag }
|
88
|
+
end
|
89
|
+
|
90
|
+
def comments
|
91
|
+
each_with_object([]) { |node, acc| acc << node.content.sub(/^<!--/, "").sub(/-->$/, "") if node.type == :comment }
|
92
|
+
end
|
93
|
+
|
94
|
+
def joinedtexts
|
95
|
+
texts.join.gsub(/[\r\n\s]+/, " ").strip
|
96
|
+
end
|
97
|
+
|
98
|
+
def attr(name)
|
99
|
+
first.attr(name)
|
100
|
+
end
|
101
|
+
|
102
|
+
def id
|
103
|
+
first.id
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_s
|
107
|
+
rebuild
|
108
|
+
end
|
109
|
+
|
110
|
+
%i(src href title).each do |m|
|
111
|
+
define_method(m) { self.attr(m) }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Foxy
|
2
|
+
class HtmlResponse
|
3
|
+
attr_accessor :html, :params
|
4
|
+
|
5
|
+
def initialize(html, params)
|
6
|
+
@html = html
|
7
|
+
@params = params
|
8
|
+
end
|
9
|
+
|
10
|
+
def foxy
|
11
|
+
@foxy ||= Foxy::Html.new(@html)
|
12
|
+
end
|
13
|
+
|
14
|
+
def clean
|
15
|
+
@clean ||= foxy.clean(allow: %w(alt src href title class))
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def is_number(hash, key)
|
21
|
+
hash[key] = hash[key].try(:gsub, ",", "").try(:to_i) if hash[key]
|
22
|
+
end
|
23
|
+
|
24
|
+
def is_list(hash, key, sep = /\s*,\s*/)
|
25
|
+
return if hash[key].is_a? Array
|
26
|
+
hash[key] = hash[key].to_s.split(sep)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/foxy/node.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
3
|
+
module Foxy
|
4
|
+
Node = Struct.new(:type, :content, :extra) do
|
5
|
+
include Monads
|
6
|
+
|
7
|
+
def attr(name)
|
8
|
+
value = attr_regex(name).match(content)
|
9
|
+
value && value[1].sub(/\A('|")?/, "").sub(/('|")?\Z/, "")
|
10
|
+
end
|
11
|
+
|
12
|
+
def tag?
|
13
|
+
[:tag, :singletag].include? type
|
14
|
+
end
|
15
|
+
|
16
|
+
def closetag?
|
17
|
+
[:closetag, :singletag].include? type
|
18
|
+
end
|
19
|
+
|
20
|
+
def tagname
|
21
|
+
(tag? || closetag?) && tagname!
|
22
|
+
end
|
23
|
+
|
24
|
+
def id
|
25
|
+
tag? && id!
|
26
|
+
end
|
27
|
+
|
28
|
+
def tagname!
|
29
|
+
extra[0]
|
30
|
+
end
|
31
|
+
|
32
|
+
def id!
|
33
|
+
extra[1]
|
34
|
+
end
|
35
|
+
|
36
|
+
def cls!
|
37
|
+
extra[2]
|
38
|
+
end
|
39
|
+
|
40
|
+
def clean(translate_table: {}, allow: ALLOW)
|
41
|
+
if [:tag, :singletag, :closetag].include? type
|
42
|
+
name = extra[0].downcase
|
43
|
+
slash1 = tag? ? "" : "/"
|
44
|
+
slash2 = (tag? && closetag?) ? "/" : ""
|
45
|
+
allow.each do |attr_name|
|
46
|
+
attr_value = attr(attr_name)
|
47
|
+
slash2 = " #{attr_name}=\"#{attr_value}\"#{slash2}" if attr_value
|
48
|
+
end
|
49
|
+
name = translate_table.fetch(name, name)
|
50
|
+
content = "<#{slash1}#{name}#{slash2}>"
|
51
|
+
|
52
|
+
id = allow.include?("id") ? extra[1] : nil
|
53
|
+
cls = allow.include?("class") ? extra[2] : []
|
54
|
+
return Node.new(type, content, [name, id, cls])
|
55
|
+
end
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def attr_regex(name)
|
60
|
+
/#{name}=(("[^"]*")|('[^']*')|[^\s>]+)/mi
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.build(closetag, singletag, tag, notag, comment, other)
|
64
|
+
if tag
|
65
|
+
tagname = RE_TAG.match(tag)[1]
|
66
|
+
id = RE_TAG_ID.match(tag)
|
67
|
+
id &&= id[1].gsub(/\A('|")*|('|")*\Z/, "")
|
68
|
+
cls = RE_TAG_CLS.match(tag)
|
69
|
+
cls = cls && cls[1].gsub(/\A('|")*|('|")*\Z/, "").split || []
|
70
|
+
if SINGLES.include? tagname
|
71
|
+
Node.new(:singletag, tag, [tagname, id, cls])
|
72
|
+
else
|
73
|
+
Node.new(:tag, tag, [tagname, id, cls])
|
74
|
+
end
|
75
|
+
elsif singletag
|
76
|
+
tagname = RE_TAG.match(singletag)[1]
|
77
|
+
id = RE_TAG_ID.match(singletag)
|
78
|
+
id &&= id[1].gsub(/\A('|")*|('|")*\Z/, "")
|
79
|
+
cls = RE_TAG_CLS.match(singletag)
|
80
|
+
cls = cls && cls[1].gsub(/\A('|")*|('|")*\Z/, "").split || []
|
81
|
+
Node.new(:singletag, singletag, [tagname, id, cls])
|
82
|
+
elsif closetag
|
83
|
+
closetagname = RE_CLOSETAG.match(closetag)[1]
|
84
|
+
Node.new(:closetag, closetag, [closetagname])
|
85
|
+
elsif notag
|
86
|
+
Node.new(:notag, notag, nil)
|
87
|
+
elsif comment
|
88
|
+
Node.new(:comment, comment, nil)
|
89
|
+
elsif other
|
90
|
+
Node.new(:notag, other, nil)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# def to_s
|
95
|
+
# super.sub("#<struct ", "#<" )
|
96
|
+
# end
|
97
|
+
|
98
|
+
# def inspect
|
99
|
+
# super.sub("#<struct ", "#<" )
|
100
|
+
# end
|
101
|
+
|
102
|
+
# def pretty_print(q)
|
103
|
+
# q.text inspect
|
104
|
+
# end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Foxy
|
2
|
+
module RateLimit
|
3
|
+
private
|
4
|
+
|
5
|
+
attr_reader :rate_limit
|
6
|
+
|
7
|
+
def interval
|
8
|
+
1.0 / rate_limit
|
9
|
+
end
|
10
|
+
|
11
|
+
def wait!
|
12
|
+
return unless rate_limit
|
13
|
+
@last ||= 0
|
14
|
+
delta = interval - (Time.now.to_f - @last.to_f)
|
15
|
+
sleep(delta) if delta > 0
|
16
|
+
@last = Time.now.to_f
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'yaml/store'
|
2
|
+
|
3
|
+
require 'foxy/storages/yaml'
|
4
|
+
|
5
|
+
module Foxy
|
6
|
+
class Repository
|
7
|
+
attr_reader :pk, :collection, :storage, :model, :class_key
|
8
|
+
|
9
|
+
def initialize(collection: nil, pk: :id, storage: Foxy::Storages::Yaml, model: true, class_key: :class)
|
10
|
+
@collection = collection || class_name.downcase
|
11
|
+
@pk = pk
|
12
|
+
@storage = storage
|
13
|
+
@model = model == true ? find_model : model
|
14
|
+
@class_key = class_key
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_or_create(entity)
|
18
|
+
deserialize find_or_create! serialize entity
|
19
|
+
end
|
20
|
+
|
21
|
+
def find(id)
|
22
|
+
deserialize find! id
|
23
|
+
end
|
24
|
+
|
25
|
+
def all
|
26
|
+
deserialize_collection store.all
|
27
|
+
end
|
28
|
+
|
29
|
+
def where(query={})
|
30
|
+
deserialize_collection where!(query)
|
31
|
+
end
|
32
|
+
|
33
|
+
def create(entity)
|
34
|
+
deserialize create! serialize entity
|
35
|
+
end
|
36
|
+
|
37
|
+
def update(entity, attrs)
|
38
|
+
deserialize update! (serialize entity), attrs
|
39
|
+
end
|
40
|
+
|
41
|
+
def save(entity)
|
42
|
+
deserialize save! serialize entity
|
43
|
+
end
|
44
|
+
|
45
|
+
def destroy(entity)
|
46
|
+
destroy! serialize entity
|
47
|
+
end
|
48
|
+
|
49
|
+
def destroy_all
|
50
|
+
store.destroy_all
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def store
|
56
|
+
@store ||= storage.new(collection)
|
57
|
+
end
|
58
|
+
|
59
|
+
def serialize entity
|
60
|
+
return entity.merge(class_key => model.name) if model && entity.is_a?(Hash)
|
61
|
+
raise "#{entity} is not a #{model.class}" if model && !entity.is_a?(model)
|
62
|
+
entity.serializable_hash.deep_symbolize_keys.merge(class_key => entity.class.name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def deserialize hash
|
66
|
+
type = hash.delete(class_key)
|
67
|
+
(model || Object.const_get(type)).new(hash)
|
68
|
+
end
|
69
|
+
|
70
|
+
def deserialize_collection(collection)
|
71
|
+
collection.map{ |e| deserialize(e) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def where!(query)
|
75
|
+
store.where(query)
|
76
|
+
end
|
77
|
+
|
78
|
+
def create!(attrs)
|
79
|
+
store.add attrs
|
80
|
+
end
|
81
|
+
|
82
|
+
def find!(id)
|
83
|
+
where!(pk => id).first
|
84
|
+
end
|
85
|
+
|
86
|
+
def find_or_create!(attrs)
|
87
|
+
find!(attrs[pk]) || create!(attrs)
|
88
|
+
end
|
89
|
+
|
90
|
+
def save!(attrs)
|
91
|
+
destroy! attrs
|
92
|
+
create! attrs
|
93
|
+
end
|
94
|
+
|
95
|
+
def update!(attrs, more)
|
96
|
+
store.update(attrs) { |item| item.merge!(more) }.first
|
97
|
+
end
|
98
|
+
|
99
|
+
def destroy!(attrs)
|
100
|
+
store.delete(pk => attrs[pk])
|
101
|
+
end
|
102
|
+
|
103
|
+
def find_model
|
104
|
+
Object.const_get(class_name)
|
105
|
+
end
|
106
|
+
|
107
|
+
def class_name
|
108
|
+
self.class.name.split('::').last
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'yaml/store'
|
2
|
+
|
3
|
+
module Foxy
|
4
|
+
module Storages
|
5
|
+
class Yaml
|
6
|
+
attr_accessor :collection
|
7
|
+
|
8
|
+
def initialize(collection)
|
9
|
+
@collection = collection
|
10
|
+
end
|
11
|
+
|
12
|
+
def where(attrs)
|
13
|
+
all.select(&query(attrs))
|
14
|
+
end
|
15
|
+
|
16
|
+
def add(attrs)
|
17
|
+
attrs.tap { store.transaction { all! << attrs } }
|
18
|
+
end
|
19
|
+
|
20
|
+
def all
|
21
|
+
store.transaction { all! }
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(attrs)
|
25
|
+
store.transaction {
|
26
|
+
before = all!.count
|
27
|
+
all!.delete_if(&query(attrs))
|
28
|
+
before - all!.count
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def update(attrs, &block)
|
33
|
+
store.transaction { all!.select(&query(attrs)).each(&block) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete_all
|
37
|
+
File.delete store.path if File.exist? store.path
|
38
|
+
@store = nil
|
39
|
+
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def store_folder
|
46
|
+
"store"
|
47
|
+
end
|
48
|
+
|
49
|
+
def store
|
50
|
+
@store ||= store!
|
51
|
+
end
|
52
|
+
|
53
|
+
def query(attrs)
|
54
|
+
keys, values = attrs.keys, attrs.values
|
55
|
+
|
56
|
+
Proc.new { |item| item.values_at(*keys) == values }
|
57
|
+
end
|
58
|
+
|
59
|
+
def all!
|
60
|
+
store[:items] ||= []
|
61
|
+
end
|
62
|
+
|
63
|
+
def store!
|
64
|
+
FileUtils.makedir_p(store_folder.split("/"))
|
65
|
+
YAML::Store.new(path).tap { |s| s.transaction { s[:items] ||= [] } }
|
66
|
+
end
|
67
|
+
|
68
|
+
def path
|
69
|
+
"#{store_folder}/#{collection}#{env}.store.yaml"
|
70
|
+
end
|
71
|
+
|
72
|
+
def env
|
73
|
+
Foxy.env && "-#{Foxy.env}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/foxy/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: foxy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Manuel Albarrán
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-05-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.9.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.9.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: faraday_middleware
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.10.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.10.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: patron
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.6.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.6.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.11'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.11'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.0'
|
97
|
+
description: A set of foxy tools for make easy retrieve information for another servers.
|
98
|
+
email:
|
99
|
+
- weap88@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rspec"
|
106
|
+
- ".rubocop.yml"
|
107
|
+
- ".travis.yml"
|
108
|
+
- Gemfile
|
109
|
+
- LICENSE.txt
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- bin/console
|
113
|
+
- bin/setup
|
114
|
+
- foxy.gemspec
|
115
|
+
- lib/foxy.rb
|
116
|
+
- lib/foxy/adverb.rb
|
117
|
+
- lib/foxy/client.rb
|
118
|
+
- lib/foxy/collection.rb
|
119
|
+
- lib/foxy/extensions.rb
|
120
|
+
- lib/foxy/file_cache.rb
|
121
|
+
- lib/foxy/html.rb
|
122
|
+
- lib/foxy/html_response.rb
|
123
|
+
- lib/foxy/node.rb
|
124
|
+
- lib/foxy/rate_limit.rb
|
125
|
+
- lib/foxy/repository.rb
|
126
|
+
- lib/foxy/storages/yaml.rb
|
127
|
+
- lib/foxy/version.rb
|
128
|
+
homepage: https://github.com/weapp/foxyrb
|
129
|
+
licenses:
|
130
|
+
- MIT
|
131
|
+
metadata: {}
|
132
|
+
post_install_message:
|
133
|
+
rdoc_options: []
|
134
|
+
require_paths:
|
135
|
+
- lib
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
requirements: []
|
147
|
+
rubyforge_project:
|
148
|
+
rubygems_version: 2.4.5.1
|
149
|
+
signing_key:
|
150
|
+
specification_version: 4
|
151
|
+
summary: Foxy tools for foxy things.
|
152
|
+
test_files: []
|
153
|
+
has_rdoc:
|