snapi 0.0.6 → 0.0.7

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.
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