hyper_navigator 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -6
- data/examples/surf.rb +50 -0
- data/lib/hyper_navigator.rb +2 -18
- data/lib/hyper_navigator/node.rb +86 -27
- data/lib/hyper_navigator/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f83d77d20b8bbb093c8a1cc3e32a49e1079ebcb
|
4
|
+
data.tar.gz: cae40da73f186e5ce41d4a9f07dd9551bcce4695
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 42fb7fc920df883e1965f6c316086812a2f0daafbcaab385091d163cbc801bcd8bfdebcd115a80f4dd6f5dbabbd01d3b87b17dff598ea07324e6933868a7c62f
|
7
|
+
data.tar.gz: c44fe57cf3cf77c4c769e6222d0d6c5b6d666cc90512ab9e1c163584aa549c3202abaf44b8e2bae38177802c9ae508fe0beb3dd5567ecc3d13cb077e8126c24a
|
data/README.md
CHANGED
@@ -21,17 +21,17 @@ It expects each resource to return a structure in the given format:
|
|
21
21
|
The main entry point for this gem is
|
22
22
|
|
23
23
|
```ruby
|
24
|
-
HyperNavigator.surf(
|
24
|
+
HyperNavigator.surf(href, traversal_pattern)
|
25
25
|
```
|
26
26
|
|
27
|
-
The `
|
27
|
+
The `traversal_pattern` argument provided is an array of `rel` names. This array should contain the rel names in order of traversal.
|
28
28
|
|
29
29
|
An example path, that will look for an `apple` rel in the resource, fetch from it's corresponding href, then look for a `pudding` rel in that resource and fetch its corresponding href:
|
30
30
|
|
31
31
|
```ruby
|
32
|
-
|
32
|
+
traversal_pattern = ["apple", "pudding"]
|
33
33
|
|
34
|
-
HyperNavigator.surf("https://fruitful-resources.io",
|
34
|
+
HyperNavigator.surf("https://fruitful-resources.io", traversal_pattern)
|
35
35
|
```
|
36
36
|
|
37
37
|
The return value of `#surf` will be a `HyperNavigator::Node`.
|
@@ -61,7 +61,6 @@ Or install it yourself as:
|
|
61
61
|
## Usage
|
62
62
|
|
63
63
|
`#surf` will return all nodes encountered during a browse.
|
64
|
-
`#surf_to_leaves` will return just the leaf nodes during a browse.
|
65
64
|
|
66
65
|
Example usage:
|
67
66
|
|
@@ -71,9 +70,17 @@ Example usage:
|
|
71
70
|
path = ["apple", "pudding"]
|
72
71
|
headers = { "Authorization": "Bearer #{$token}" }
|
73
72
|
|
74
|
-
result = HyperNavigator.
|
73
|
+
result = HyperNavigator.surf("https://fruitful-resources.io", traversal_pattern, headers)
|
75
74
|
```
|
76
75
|
|
76
|
+
### Pattern Matching
|
77
|
+
|
78
|
+
The traversal pattern can contain the special matching symbols `:any` and `:star`.
|
79
|
+
They are analogous to the regular expression metacharacters `.` and `*`.
|
80
|
+
|
81
|
+
`:any` will match any rel.
|
82
|
+
`:star` matches the preceding element zero or more times.
|
83
|
+
|
77
84
|
## Development
|
78
85
|
|
79
86
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/examples/surf.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "optparse"
|
5
|
+
require "ostruct"
|
6
|
+
require "hyper_navigator"
|
7
|
+
require "pp"
|
8
|
+
|
9
|
+
options = OpenStruct.new
|
10
|
+
options.verbose = false
|
11
|
+
|
12
|
+
OptionParser.new do |opts|
|
13
|
+
opts.banner ="Usage: #{ARGV[0]} [options]"
|
14
|
+
opts.on("-uURL", "--root-url=URL", "required root URL") do |url|
|
15
|
+
options.url = url
|
16
|
+
end
|
17
|
+
opts.on("-pPATTERN", "--pattern=PATTERN", "required traversal pattern") do |pattern|
|
18
|
+
options.pattern = eval(pattern)
|
19
|
+
end
|
20
|
+
opts.on("-a[AUTH_URL]", "--auth-url=[AUTH_URL]") do |url|
|
21
|
+
options.auth_url = url
|
22
|
+
end
|
23
|
+
opts.on("-v", "--verbose") do |v|
|
24
|
+
options.verbose = true
|
25
|
+
end
|
26
|
+
end.parse!
|
27
|
+
|
28
|
+
CLIENT_ID=ENV['CLIENT_ID']
|
29
|
+
CLIENT_SECRET=ENV['CLIENT_SECRET']
|
30
|
+
|
31
|
+
if options.auth_url
|
32
|
+
headers = {"Content-type" => "application/x-www-form-urlencoded"}
|
33
|
+
body = URI.encode_www_form(
|
34
|
+
"client_id" => CLIENT_ID,
|
35
|
+
"client_secret" => CLIENT_SECRET,
|
36
|
+
"grant_type" => "client_credentials",
|
37
|
+
"response_type" => "code")
|
38
|
+
response = HyperNavigator.post(options.auth_url, body, headers)
|
39
|
+
json = JSON.parse(response.body)
|
40
|
+
$token = json['id_token']
|
41
|
+
options.headers = { "Authorization" => "Bearer #{$token}" }
|
42
|
+
else
|
43
|
+
options.headers = {}
|
44
|
+
end
|
45
|
+
|
46
|
+
result = HyperNavigator.surf(options.url, options.pattern, options.headers, { :verbose => options.verbose })
|
47
|
+
|
48
|
+
result.map do |n|
|
49
|
+
pp JSON.parse(n.response.body) rescue nil
|
50
|
+
end
|
data/lib/hyper_navigator.rb
CHANGED
@@ -3,24 +3,8 @@ require "hyper_navigator/node"
|
|
3
3
|
|
4
4
|
module HyperNavigator
|
5
5
|
|
6
|
-
def self.surf(
|
7
|
-
|
8
|
-
nodes = lambda do |node|
|
9
|
-
node.descendants + node.descendants.flat_map {|d| nodes.call(d) }
|
10
|
-
end
|
11
|
-
nodes.call(root)
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.surf_to_leaves(home, path=nil, headers={})
|
15
|
-
root = Node.new('rel', home, nil, 0, path, headers)
|
16
|
-
nodes = lambda do |node|
|
17
|
-
if node.descendants.empty?
|
18
|
-
node
|
19
|
-
else
|
20
|
-
node.descendants.flat_map {|d| nodes.call(d) }
|
21
|
-
end
|
22
|
-
end
|
23
|
-
nodes.call(root)
|
6
|
+
def self.surf(root_url, exp, headers={}, options={})
|
7
|
+
PatternMatcher.new(headers, options).match(root_url, exp).flatten_branch
|
24
8
|
end
|
25
9
|
|
26
10
|
end
|
data/lib/hyper_navigator/node.rb
CHANGED
@@ -25,46 +25,105 @@ module HyperNavigator
|
|
25
25
|
http.request(request)
|
26
26
|
end
|
27
27
|
|
28
|
-
class
|
29
|
-
|
30
|
-
attr_reader :ancestor, :descendants, :rel, :href, :depth, :response, :path
|
28
|
+
class PatternMatcher
|
31
29
|
|
32
|
-
def initialize(
|
33
|
-
@
|
34
|
-
@descendants = []
|
35
|
-
@rel = rel
|
36
|
-
@href = href
|
37
|
-
@depth = depth
|
38
|
-
@path = path
|
30
|
+
def initialize(headers,opts={})
|
31
|
+
@opts = opts
|
39
32
|
@headers = headers
|
33
|
+
end
|
40
34
|
|
41
|
-
|
42
|
-
@
|
35
|
+
def match(href, exp)
|
36
|
+
match_here(exp, Node.new(:root, href, @headers, 0))
|
43
37
|
end
|
44
38
|
|
45
|
-
|
39
|
+
def match_here(exp, node)
|
40
|
+
if exp == nil
|
41
|
+
return node
|
42
|
+
elsif exp[1] == :star
|
43
|
+
match_star(exp[0], exp.drop(2), node)
|
44
|
+
return node
|
45
|
+
elsif exp[0] == :any
|
46
|
+
match_here_descendants(exp, node)
|
47
|
+
return node
|
48
|
+
elsif exp[0] == node.rel
|
49
|
+
match_here_descendants(exp, node)
|
50
|
+
return node
|
51
|
+
end
|
52
|
+
return NullNode.new
|
53
|
+
end
|
46
54
|
|
47
|
-
def
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
55
|
+
def match_here_descendants(exp, node)
|
56
|
+
links = node.links.map {|link| make_node(link, node.depth + 1)}
|
57
|
+
descendants = links.map {|n| match_here(exp.drop(1), n) }
|
58
|
+
node.descendants = descendants.select {|d| d.class == Node }
|
59
|
+
end
|
60
|
+
|
61
|
+
def match_star(exp_star, exp, node)
|
62
|
+
# in case of zero matches, exp can match here
|
63
|
+
node_here = match_here(exp, node)
|
64
|
+
|
65
|
+
if node_here.is_a? NullNode
|
66
|
+
match_star_descendants(exp_star, exp, node)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def match_star_descendants(exp_star, exp, node)
|
71
|
+
if exp_star == :any
|
72
|
+
links = node.links
|
53
73
|
else
|
54
|
-
|
74
|
+
links = node.links.select { |link| link["rel"] == exp_star }
|
55
75
|
end
|
76
|
+
|
77
|
+
node.descendants = links.map { |link| make_node(link, node.depth + 1) }
|
78
|
+
node.descendants.map {|desc| match_star(exp_star, exp, desc) }
|
56
79
|
end
|
57
80
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
81
|
+
def make_node(link, depth=nil)
|
82
|
+
padding = ' ' * depth
|
83
|
+
puts "#{padding}#{link}" if @opts[:verbose]
|
84
|
+
Node.new(link["rel"], link["href"], @headers, depth)
|
61
85
|
end
|
62
86
|
|
63
|
-
|
64
|
-
|
65
|
-
|
87
|
+
end
|
88
|
+
|
89
|
+
class Node
|
90
|
+
|
91
|
+
IGNORE_REFS = ["self", "up", "next", "prev"]
|
92
|
+
|
93
|
+
attr_reader :rel, :href, :headers, :response
|
94
|
+
attr_accessor :descendants, :depth
|
95
|
+
|
96
|
+
def initialize(rel, href, headers={}, depth=nil)
|
97
|
+
@rel = rel
|
98
|
+
@href = href
|
99
|
+
@headers = headers
|
100
|
+
@descendants = []
|
101
|
+
@depth = depth
|
102
|
+
if href
|
103
|
+
@response = HyperNavigator.get(href, headers)
|
104
|
+
raise RuntimeError, @response unless @response.code =~ /^2..$/
|
66
105
|
end
|
67
106
|
end
|
68
107
|
|
108
|
+
def links
|
109
|
+
@cached_links ||= begin
|
110
|
+
json = JSON.parse(@response.body) rescue nil
|
111
|
+
return [] unless json
|
112
|
+
json["links"].reject do |i|
|
113
|
+
IGNORE_REFS.any? { |r| r == i["rel"] }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def flatten_branch
|
119
|
+
descendants + descendants.flat_map { | d| d.flatten_branch }
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
class NullNode < Node
|
125
|
+
def initialize()
|
126
|
+
super(nil, nil)
|
127
|
+
end
|
69
128
|
end
|
70
|
-
end
|
129
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hyper_navigator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carl Douglas
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -70,6 +70,7 @@ files:
|
|
70
70
|
- Rakefile
|
71
71
|
- bin/console
|
72
72
|
- bin/setup
|
73
|
+
- examples/surf.rb
|
73
74
|
- hyper_navigator.gemspec
|
74
75
|
- lib/hyper_navigator.rb
|
75
76
|
- lib/hyper_navigator/node.rb
|