hyper_navigator 0.1.0 → 0.2.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 +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
|