xn.rb 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|