snapi 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,16 @@
1
+ 0.0.7
2
+ -- remove support for Snapi.capability_root
3
+ -- Import deep_merge, and deep_merge! methods for Ruby
4
+ core Has class
5
+ -- Improve Snapi module API
6
+ -- Improvements to Sinatra Extension
7
+ -- Sinatra Extension helper with repsonse_wrapper method
8
+ -- to_hash method on function now includes and Array of argument
9
+ -- utilize default_value if set for a :string argument
10
+ -- remove useless regexes from validator
11
+ -- Improve spec tests
12
+ -- Improve Documentation in README.md
13
+
1
14
  0.0.6
2
15
  -- fix bug around @@capabilities overide in Sinatra Extension
3
16
  0.0.5
data/README.md CHANGED
@@ -2,55 +2,203 @@
2
2
 
3
3
  Snapi is a modular API functionality definition tool.
4
4
 
5
- ## Installation
5
+ [![Gem Version](https://badge.fury.io/rb/snapi.png)](http://badge.fury.io/rb/snapi) [![Code Climate](https://codeclimate.com/github/pwnieexpress/snapi.png)](https://codeclimate.com/github/pwnieexpress/snapi)
6
6
 
7
- This hasn't been put on Rubygems yet so you still have to build the gem
8
- manually for now.
7
+ ## Usage
9
8
 
10
- ```sh
11
- git clone git@github.com:pwnieexpress/snapi.git
12
- cd snapi
13
- gem build snapi.gemspec
14
- gem install snapi
15
- ```
16
- This has only been used on `1.9.3` but is should run fine on `1.9+` and `2.x+`
9
+ ### Installation
17
10
 
18
- ## Usage
11
+ Install by simply typing `gem install snapi`. This has only been tested on
12
+ `1.9.3` but is should run fine on `1.9+` and `2.x+`
13
+
14
+ ### Snapi
15
+
16
+ The main access point to the functionality that has been defined is through the
17
+ top level `Snapi` module.
18
+
19
+ #### Functionality Disclosure
19
20
 
20
- Simple Example:
21
+ `Snapi` is intended to provided *functionality disclosure* so that different
22
+ APIs can be defined for different purposes quickly and in a way that self
23
+ documents and organizes the functionality.
24
+
25
+
26
+ ### Capabilities
27
+
28
+ Capabilities are core to Snapis API organizational structure. A capability
29
+ represents a collection of functions and can, for many intents and purposes, be
30
+ thought of as a kind of module.
31
+
32
+ To create a capability start with a Ruby class and include the
33
+ `Snapi::Capability` module.
21
34
 
22
35
  ```ruby
23
36
  require 'snapi'
24
- require 'json'
25
-
26
- class ScannerLibrary
27
- def self.scan(args)
28
- #
29
- # _| _ _|_|_ _ _|_|_ o __ _
30
- # (_|(_) |_| |(/_ |_| | | | |(_|
31
- # __|
37
+
38
+ class SayHello
39
+ include Snapi::Capability
40
+ function :hello_world
41
+
42
+ # Note, even if this doesn't require any external args it still requires an
43
+ # arity of 1.
44
+ def self.hello_world(params)
45
+ "Hello World"
32
46
  end
33
47
  end
48
+ ```
34
49
 
35
- class Scanner
36
- include Snapi::Capability
37
- function :scan do |fn|
38
- fn.argument :target do |arg|
39
- arg.required true
40
- arg.list true
41
- arg.type :string
42
- arg.format :address
43
- end
44
- fn.argument :port do |arg|
45
- arg.type :string
46
- end
47
- fn.return :structured
50
+ #### Working with Snapi Capabilities
51
+
52
+ `Snapi` provides a number of helpful ways to view and access and work with
53
+ capabilities.
54
+
55
+ ##### Check for the presence of a capability:
56
+
57
+ ```ruby
58
+ Snapi.has_capability?(:capability)
59
+ #=> true / false
60
+ ```
61
+
62
+ ##### Access a specific capability by name:
63
+
64
+ ```ruby
65
+ Snapi[:capability]
66
+ #=> the class in question
67
+ ```
68
+ ##### See all capabilities
69
+
70
+ ```ruby
71
+ Snapi.capabilities
72
+ #=> Hash of Capability information
73
+
74
+ Snapi.valid_capabilities
75
+ #=> Array of the names of valid capabilities
76
+ ```
77
+
78
+ ### Functions
79
+
80
+ Functions are declared inside the capability class. At a minimum the functions
81
+ take a name which maps to a class method defined on the capability class.
82
+
83
+ The minimal function as declared above looks like:
84
+
85
+ ```ruby
86
+ function :hello_world
87
+ ```
88
+
89
+ :exclamation: *Note:* class methods which serve functions demand having an
90
+ arity of 1. They should expect a hash containing keyed values which map to the
91
+ arguments defined in the `Snapi` function definition.
92
+
93
+ #### Arguments
94
+
95
+ Functions are handy but they are much more useful when you begin to declare
96
+ arguments for them. Lets define a more interested method for our capability.
97
+
98
+ ```ruby
99
+ function :hello do |fn|
100
+ fn.argument :friend do |arg|
101
+ arg.required true
102
+ arg.type :string
48
103
  end
49
- library ScannerLibrary
50
104
  end
51
105
  ```
52
106
 
53
- ## Sinatra Extension
107
+ Now we can create more dynamic method to serve this function.
108
+
109
+ ```ruby
110
+ def self.hello(params)
111
+ "Hello #{params[:friend]}"
112
+ end
113
+ ```
114
+
115
+ ##### Argument Attributes
116
+
117
+ Arguments can collect a bit of meta information as they are being defined.
118
+
119
+ ###### type
120
+
121
+ Arguments can come in the following types:
122
+
123
+ * `:boolean`
124
+ * `:enum`: *not currently implemented*
125
+ * `:string`
126
+ * `:number`
127
+ * `:timestamp`: *not currently implemented*
128
+
129
+ ###### default_value
130
+
131
+ Provide a default value for non-required `:string` typed arguments.
132
+
133
+ ###### format
134
+
135
+ Provide an expected validation format for `:string` typed arguments.
136
+
137
+ The following formats are currently implemented:
138
+
139
+ * `:address`: a valid hostname, domain name, IPv4 or IPv6 address
140
+ * `:anything`: Any string. This is the default used. (regex: `/.*/`)
141
+ * `:bool`: A boolean value string containing `'true'` or `'false'`.
142
+ * `:command`: Simple command regex containing alphanumeric, characters and basic punctionation
143
+ * `:hostname`: A hostname
144
+ * `:interface`: Simple interface validation like `<string><number>`
145
+ * `:ip`, `ipv6`, `ipv4`: IP addresses
146
+ * `:mac`: MAC Address
147
+ * `:snapi_function_name`: Valid function name for Snapi
148
+ * `:on_off`: A string containing `on` or `off`
149
+ * `:port`: A valid network port (1-65535)
150
+ * `:uri`: Simplistic URI validation
151
+
152
+ ###### list
153
+
154
+ The `argument.list` attribute takes a boolean value and indicates that the
155
+ argument should come as an array of elements, rather than a single one.
156
+
157
+ This is mostly useful as meta-information for the purposes of functionality
158
+ disclosure and does not
159
+
160
+ :exclamation: *Note* list arguments are currently *not* validated even if
161
+ defined as such.
162
+
163
+ ###### required
164
+
165
+ Expecting `true` or `false` the required argument indicates to snapi if the argument MUST be included.
166
+
167
+ ###### values
168
+
169
+ *Pending...*
170
+
171
+ ###### description
172
+
173
+ *Pending...*
174
+
175
+ #### Return Type
176
+
177
+ A return type can be defined for a function.
178
+
179
+ ```ruby
180
+ fn.return :structured
181
+ ```
182
+
183
+ Valid types:
184
+
185
+ * `:structured`: Indicates structured output of some type
186
+ * `:raw`: indicates a hash containing shell command run information like `:stdout` or `:exitstatus`
187
+ * `:none`: indicates an unformated string output
188
+
189
+ ### Library Class
190
+
191
+ The `library` option on a capability can be used to make `Snapi` expect its
192
+ defined functions to be served from another class.
193
+
194
+ ```ruby
195
+ library ExternalRubyClass
196
+ ```
197
+
198
+ :exclamation: *Note:* This class *must* have valid class methods with an arity
199
+ of 1 for each function declared in the capability defition.
200
+
201
+ ### Sinatra Extension
54
202
 
55
203
  A Sinatra application can be extended with this functionality as follows.
56
204
 
@@ -58,7 +206,7 @@ A Sinatra application can be extended with this functionality as follows.
58
206
  class MyAPi < Sinatra::Base
59
207
  register Sinatra::Namespace
60
208
 
61
- namespace Snapi.capability_root do
209
+ namespace "/snapi" do
62
210
  register Snapi::SinatraExtension
63
211
  end
64
212
  end
@@ -67,9 +215,84 @@ end
67
215
  When loaded this application will offer the following routes:
68
216
 
69
217
  ```
70
- # /plugins/
71
- # /plugins/scanner/
72
- # /plugins/scanner/scan/
218
+ # /snapi/
219
+ # /snapi/say_hello/
220
+ # /snapi/say_hello/hello_world
221
+ # /snapi/say_hello/hello
222
+ ...
223
+ ```
224
+
225
+ #### The API
226
+
227
+ If we run the [sample app](https://github.com/pwnieexpress/snapi/blob/develop/readme_sample.rb) we can make the following requests as either GET or
228
+ POST:
229
+
230
+ ##### http://localhost:4567/snapi
231
+
232
+ ```
233
+ {
234
+ "status":200,
235
+ "data":{
236
+ "say_hello":{
237
+ "hello_world":{
238
+ "return_type":null,
239
+ "arguments":[
240
+
241
+ ]
242
+ },
243
+ "hello":{
244
+ "return_type":null,
245
+ "arguments":[
246
+ {
247
+ "name":"friend",
248
+ "required":false,
249
+ "type":"string"
250
+ }
251
+ ]
252
+ }
253
+ }
254
+ },
255
+ "execution_time":2.602e-05
256
+ }
257
+ ```
258
+
259
+ ##### http://localhost:4567/snapi/hello_world
260
+
261
+ ```
262
+ {
263
+ "status":200,
264
+ "data":{
265
+ "hello_world":{
266
+ "return_type":null,
267
+ "arguments":[
268
+
269
+ ]
270
+ },
271
+ "hello":{
272
+ "return_type":null,
273
+ "arguments":[
274
+ {
275
+ "name":"friend",
276
+ "required":true,
277
+ "type":"string"
278
+ }
279
+ ]
280
+ }
281
+ },
282
+ "execution_time":3.4522e-05
283
+ }
284
+ ```
285
+
286
+ ##### http://localhost:4567/snapi/say_hello/hello?friend=Snapi
287
+
288
+ ```
289
+ {
290
+ "status":200,
291
+ "data":{
292
+ "result":"Hello Snapi"
293
+ },
294
+ "execution_time":0.000346298
295
+ }
73
296
  ```
74
297
 
75
298
  ## Project Goals & Name
@@ -88,5 +311,6 @@ The ultimate goal being for an API to be able to define itself dynamically and
88
311
  dislose that functionality to remote systems and even do things like:
89
312
 
90
313
  * Dynamic Form Generation
91
- * API generation (TODO `snapi generate hosts plugin:crud`)
314
+ * CLI Tool / Option Parser
92
315
  * Self Documentation
316
+
@@ -0,0 +1,30 @@
1
+ # The core Hash class
2
+ # This adds the deep_merge method to this core class which is used to join nested hashes
3
+ class Hash
4
+
5
+ # Non destructive version of deep_merge using a dup
6
+ #
7
+ # @param [Hash] the hash to be merged with
8
+ # @returns [Hash] A copy of a new hash merging the hash
9
+ # this was called on and the param hash
10
+ def deep_merge(other_hash)
11
+ dup.deep_merge!(other_hash)
12
+ end
13
+
14
+ # Recusively merges hashes into each other. Any value that is not a Hash will
15
+ # be overridden with the value in the other hash.
16
+ #
17
+ # @param [Hash] the hash to be merged with
18
+ # @returns [Hash] A copy of itself with the new hash merged in
19
+ def deep_merge!(other)
20
+ raise ArgumentError unless other.is_a?(Hash)
21
+
22
+ other.each do |k, v|
23
+ self[k] = (self[k].is_a?(Hash) && self[k].is_a?(Hash)) ? self[k].deep_merge(v) : v
24
+ end
25
+
26
+ self
27
+ end
28
+
29
+ end
30
+
@@ -1,4 +1,6 @@
1
1
  # include all the things!
2
+ require "core_ext/hash"
3
+
2
4
  require "snapi/validator"
3
5
  require "snapi/errors"
4
6
  require "snapi/argument"
@@ -10,7 +12,11 @@ module Snapi
10
12
  @@capabilities = {}
11
13
 
12
14
  def self.capabilities
13
- @@capabilities || {}
15
+ @@capabilities
16
+ end
17
+
18
+ def self.[](key)
19
+ @@capabilities[key]
14
20
  end
15
21
 
16
22
  def self.register_capability(klass)
@@ -21,11 +27,26 @@ module Snapi
21
27
  @@capabilities.keys
22
28
  end
23
29
 
24
- def self.capability_root
25
- "/plugins/?"
30
+ def self.capability_hash
31
+ valid_capabilities.each_with_object({}) do |cap,coll|
32
+ coll[cap] = Snapi[cap].to_hash
33
+ end
34
+ end
35
+
36
+ def self.has_capability?(capability)
37
+ valid_capabilities.include?(capability)
38
+ end
39
+
40
+ def self.supports?(capability,function,params)
41
+ if Snapi.has_capability?(capability) &&
42
+ Snapi[capability].valid_function_call?(function, params)
43
+ true
44
+ else
45
+ false
46
+ end
26
47
  end
27
48
  end
28
49
 
29
50
  # This depends on Snapi module being defined as above
51
+ require "snapi/sinatra_extension_helper"
30
52
  require "snapi/sinatra_extension"
31
-
@@ -50,7 +50,8 @@ module Snapi
50
50
  def valid_attributes
51
51
  [
52
52
  :default_value, :format, :list,
53
- :required, :type, :values, :name, :description
53
+ :required, :type, :values, :name,
54
+ :description
54
55
  ]
55
56
  end
56
57
 
@@ -79,6 +80,7 @@ module Snapi
79
80
  #
80
81
  # @param bool, Boolean value
81
82
  def list(bool)
83
+ #TODO work out kinks in list behavior...
82
84
  raise InvalidBooleanError unless [true,false].include? bool
83
85
  @attributes[:list] = bool
84
86
  end
@@ -132,7 +134,6 @@ module Snapi
132
134
  when :enum
133
135
  raise MissingValuesError unless @attributes[:values]
134
136
  raise InvalidValuesError unless @attributes[:values].class == Array
135
-
136
137
  @attributes[:values].include?(input)
137
138
  when :string
138
139
  format = @attributes[:format] || :anything
@@ -141,7 +142,8 @@ module Snapi
141
142
  [Integer, Fixnum].include?(input.class)
142
143
  when :timestamp
143
144
  # TODO timestamp pending
144
- raise PendingBranchError
145
+ # raise PendingBranchError
146
+ true
145
147
  else
146
148
  false
147
149
  end
@@ -24,7 +24,6 @@ module Snapi
24
24
  LibraryClassMissingFunctionError = Class.new(StandardError)
25
25
  MissingValuesError = Class.new(StandardError)
26
26
 
27
-
28
27
  # TODO remove
29
28
  PendingBranchError = Class.new(StandardError)
30
29
  end
@@ -45,9 +45,9 @@ module Snapi
45
45
  #
46
46
  # @returns Hash representation of Function
47
47
  def to_hash
48
- args_hash = {}
49
- arguments.each { |k,v| args_hash[k] = v.attributes } if arguments
50
- { return_type: return_type }.merge args_hash
48
+ args = []
49
+ arguments.each { |k,v| args << {name: k }.merge(v.attributes) } if arguments
50
+ { return_type: return_type, arguments: args }
51
51
  end
52
52
 
53
53
  # This method accepts a hash (as from a web request) which it
@@ -61,12 +61,23 @@ module Snapi
61
61
  # @returns Boolean
62
62
  def valid_args?(args={})
63
63
  valid = false
64
- arguments.each do |name, argument|
65
- if argument[:required]
66
- return false if args[name] == nil
64
+
65
+ if arguments
66
+ arguments.each do |name, argument|
67
+ if argument[:required]
68
+ return false if args[name] == nil
69
+ end
70
+
71
+ if argument[:default_value] && !args[name]
72
+ args[name] = argument[:default_value]
73
+ end
74
+
75
+ valid = argument.valid_input?(args[name])
67
76
  end
68
- valid = argument.valid_input?(args[name])
77
+ else
78
+ valid = true
69
79
  end
80
+
70
81
  return valid
71
82
  end
72
83
  end
@@ -3,42 +3,53 @@ require 'sinatra/contrib'
3
3
 
4
4
  module Snapi
5
5
  module SinatraExtension
6
+
6
7
  extend Sinatra::Extension
7
8
 
8
- get "/?" do
9
- capabilities = Snapi.capabilities.dup
10
- capabilities.keys.each do |key|
11
- capabilities[key] = capabilities[key].to_hash
9
+ helpers Snapi::SinatraExtensionHelper
10
+
11
+ # Hello darkness, my old friend
12
+ def self.get_or_post(url,&block)
13
+ get(url,&block)
14
+ post(url,&block)
15
+ end
16
+
17
+ get_or_post "/?" do
18
+ response_wrapper do
19
+ Snapi.capability_hash
12
20
  end
13
- JSON.generate(capabilities)
14
21
  end
15
22
 
16
- get "/:capability/?" do
23
+ get_or_post "/:capability/?" do
17
24
  @capability = params.delete("capability").to_sym
18
25
 
19
- unless Snapi.valid_capabilities.include?(@capability)
20
- raise InvalidCapabilityError
26
+ response_wrapper do
27
+ if Snapi.has_capability?(@capability)
28
+ Snapi[@capability].to_hash
29
+ else
30
+ raise InvalidCapabilityError
31
+ end
21
32
  end
22
-
23
- JSON.generate(Snapi.capabilities[@capability].to_hash)
24
33
  end
25
34
 
26
- get "/:capability/:function/?" do
35
+ get_or_post "/:capability/:function/?" do
27
36
  @capability = params.delete("capability").to_sym
28
37
  @function = params.delete("function").to_sym
29
38
 
30
- unless Snapi.valid_capabilities.include?(@capability)
31
- raise InvalidCapabilityError
32
- end
39
+ response_wrapper do
33
40
 
34
- unless Snapi.capabilities[@capability].valid_function_call?(@function,params)
35
- raise InvalidFunctionCallError
36
- end
41
+ unless Snapi.supports?(@capability, @function, params)
42
+ raise InvalidFunctionCallError
43
+ end
44
+
45
+ response = Snapi[@capability].run_function(@function,params)
37
46
 
38
- # TODO add response_wrapper which ensures that the return data from the
39
- # function matches the type declared in the capabilities function defitition
40
- response = Snapi.capabilities[@capability].run_function(@function,params)
41
- response.class == String ? response : JSON.generate(response)
47
+ unless response.class == Hash
48
+ response = { raw_result: response }
49
+ end
50
+
51
+ response
52
+ end
42
53
  end
43
54
  end
44
55
  end
@@ -0,0 +1,39 @@
1
+ require 'digest/sha2'
2
+
3
+ module Snapi
4
+ module SinatraExtensionHelper
5
+ # Helper that handles wrapping all API data requests in a standard format
6
+ # that includes status and error messages (if there were any).
7
+ #
8
+ # @param data [Hash] Singular or multiple data objects the client
9
+ # requested, should include a JSON schema attribute to allow for response
10
+ # validation.
11
+ # @param response_code [Fixnum] HTTP Status code to return
12
+ # @param errors [Array<String>] List of errors that occured while processing
13
+ # the request.
14
+ def response_wrapper(data = {}, response_code = 200, errors = [])
15
+ time_taken = nil
16
+
17
+ if block_given?
18
+ time_start = Time.now
19
+
20
+ begin
21
+ data = data.deep_merge(yield)
22
+ rescue Exception => e
23
+ response_code = 500
24
+ errors << "#{e.class.name}: #{e.backtrace}"
25
+ end
26
+
27
+ time_end = Time.now
28
+ time_taken = (time_end - time_start)
29
+ end
30
+
31
+ response = { status: response_code, data: data }
32
+ response[:errors] = errors unless errors.empty?
33
+ response[:execution_time] = time_taken unless time_taken.nil?
34
+
35
+ # Use halt to prevent all further processing
36
+ halt(response_code, response.to_json)
37
+ end
38
+ end
39
+ end
@@ -47,7 +47,6 @@ module Snapi
47
47
  :anything => [/.*/],
48
48
  :bool => [TRUEFALSE_REGEX],
49
49
  :command => [SIMPLE_COMMAND_REGEX],
50
- :cron => [CRON_REGEX],
51
50
  :gsm_adapter => [ADAPTER_REGEX],
52
51
  :hostname => [HOSTNAME_REGEX],
53
52
  :interface => [INTERFACE_REGEX],
@@ -56,12 +55,9 @@ module Snapi
56
55
  :ipv4 => [IP_V4_REGEX],
57
56
  :mac => [MAC_REGEX],
58
57
  :snapi_function_name => [SNAPI_FUNCTION_NAME],
59
- :string => [STRING_REGEX],
60
58
  :on_off => [ON_OFF_REGEX],
61
59
  :port => [PORT_REGEX],
62
60
  :uri => [URI_REGEX],
63
- :username => [HOSTNAME_REGEX],
64
- :password => [NUM_LETTERS_SP_CHARS]
65
61
  }
66
62
  end
67
63
 
@@ -88,9 +84,6 @@ module Snapi
88
84
  # Note base on lib/shells/gsm.rb
89
85
  ADAPTER_REGEX = /^(unlocked_gsm|verizon_virgin_mobile|tmobile)$/
90
86
  #
91
- # Note set in lib/shells/scheduler.rb
92
- CRON_REGEX = /^(1_minute|5_minutes|15_minutes|60_minutes)$/
93
- #
94
87
  # Source:
95
88
  SIMPLE_COMMAND_REGEX = /^([a-zA-Z0-9\.\s\-\_\/\\\,\=]+)*$/i
96
89
  #
@@ -117,9 +110,6 @@ module Snapi
117
110
  # SIMPLE ON / OFF (CHECKBOXES)
118
111
  ON_OFF_REGEX = /^(on|off)$/i
119
112
  #
120
- # Source:
121
- STRING_REGEX = /^([a-zA-Z0-9\.\-\_]+)*$/i
122
- #
123
113
  # TRUE OR FALSE
124
114
  TRUEFALSE_REGEX = /^(true|false)$/i
125
115
  #
@@ -1,3 +1,3 @@
1
1
  module Snapi
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.7"
3
3
  end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Snapi::Argument do
4
4
  it "is a class" do
5
- subject.class.class.should == Class
5
+ Snapi::Argument.class.should == Class
6
6
  end
7
7
 
8
8
  it "has an attributes hash" do
@@ -46,7 +46,7 @@ describe Snapi::Argument do
46
46
  subject.attributes.keys.sort.should == []
47
47
  end
48
48
 
49
- describe "can validated types such as" do
49
+ describe "can validate types such as" do
50
50
  it ":boolean" do
51
51
  a = Snapi::Argument.new
52
52
  a.type :boolean
@@ -17,7 +17,7 @@ describe Snapi::BasicCapability do
17
17
  fn.return :raw
18
18
  end
19
19
  end
20
- lemon_grab = {:summon_zombies => { :return_type => :raw}}
20
+ lemon_grab = {:summon_zombies=>{:return_type=>:raw, :arguments=>[]}}
21
21
 
22
22
  PrinceLemonGrab.to_hash.should == lemon_grab
23
23
  end
@@ -40,17 +40,19 @@ describe Snapi::BasicCapability do
40
40
 
41
41
  expected_return = {
42
42
  :create_candy_person => {
43
- :return_type =>:structured,
44
- :candy_base => {
43
+ :return_type => :structured,
44
+ :arguments => [{
45
+ :name => :candy_base,
45
46
  :default_value => "sugar",
46
- :format => :anything,
47
- :required => true,
48
- :list => true,
49
- :type => :enum,
50
- }
51
- }
47
+ :format => :anything,
48
+ :list => true,
49
+ :required => true,
50
+ :type => :enum
51
+ }]
52
52
  }
53
- PrincessBubblegum.to_hash.should == expected_return
53
+ }
54
+
55
+ PrincessBubblegum.to_hash.should == expected_return
54
56
  end
55
57
 
56
58
  it "doesn't shared functions between inherited classes" do
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe Snapi do
4
+ it "is a module" do
5
+ Snapi.class.should == Module
6
+ end
7
+
8
+ it "can return a hash of registered capabilities" do
9
+ Snapi.capabilities.class.should == Hash
10
+ Snapi.capabilities[:basic_capability].should == Snapi::BasicCapability
11
+ end
12
+
13
+ it "provides direct hash style access to its capabilities" do
14
+ Snapi[:basic_capability].should == Snapi.capabilities[:basic_capability]
15
+ end
16
+
17
+ it "allows other ruby classes to register as capabilities" do
18
+ TmpClass = Class.new(Snapi::BasicCapability)
19
+
20
+ Snapi.capabilities.length.should == 1
21
+ Snapi.register_capability(TmpClass)
22
+
23
+ Snapi.capabilities.length.should == 2
24
+ Snapi[:tmp_class].should == TmpClass
25
+ end
26
+
27
+ it "provides a list of known capabilities" do
28
+ Snapi.valid_capabilities.class.should == Array
29
+ Snapi.valid_capabilities.should == [:basic_capability, :tmp_class]
30
+ end
31
+
32
+ it "provides a hash containing a full functionality disclosure" do
33
+ hash = Snapi.capability_hash
34
+ hash.class.should == Hash
35
+ hash[:tmp_class].should == TmpClass.to_hash
36
+ hash[:basic_capability].should == Snapi::BasicCapability.to_hash
37
+ end
38
+
39
+ it "can test is a capability is valid" do
40
+ Snapi.has_capability?(:basic_capability).should == true
41
+ Snapi.has_capability?(:sexy_ninja).should == false # :(
42
+ end
43
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: snapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-10-09 00:00:00.000000000 Z
12
+ date: 2014-03-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -34,12 +34,14 @@ extensions: []
34
34
  extra_rdoc_files: []
35
35
  files:
36
36
  - lib/snapi.rb
37
+ - lib/core_ext/hash.rb
37
38
  - lib/snapi/argument.rb
38
39
  - lib/snapi/validator.rb
39
40
  - lib/snapi/function.rb
40
41
  - lib/snapi/version.rb
41
42
  - lib/snapi/sinatra_extension.rb
42
43
  - lib/snapi/capability.rb
44
+ - lib/snapi/sinatra_extension_helper.rb
43
45
  - lib/snapi/errors.rb
44
46
  - README.md
45
47
  - CHANGELOG
@@ -47,6 +49,7 @@ files:
47
49
  - Gemfile.lock
48
50
  - Gemfile
49
51
  - spec/basic_capability_spec.rb
52
+ - spec/snapi_spec.rb
50
53
  - spec/validator_spec.rb
51
54
  - spec/spec_helper.rb
52
55
  - spec/function_spec.rb