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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 153aa069f454992869ca73604cfd58b63abceb83
4
- data.tar.gz: 8753b08c926a28fd9e29bf7b401ccab668340245
3
+ metadata.gz: 0f83d77d20b8bbb093c8a1cc3e32a49e1079ebcb
4
+ data.tar.gz: cae40da73f186e5ce41d4a9f07dd9551bcce4695
5
5
  SHA512:
6
- metadata.gz: a69db4f2c27ca273580b5eceab5535a7591e866bcba43f066cf0d87da3593450ac60dd90abc3384da5e6910f2d9054fe8a6d0a39e8d966710306bbb2ce7c9481
7
- data.tar.gz: 42a615509a65e5b9fb46ef341feaebfa9f53b0ad4573ac2cb9b5e16a416f8feb6aaa3d1d21bcd6e7fa162258446eb91dd0f8029081b30e72e2fbb8604ab0042d
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(url, traversal-path)
24
+ HyperNavigator.surf(href, traversal_pattern)
25
25
  ```
26
26
 
27
- The `traversal-path` argument provided is an array of `rel` names. This array should contain the rel names in order of traversal.
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
- traversal-path = ["apple", "pudding"]
32
+ traversal_pattern = ["apple", "pudding"]
33
33
 
34
- HyperNavigator.surf("https://fruitful-resources.io", traversal-path)
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.surf_to_leaves('https://fruitful-resources.io', path, headers)
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
@@ -3,24 +3,8 @@ require "hyper_navigator/node"
3
3
 
4
4
  module HyperNavigator
5
5
 
6
- def self.surf(home, path=nil, headers={})
7
- root = Node.new('rel', home, nil, 0, path, headers)
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
@@ -25,46 +25,105 @@ module HyperNavigator
25
25
  http.request(request)
26
26
  end
27
27
 
28
- class Node
29
-
30
- attr_reader :ancestor, :descendants, :rel, :href, :depth, :response, :path
28
+ class PatternMatcher
31
29
 
32
- def initialize(rel, href, ancestor=nil, depth=nil, path=nil, headers={})
33
- @ancestor = ancestor
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
- @response = HyperNavigator.get(href, headers)
42
- @descendants = follow_links
35
+ def match(href, exp)
36
+ match_here(exp, Node.new(:root, href, @headers, 0))
43
37
  end
44
38
 
45
- private
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 links
48
- json = JSON.parse(@response.body) rescue nil
49
- return [] unless json
50
- # If we've been given a path then only follow the links in the path
51
- if @path
52
- json["links"].select { |link| link["rel"] == @path.first }
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
- json["links"].reject {|i| ["self","up","next","prev"].any?{|r| r == i["rel"]} }
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 next_step
59
- return @path.drop(1) if @path
60
- nil
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
- def follow_links
64
- links.map do |link|
65
- Node.new(link["rel"], link["href"], self, @depth + 1, next_step, @headers)
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
@@ -1,3 +1,3 @@
1
1
  module HyperNavigator
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  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.1.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: 2017-12-21 00:00:00.000000000 Z
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