foxy 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 +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:
|