xn.rb 0.0.2
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 +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +17 -0
- data/LICENSE.txt +22 -0
- data/README.md +62 -0
- data/Rakefile +3 -0
- data/lib/xn.rb +206 -0
- data/lib/xn/api_requestor.rb +98 -0
- data/lib/xn/version.rb +3 -0
- data/xn.rb.gemspec +23 -0
- metadata +70 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ea2329de7679cd2d4e1d11d8e744d27b874421e1
|
4
|
+
data.tar.gz: 23b61587ef31d5aca7da249871c88e314ff802da
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 28d2d444b53fdd371e7b169af8ab6d64dcd703a31cfc5c8b985da9950950c6694f564a4537fccff9939e6f5e843cdb559a5b775c5f92602addcc76a7a3fd9eb2
|
7
|
+
data.tar.gz: aff9f1d1aa456a3b296ed378252d3a91157c9fbc25b349f9099e55e8c078ca3c1fc80db5fe04ecd777476f6fd62347e2e3bd18bfb2970b27e51a780c7622ae77
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 XN LOGIC CORPORATION
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
xn.rb
|
2
|
+
=====
|
3
|
+
|
4
|
+
Ruby API client for XN Logic based applications.
|
5
|
+
|
6
|
+
Source code available at https://github.com/xnlogic/xn.rb and Pull Requests are welcome!
|
7
|
+
|
8
|
+
Synopsis
|
9
|
+
--------
|
10
|
+
|
11
|
+
Set up and authentication:
|
12
|
+
```
|
13
|
+
require 'rubygems'
|
14
|
+
require 'xn.rb'
|
15
|
+
api_session = Xn::XnApiSession.new 'https://app.lightmesh.com', 'my@email.com'
|
16
|
+
```
|
17
|
+
This will prompt the terminal for your password. If you are not writing an interactive script, set the LMTOKEN environment variable first and the XnApiSession constructor will use that.
|
18
|
+
e.g.
|
19
|
+
|
20
|
+
```
|
21
|
+
~/my_project $ LMTOKEN='client_name XYZ1234' irb
|
22
|
+
2.1.0p0 :001 > require 'rubygems'
|
23
|
+
2.1.0p0 :002 > require 'xn.rb'
|
24
|
+
2.1.0p0 :003 > api_session = Xn::XnApiSession.new 'https://app.lightmesh.com', 'my@email.com'
|
25
|
+
2.1.0p0 :004 > Using token from env LMTOKEN
|
26
|
+
=> #<Xn::XnApiSession:0x007feef5c65070.....>
|
27
|
+
2.1.0p0 :005 >
|
28
|
+
```
|
29
|
+
|
30
|
+
To query the API, there are a few helpers:
|
31
|
+
```
|
32
|
+
# NB: These two queries return subtly different results. The first returns a Model record, with all Parts your user
|
33
|
+
# has access to, whereas the second returns just the Part data. For more on the difference between Models and Parts see:
|
34
|
+
# https://lightmesh.zendesk.com/entries/41374508-Concepts-Types-or-parts-and-Models for
|
35
|
+
|
36
|
+
response_hash = api_session.find_vertex_by_model 'ip', '/filter/name?name=10.0.0.2'
|
37
|
+
response_hash = api_session.find_vertex_by_part 'ip', '/filter/name?name=10.0.0.2'
|
38
|
+
|
39
|
+
# Create, Update, Find-or-Create:
|
40
|
+
new_ip_r_hash = api_session.create_vertex 'ip', name: '10.0.0.3', description: 'New reserved IP for projekt XYZ'
|
41
|
+
response_hash = api_session.update_vertex new_ip_r_hash, description: 'New reserved IP for project XYZ'
|
42
|
+
response_hash = api_session.find_or_create_by_model_and_name('ip', '10.0.0.3', nil, description: 'New reserved IP for project XYZ')
|
43
|
+
|
44
|
+
# Get all related vertices:
|
45
|
+
api_session.related_vertices(new_ip_r_hash)
|
46
|
+
|
47
|
+
# Execute an action:
|
48
|
+
api_session.exec_action( new_ip_r_hash, 'set_subnet_from_ip', {} ) # Some actions take paramters in the form of a hash
|
49
|
+
|
50
|
+
```
|
51
|
+
We are always interested in hearing suggestions for more helpers (open an issue at https://github.com/xnlogic/xn.rb)
|
52
|
+
|
53
|
+
but you can also use the API http wrapper directly for more advanced use-cases:
|
54
|
+
```
|
55
|
+
api_session.api.get "/v1/is/ip/filters/name/properties/name/?name[regex]=^(?!10)" do |response|
|
56
|
+
response.each do |id, ip|
|
57
|
+
puts "IP: #{ip}, xnid: /model/ip/#{id}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
|
data/Rakefile
ADDED
data/lib/xn.rb
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
if RUBY_VERSION == '1.8.7'
|
2
|
+
STDERR.puts <<WARNING
|
3
|
+
WARNING: XnApiSession is developed using Ruby in 1.9.3 mode.
|
4
|
+
We recommend you use ruby-1.9.3 or if using JRuby, start ruby in 1.9 mode,
|
5
|
+
either with the --1.9 flag, or by setting the environment variable
|
6
|
+
JRUBY_OPTS=--1.9
|
7
|
+
WARNING
|
8
|
+
end
|
9
|
+
require 'rubygems'
|
10
|
+
require 'bundler'
|
11
|
+
require 'bundler/setup'
|
12
|
+
|
13
|
+
require 'json'
|
14
|
+
require 'net/http'
|
15
|
+
raise "Missing Net::HTTP::Patch in #{RUBY_VERSION}" unless defined? Net::HTTP::Patch
|
16
|
+
|
17
|
+
require File.join(File.dirname(__FILE__), 'xn', 'api_requestor')
|
18
|
+
|
19
|
+
module Xn
|
20
|
+
class XnApiSession
|
21
|
+
if RUBY_PLATFORM == 'java'
|
22
|
+
require 'java'
|
23
|
+
include_class 'java.lang.System'
|
24
|
+
include_class 'java.io.Console'
|
25
|
+
else
|
26
|
+
require 'highline/import'
|
27
|
+
end
|
28
|
+
|
29
|
+
# Could make these configurable via xn.yml file in the future:
|
30
|
+
attr_reader :url, :login_path, :user, :api_suffix
|
31
|
+
attr_accessor :api
|
32
|
+
|
33
|
+
def initialize(server_url, user_email, api_suffix = "v1")
|
34
|
+
@url = URI(server_url)
|
35
|
+
@login_path = '/sessions.json'
|
36
|
+
@user = user_email
|
37
|
+
@api_suffix = api_suffix
|
38
|
+
@api = ApiRequestor.new url
|
39
|
+
|
40
|
+
if ENV['LMTOKEN']
|
41
|
+
@token = ENV['LMTOKEN']
|
42
|
+
puts "Using token from env LMTOKEN"
|
43
|
+
else
|
44
|
+
@token = token
|
45
|
+
end
|
46
|
+
@api.token = @token
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get a user's password from the command line
|
50
|
+
# don't cache me...
|
51
|
+
def password
|
52
|
+
if RUBY_PLATFORM == 'java'
|
53
|
+
console = System.console
|
54
|
+
pass = console.read_password("Password: ")
|
55
|
+
java.lang.String.new(pass)
|
56
|
+
else
|
57
|
+
pass = ask("Password: ") { |q| q.echo = "*" }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Login to the host and prompt for password.
|
62
|
+
# return the token
|
63
|
+
def login
|
64
|
+
request_hash = { user: { email: user, password: password } }
|
65
|
+
api.post login_path, request_hash do |response|
|
66
|
+
response['token']
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def token
|
71
|
+
@token ||= login
|
72
|
+
end
|
73
|
+
|
74
|
+
# Fetch a single vertex of given model type from the server
|
75
|
+
# return a hash of the vertex or nil
|
76
|
+
def find_vertex_by_model(model, filter_string)
|
77
|
+
debug "find_vertex_by_model(#{model.downcase}, #{filter_string})"
|
78
|
+
raise "model is a required argument" if model.nil?
|
79
|
+
if filter_string and filter_string[/\?/]
|
80
|
+
filter_string = "#{filter_string}&limit=1"
|
81
|
+
else
|
82
|
+
filter_string = "#{filter_string}?limit=1"
|
83
|
+
end
|
84
|
+
request_path = "/#{api_suffix}/model/#{model.downcase}/#{filter_string}"
|
85
|
+
debug "GET #{request_path}"
|
86
|
+
api.get request_path do |response|
|
87
|
+
vertex = response.first
|
88
|
+
debug "found vertex [#{vertex}]"
|
89
|
+
vertex
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Fetch a single vertex of given part type from the server
|
94
|
+
# return a hash of the vertex or nil
|
95
|
+
def find_vertex_by_part(part, filter_string)
|
96
|
+
debug "find_vertex_by_part(#{part}, #{filter_string})"
|
97
|
+
raise "part is a required argument" if part.nil?
|
98
|
+
if filter_string and filter_string[/\?/]
|
99
|
+
filer_string = "#{filter_string}&limit=1"
|
100
|
+
else
|
101
|
+
filter_string = "#{filter_string}?limit=1"
|
102
|
+
end
|
103
|
+
request_path = "/#{api_suffix}/is/#{part.downcase}/#{filter_string}"
|
104
|
+
api.get request_path do |response|
|
105
|
+
vertex = response.first
|
106
|
+
debug "found vertex [#{vertex}]"
|
107
|
+
vertex
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Create a vertex of given model with given properties
|
112
|
+
# return a hash of the vertex or nil
|
113
|
+
def create_vertex(model, props)
|
114
|
+
debug "create_vertex(#{model}, #{props})"
|
115
|
+
raise "model and :name property are required" if model.nil? or props.nil? or props[:name].nil?
|
116
|
+
|
117
|
+
request_path = "/#{api_suffix}/model/#{model.downcase}"
|
118
|
+
api.put request_path, props do |response|
|
119
|
+
if response and response[0] != false
|
120
|
+
return vertex = response[2]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# update the given vertex with the given hash
|
126
|
+
# return a hash of the updates or nil
|
127
|
+
def update_vertex(vertex, update_hash)
|
128
|
+
debug "update_vertex(#{vertex}, #{update_hash})"
|
129
|
+
return nil if vertex.nil? or update_hash.nil?
|
130
|
+
if !vertex.is_a? Hash
|
131
|
+
vertex = find_vertex_by_part :record, vertex
|
132
|
+
end
|
133
|
+
|
134
|
+
request_path = "/#{api_suffix}/model/#{vertex['meta']['model_name']}/#{vertex['id']}"
|
135
|
+
debug "PATCH #{request_path} (#{update_hash.to_json})"
|
136
|
+
api.patch request_path, update_hash do |response|
|
137
|
+
debug "updated vertex [#{response}]"
|
138
|
+
response
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# find or create a vertex by model and name and optionally provide
|
143
|
+
# a filter string to append to the model/xxx/ URL and properties to set
|
144
|
+
# if not found.
|
145
|
+
#
|
146
|
+
# (example filter_string: 'filter/name/?name[value]=myname')
|
147
|
+
def find_or_create_by_model_and_name(model, name, filter_string = nil, props = {})
|
148
|
+
debug "find_or_create_by_model_and_name(#{model}, #{name}, #{filter_string}, #{props})"
|
149
|
+
filter_string = "filter/name/?name[value]=#{name}" unless filter_string
|
150
|
+
props = props.merge name: name
|
151
|
+
obj = find_vertex_by_model(model, filter_string)
|
152
|
+
if obj.nil? or obj.empty?
|
153
|
+
debug "about to create a #{model} vertex with #{props} properties" if obj.nil? or obj.empty?
|
154
|
+
obj = create_vertex(model, props)
|
155
|
+
end
|
156
|
+
obj
|
157
|
+
end
|
158
|
+
|
159
|
+
# return all vertices related to the given one
|
160
|
+
def related_vertices(vertex)
|
161
|
+
debug "related_vertices(#{vertex})"
|
162
|
+
if vertex and vertex.is_a? Hash and vertex['meta']['model_name']
|
163
|
+
model = vertex['meta']['model_name']
|
164
|
+
request_path = "/#{api_suffix}/model/#{model}/#{vertex['id']}/rel"
|
165
|
+
api.get request_path do |parts|
|
166
|
+
debug " parts: #{parts}"
|
167
|
+
related = parts.map do |part|
|
168
|
+
find_vertex_by_model model, "#{vertex['id']}/rel/#{part}"
|
169
|
+
end.compact.reject do |response|
|
170
|
+
response[:status] == 404 # No need to tell us what doesn't exist!
|
171
|
+
end
|
172
|
+
debug "found related: [#{related}]"
|
173
|
+
return related if related.any?
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# execute the named action with any properties as args
|
179
|
+
def exec_action(vertex, action_name, props = {})
|
180
|
+
debug "exec_action(#{vertex}, #{action_name}, #{props})"
|
181
|
+
request_path = "/#{api_suffix}/model/#{vertex['meta']['model_name']}/#{vertex['id']}/action/#{action_name}"
|
182
|
+
debug "POST #{request_path} (#{props.to_json})"
|
183
|
+
api.post request_path, props do |response|
|
184
|
+
debug "executed #{action_name} on vertex [#{response}]"
|
185
|
+
response
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
protected
|
190
|
+
|
191
|
+
def debug(message = "")
|
192
|
+
if ENV['XN_VERBOSITY'] and ENV['XN_VERBOSITY'] == 'debug'
|
193
|
+
message = yield if block_given?
|
194
|
+
STDERR.puts "DEBUG: #{message}"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def verbose(message)
|
199
|
+
if ENV['XN_VERBOSITY']
|
200
|
+
message = yield if block_given?
|
201
|
+
STDERR.puts "#{message}"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# Simple API request wrapper. Instantiate multiple times to hit the
|
2
|
+
# server in parallel.
|
3
|
+
|
4
|
+
module Xn
|
5
|
+
class AuthorizationError < StandardError; end
|
6
|
+
|
7
|
+
class ApiRequestor
|
8
|
+
attr_accessor :uri, :token
|
9
|
+
|
10
|
+
def initialize(server_uri, token = nil)
|
11
|
+
raise "#{self.class} Err: First arg must be an instance of URI" if !server_uri.is_a? URI
|
12
|
+
@uri = server_uri
|
13
|
+
@token = token
|
14
|
+
end
|
15
|
+
|
16
|
+
# Request the resource(s) and a renderable JSON response
|
17
|
+
def get(resource_url, &block)
|
18
|
+
call_http_server Net::HTTP::Get.new( URI.encode(resource_url) ), &block
|
19
|
+
end
|
20
|
+
|
21
|
+
# Calls a method resource and returns a renderable JSON response
|
22
|
+
def post(resource_url, body = nil, &block)
|
23
|
+
http_post = Net::HTTP::Post.new resource_url, {'Content-Type' =>'application/json'}
|
24
|
+
if body
|
25
|
+
body = body.to_json if body.is_a? Enumerable
|
26
|
+
http_post.body = body
|
27
|
+
end
|
28
|
+
call_http_server http_post, &block
|
29
|
+
end
|
30
|
+
|
31
|
+
# Calls a method resource to create a vertex and returns a renderable JSON response
|
32
|
+
def put(resource_url, body = nil, &block)
|
33
|
+
http_put = Net::HTTP::Put.new resource_url, {'Content-Type' =>'application/json'}
|
34
|
+
http_put.body = json_body(body) if body
|
35
|
+
call_http_server http_put, &block
|
36
|
+
end
|
37
|
+
|
38
|
+
# Calls a method resource to update a vertex and returns a renderable JSON response
|
39
|
+
def patch(resource_url, body = nil, &block)
|
40
|
+
http_patch = Net::HTTP::Patch.new resource_url, {'Content-Type' =>'application/json'}
|
41
|
+
http_patch.body = json_body(body) if body
|
42
|
+
call_http_server http_patch, &block
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Ensure the body is json
|
48
|
+
def json_body(body)
|
49
|
+
if body.is_a? Enumerable
|
50
|
+
body.to_json
|
51
|
+
else
|
52
|
+
body
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def call_http_server(request, &block)
|
57
|
+
request['AUTHORIZATION'] = token if token
|
58
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https' ) do |http|
|
59
|
+
response = nil
|
60
|
+
http.read_timeout = 240
|
61
|
+
start_time = Time.now
|
62
|
+
t = Thread.new { response = http.request(request) }
|
63
|
+
i = 0
|
64
|
+
while response.nil? and ( Time.now - start_time < http.read_timeout )
|
65
|
+
i += 1
|
66
|
+
print '.'
|
67
|
+
if i % 20 == 0
|
68
|
+
print "\r"
|
69
|
+
end
|
70
|
+
sleep 0.01
|
71
|
+
end
|
72
|
+
print "\r"
|
73
|
+
t.join
|
74
|
+
|
75
|
+
begin
|
76
|
+
json = JSON.parse(response.body)
|
77
|
+
raise AuthorizationError, "#{json['message']} (#{json['parsed_url']})" if response.code.to_i == 401
|
78
|
+
rescue JSON::ParserError => e
|
79
|
+
# Probably not JSON, return entire body
|
80
|
+
return { status: response.code.to_i, body: response.body }
|
81
|
+
end
|
82
|
+
if block and response.code.to_i < 300
|
83
|
+
block.call json
|
84
|
+
else
|
85
|
+
{ status: response.code.to_i, json: json }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
rescue Exception => e
|
89
|
+
raise e if e.is_a? AuthorizationError
|
90
|
+
raise e if e.is_a? Errno::ECONNREFUSED or e.is_a? Net::HTTPBadRequest
|
91
|
+
puts "ERROR making request from: "
|
92
|
+
puts request
|
93
|
+
puts e.message
|
94
|
+
puts e.backtrace
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
data/lib/xn/version.rb
ADDED
data/xn.rb.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "xn/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "xn.rb"
|
7
|
+
s.version = Xn::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["David Colebatch", "Darrick Wiebe"]
|
10
|
+
s.email = ["dc@xnlogic", "dw@xnlogic"]
|
11
|
+
s.homepage = "http://www.xnlogic.com/"
|
12
|
+
s.summary = %q{XN Logic API client - first used by LightMesh utilities}
|
13
|
+
s.description = %q{Aims to provide a semantic wrapper for the XN Logic graph-db application framework's REST API.}
|
14
|
+
|
15
|
+
s.add_dependency 'highline'
|
16
|
+
|
17
|
+
s.rubyforge_project = "xn.rb"
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
end
|
23
|
+
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: xn.rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Colebatch
|
8
|
+
- Darrick Wiebe
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-02-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: highline
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
description: Aims to provide a semantic wrapper for the XN Logic graph-db application
|
29
|
+
framework's REST API.
|
30
|
+
email:
|
31
|
+
- dc@xnlogic
|
32
|
+
- dw@xnlogic
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- ".gitignore"
|
38
|
+
- Gemfile
|
39
|
+
- Gemfile.lock
|
40
|
+
- LICENSE.txt
|
41
|
+
- README.md
|
42
|
+
- Rakefile
|
43
|
+
- lib/xn.rb
|
44
|
+
- lib/xn/api_requestor.rb
|
45
|
+
- lib/xn/version.rb
|
46
|
+
- xn.rb.gemspec
|
47
|
+
homepage: http://www.xnlogic.com/
|
48
|
+
licenses: []
|
49
|
+
metadata: {}
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project: xn.rb
|
66
|
+
rubygems_version: 2.2.2
|
67
|
+
signing_key:
|
68
|
+
specification_version: 4
|
69
|
+
summary: XN Logic API client - first used by LightMesh utilities
|
70
|
+
test_files: []
|