katana_resource 0.7.3
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 +15 -0
- data/.gitmodules +6 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +36 -0
- data/app/app_delegate.rb +10 -0
- data/app/models/bicycle.rb +4 -0
- data/katana_resource.gemspec +29 -0
- data/lib/katana_resource.rb +9 -0
- data/lib/katana_resource/associations.rb +22 -0
- data/lib/katana_resource/attributes.rb +178 -0
- data/lib/katana_resource/base.rb +61 -0
- data/lib/katana_resource/boolean.rb +15 -0
- data/lib/katana_resource/client.rb +29 -0
- data/lib/katana_resource/configuration.rb +25 -0
- data/lib/katana_resource/core_ext/ns_string.rb +15 -0
- data/lib/katana_resource/create.rb +54 -0
- data/lib/katana_resource/delete.rb +33 -0
- data/lib/katana_resource/error.rb +65 -0
- data/lib/katana_resource/freeform_requests.rb +33 -0
- data/lib/katana_resource/read.rb +94 -0
- data/lib/katana_resource/resource_attribute.rb +29 -0
- data/lib/katana_resource/resource_paths.rb +42 -0
- data/lib/katana_resource/singleton_records.rb +50 -0
- data/lib/katana_resource/unknown_attribute_type.rb +6 -0
- data/lib/katana_resource/url_encoding.rb +33 -0
- data/lib/katana_resource/version.rb +3 -0
- metadata +200 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 13fe737e54c6eec8768b494347bc898ab0a19f8c
|
4
|
+
data.tar.gz: e19e589f3574b569a53c8f75b959dff4bcf7bae4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1f488768b4ee804de25ddd539c30c6ad7ff08a0427ca401806c6dda481153b8c4396f61350e480b66d86f74d00a9f2891f6d7110beabf16b489cddcb58991f52
|
7
|
+
data.tar.gz: ef99d4b43abca67bf9fe9bad3ec7a2e3f969e76f6b2943208c1b68e7f1974fb2904753d8b99316bf382164371c6c0f5ab891efaede37afe3fcbc6d6c7899640b
|
data/.gitignore
ADDED
data/.gitmodules
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
[submodule "vendor/RKSwipeBetweenViewControllers"]
|
2
|
+
path = vendor/RKSwipeBetweenViewControllers
|
3
|
+
url = git@github.com:cwRichardKim/RKSwipeBetweenViewControllers.git
|
4
|
+
[submodule "vendor/AKETooltip"]
|
5
|
+
path = vendor/AKETooltip
|
6
|
+
url = git@github.com:akeara/AKETooltip.git
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Bodacious
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# KatanaResource
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/katana_resource`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'katana_resource'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install katana_resource
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake false` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/katana_resource.
|
36
|
+
|
37
|
+
|
38
|
+
## License
|
39
|
+
|
40
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
41
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
$:.unshift("/Library/RubyMotion/lib")
|
3
|
+
$:.unshift("./lib")
|
4
|
+
require 'motion/project/template/ios'
|
5
|
+
|
6
|
+
begin
|
7
|
+
Bundler.setup
|
8
|
+
Bundler.require
|
9
|
+
rescue LoadError
|
10
|
+
puts "Please install bundler!"
|
11
|
+
end
|
12
|
+
|
13
|
+
Motion::Require.require_relative_enabled = true
|
14
|
+
require "afmotion"
|
15
|
+
require "webstub"
|
16
|
+
|
17
|
+
|
18
|
+
require "katana_resource"
|
19
|
+
|
20
|
+
Motion::Require.all(Dir.glob('app/**/*.rb'))
|
21
|
+
|
22
|
+
# Better test output
|
23
|
+
ENV["output"] ||= "test_unit"
|
24
|
+
|
25
|
+
Motion::Project::App.setup do |app|
|
26
|
+
# Use `rake config' to see complete project settings.
|
27
|
+
app.name = "Katana Resource"
|
28
|
+
|
29
|
+
app.identifier = "com.katanacode.katanaresource"
|
30
|
+
|
31
|
+
app.deployment_target = "8.0"
|
32
|
+
|
33
|
+
app.files += Dir.glob(File.join(File.dirname(__FILE__), "lib/katana_resource/**/*.rb"))
|
34
|
+
|
35
|
+
|
36
|
+
end
|
data/app/app_delegate.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'katana_resource/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "katana_resource"
|
8
|
+
spec.version = KatanaResource::VERSION
|
9
|
+
spec.authors = ["Bodacious"]
|
10
|
+
spec.email = ["gavin@gavinmorrice.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A work in progress, please ignore for now}
|
13
|
+
spec.description = %q{A work in progress, please ignore for now :)}
|
14
|
+
spec.homepage = "http://katanacode.com"
|
15
|
+
spec.license = "MIT"
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").
|
17
|
+
reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
spec.add_runtime_dependency "motion-require", "~> 0.2"
|
21
|
+
spec.add_runtime_dependency "afmotion", "~> 2.6"
|
22
|
+
spec.add_runtime_dependency "motion-support", "~> 1.1.0"
|
23
|
+
spec.add_runtime_dependency "motion-cocoapods", ">= 1.7.8"
|
24
|
+
spec.add_runtime_dependency "bubble-wrap", "~> 1.9"
|
25
|
+
spec.add_development_dependency "webstub", "~> 1.1"
|
26
|
+
spec.add_development_dependency "motion-redgreen", "~> 1.0"
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
28
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
29
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require "motion-require"
|
2
|
+
require "motion-support"
|
3
|
+
require "afmotion"
|
4
|
+
require "bubble-wrap/core"
|
5
|
+
|
6
|
+
Motion::Require.require_relative_enabled = true
|
7
|
+
|
8
|
+
Motion::Require.all(Dir.glob(File.expand_path('../core_ext/**/*.rb', __FILE__)))
|
9
|
+
Motion::Require.all(Dir.glob(File.expand_path('../katana_resource/**/*.rb', __FILE__)))
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module KatanaResource
|
2
|
+
|
3
|
+
module Associations
|
4
|
+
|
5
|
+
extend MotionSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
def has_one(assoc_name, options = {})
|
10
|
+
options[:class_name] ||= assoc_name.to_s.classify
|
11
|
+
attribute(assoc_name, options[:class_name].to_s.constantize)
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_many(assoc_name, options = {})
|
15
|
+
options[:class_name] ||= assoc_name.to_s.singularize.classify
|
16
|
+
attribute(assoc_name, options[:class_name].to_s.constantize, collection: true) end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require_relative "resource_attribute"
|
2
|
+
require_relative "unknown_attribute_type"
|
3
|
+
module KatanaResource
|
4
|
+
|
5
|
+
|
6
|
+
module Attributes
|
7
|
+
|
8
|
+
extend MotionSupport::Concern
|
9
|
+
|
10
|
+
def encodeWithCoder(coder)
|
11
|
+
self.class.resource_attributes.each do |att|
|
12
|
+
value = self.send(att.name)
|
13
|
+
coder.send(:"encode#{coder_method_for_type(att.type)}:forKey", value, att.name)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initWithCoder(coder)
|
18
|
+
self.class.resource_attributes.each do |att|
|
19
|
+
value = coder.send(:"decode#{coder_method_for_type(att.type)}ForKey", att.name)
|
20
|
+
self.send(:"#{att.name}=", value)
|
21
|
+
end
|
22
|
+
return self
|
23
|
+
end
|
24
|
+
|
25
|
+
# This record's attributes. Keys are always Strings.
|
26
|
+
#
|
27
|
+
# Example:
|
28
|
+
#
|
29
|
+
# User.new(name: "Bodacious", id: 7).attributes
|
30
|
+
# # => {"name" => "Bodacious", "id" => 7}
|
31
|
+
#
|
32
|
+
# Returns a Hash
|
33
|
+
def attributes
|
34
|
+
return_val = Hash[self.class.resource_attributes.map do |attribute|
|
35
|
+
[attribute.name.to_s, send(attribute.name)]
|
36
|
+
end]
|
37
|
+
end
|
38
|
+
|
39
|
+
def non_association_attributes()
|
40
|
+
return_val = Hash[self.class.resource_attributes.map do |attribute|
|
41
|
+
[attribute.name.to_s, send(attribute.name)] unless attribute.options[:collection]
|
42
|
+
end]
|
43
|
+
end
|
44
|
+
|
45
|
+
def primary_key
|
46
|
+
@primary_key ||= self.class.primary_key
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
|
53
|
+
def coder_method_for_type(type)
|
54
|
+
case type
|
55
|
+
when Integer then :Int
|
56
|
+
when Float then :Float
|
57
|
+
when Boolean then :Bool
|
58
|
+
else
|
59
|
+
:Object
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
module ClassMethods
|
65
|
+
|
66
|
+
# When a class is inherited, pass on the inherited attributes to subclass.
|
67
|
+
# If the class is inheriting from Base, don't pass on attributes.
|
68
|
+
#
|
69
|
+
#
|
70
|
+
# klass - The class inheriting from self
|
71
|
+
def inherited(klass)
|
72
|
+
return if self == KatanaResource::Base
|
73
|
+
klass.instance_variable_set("@resource_attributes", resource_attributes)
|
74
|
+
end
|
75
|
+
|
76
|
+
def resource_attributes
|
77
|
+
@resource_attributes ||= []
|
78
|
+
end
|
79
|
+
|
80
|
+
def primary_key(value = nil)
|
81
|
+
if value
|
82
|
+
@primary_key = value
|
83
|
+
else
|
84
|
+
return @primary_key || :id
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def attribute(*args)
|
89
|
+
options = args.extract_options!
|
90
|
+
type = args.last.is_a?(Class) ? args.pop : String
|
91
|
+
args.each do |name|
|
92
|
+
self.resource_attributes << ResourceAttribute.new(name, type, options)
|
93
|
+
|
94
|
+
define_method :"#{name}?" do
|
95
|
+
instance_variable_get(:"@#{name}").present?
|
96
|
+
end
|
97
|
+
|
98
|
+
define_method :"#{name}=" do |value|
|
99
|
+
instance_variable_set(:"@#{name}",
|
100
|
+
self.class.cast_value_as_attribute_type(value, name))
|
101
|
+
end unless options[:read_only]
|
102
|
+
|
103
|
+
define_method :"#{name}" do
|
104
|
+
default_val = options[:collection] == true ? [] : nil
|
105
|
+
ivar_val = instance_variable_get(:"@#{name}")
|
106
|
+
ivar_val.nil? ? default_val : ivar_val
|
107
|
+
end unless options[:write_only]
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def cast_value_as_attribute_type(value, name)
|
113
|
+
case
|
114
|
+
when type_name_for_attribute(name) == "Time"
|
115
|
+
time = Time.iso8601_with_timezone(value)
|
116
|
+
time.utc if time
|
117
|
+
|
118
|
+
when type_name_for_attribute(name) == "Date" then value.to_date
|
119
|
+
when type_name_for_attribute(name) == "String" then unescape_string(value.to_s)
|
120
|
+
when type_name_for_attribute(name) == "Symbol" then value.to_sym
|
121
|
+
when type_name_for_attribute(name) == "Integer" then value.to_i
|
122
|
+
when type_name_for_attribute(name) == "Float" then value.to_f
|
123
|
+
when type_name_for_attribute(name) == "Boolean" then Boolean.new(value).bool_val
|
124
|
+
when defined?(type_for_attribute(name))
|
125
|
+
|
126
|
+
# This will be true if we're decoding the record from disc rather than loading
|
127
|
+
# from server
|
128
|
+
# TODO: Refacator this...
|
129
|
+
if value.is_a?(KatanaResource::Base)
|
130
|
+
return value
|
131
|
+
end
|
132
|
+
return_val = [value].flatten.map { |atts| type_for_attribute(name).new(atts) }
|
133
|
+
if attribute_is_collection?(name)
|
134
|
+
return_val
|
135
|
+
else
|
136
|
+
return_val.first
|
137
|
+
end
|
138
|
+
else
|
139
|
+
raise UnknownAttributeType, "Don't know how to cast #{type} for attribute"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def attribute_named(name)
|
144
|
+
resource_attributes.detect { |att| att.name.to_s == name.to_s }
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
|
151
|
+
def type_for_attribute(name)
|
152
|
+
attribute_named(name).type
|
153
|
+
end
|
154
|
+
|
155
|
+
def type_name_for_attribute(name)
|
156
|
+
attribute_named(name).type.name
|
157
|
+
end
|
158
|
+
|
159
|
+
def attribute_is_collection?(name)
|
160
|
+
attribute_named(name).collection?
|
161
|
+
end
|
162
|
+
|
163
|
+
UNESCAPES = {
|
164
|
+
'a' => "\x07", 'b' => "\x08", 't' => "\x09",
|
165
|
+
'n' => "\x0a", 'v' => "\x0b", 'f' => "\x0c",
|
166
|
+
'r' => "\x0d", 'e' => "\x1b", "\\\\" => "\x5c",
|
167
|
+
"\"" => "\x22", "'" => "\x27"
|
168
|
+
}
|
169
|
+
|
170
|
+
def unescape_string(string)
|
171
|
+
string
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative "associations"
|
2
|
+
require_relative "attributes"
|
3
|
+
require_relative "client"
|
4
|
+
require_relative "create"
|
5
|
+
require_relative "delete"
|
6
|
+
require_relative "freeform_requests"
|
7
|
+
require_relative "read"
|
8
|
+
require_relative "resource_paths"
|
9
|
+
require_relative "singleton_records"
|
10
|
+
require_relative "url_encoding"
|
11
|
+
require_relative "configuration"
|
12
|
+
|
13
|
+
module KatanaResource
|
14
|
+
|
15
|
+
|
16
|
+
class Base
|
17
|
+
|
18
|
+
include KatanaResource::Associations
|
19
|
+
include KatanaResource::Attributes
|
20
|
+
include KatanaResource::Client
|
21
|
+
include KatanaResource::Configuration
|
22
|
+
include KatanaResource::Create
|
23
|
+
include KatanaResource::Delete
|
24
|
+
include KatanaResource::Read
|
25
|
+
include KatanaResource::FreeformRequests
|
26
|
+
include KatanaResource::ResourcePaths
|
27
|
+
include KatanaResource::SingletonRecords
|
28
|
+
include KatanaResource::UrlEncoding
|
29
|
+
|
30
|
+
def initialize(attributes = {})
|
31
|
+
attributes = attributes.with_indifferent_access
|
32
|
+
self.class.resource_attributes.each do |resource_att|
|
33
|
+
next unless attributes[resource_att.name]
|
34
|
+
send(:"#{resource_att.name}=", attributes[resource_att.name])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# TODO: Find a better way to represent a persisted record
|
39
|
+
def persisted?
|
40
|
+
id.present?
|
41
|
+
end
|
42
|
+
|
43
|
+
def ==(record)
|
44
|
+
return false unless record.respond_to?(:id)
|
45
|
+
self.id == record.id
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
class << self
|
53
|
+
|
54
|
+
def fetched_data=(data)
|
55
|
+
@fetched_data = data
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module KatanaResource
|
2
|
+
class ::Boolean
|
3
|
+
|
4
|
+
def initialize(value)
|
5
|
+
@value = true if value.to_s =~ /true|1/
|
6
|
+
@value = false if value.to_s =~ /false|0/
|
7
|
+
raise ArgumentError, "#{value} is not a valid boolean" if @value.nil? && !value.nil?
|
8
|
+
end
|
9
|
+
|
10
|
+
def bool_val
|
11
|
+
@value
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module KatanaResource
|
2
|
+
|
3
|
+
module Client
|
4
|
+
|
5
|
+
extend MotionSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
# DSL Mapping to properties of AFHTTPSessionManager
|
10
|
+
def client
|
11
|
+
AFMotion::Client.shared || load_client
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def load_client
|
16
|
+
AFMotion::Client.build_shared(root_url) do
|
17
|
+
header "Accept", "application/json"
|
18
|
+
KatanaResource::Base.additional_headers.each do |name, value|
|
19
|
+
header name, value
|
20
|
+
end
|
21
|
+
response_serializer :json
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module KatanaResource
|
2
|
+
|
3
|
+
module Configuration
|
4
|
+
|
5
|
+
extend MotionSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
|
10
|
+
def additional_headers
|
11
|
+
@additional_headers ||= {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def root_url=(value)
|
15
|
+
@@root_url = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def root_url
|
19
|
+
@@root_url || raise("Base::root_url not defined")
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module KatanaResource
|
2
|
+
|
3
|
+
module Create
|
4
|
+
|
5
|
+
extend MotionSupport::Concern
|
6
|
+
|
7
|
+
def save(additional_params = {}, &block)
|
8
|
+
attributes = self.attributes
|
9
|
+
record = self
|
10
|
+
self.class.class_eval { save_record(record, attributes, additional_params, &block) }
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
|
16
|
+
def create(attributes, &block)
|
17
|
+
new(attributes).save(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
|
24
|
+
def save_record(record, attributes = {}, additional_params = {}, &block)
|
25
|
+
UIApplication.sharedApplication.setNetworkActivityIndicatorVisible(true)
|
26
|
+
attributes = attributes.reject { |k,v| v.nil? }
|
27
|
+
nested_attributes = { record.class.name.underscore => attributes }
|
28
|
+
verb = save_verb_for_record(record)
|
29
|
+
path = save_path_for_record(record)
|
30
|
+
params = nested_attributes.merge(additional_params)
|
31
|
+
call_block = Proc.new do |result, error|
|
32
|
+
UIApplication.sharedApplication.setNetworkActivityIndicatorVisible(false)
|
33
|
+
prepare_return_object_for_result(result, block)
|
34
|
+
end
|
35
|
+
client.send(verb, path, params, &call_block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def save_path_for_record(record)
|
39
|
+
if record.persisted?
|
40
|
+
prepare_url_and_params(singular_resource_path, id: record.id)
|
41
|
+
else
|
42
|
+
prepare_url_and_params(plural_resource_path)
|
43
|
+
end.first
|
44
|
+
end
|
45
|
+
|
46
|
+
def save_verb_for_record(record)
|
47
|
+
record.persisted? ? :patch : :post
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module KatanaResource
|
2
|
+
|
3
|
+
module Delete
|
4
|
+
|
5
|
+
extend MotionSupport::Concern
|
6
|
+
|
7
|
+
def delete(additional_params = {}, &block)
|
8
|
+
return false unless persisted?
|
9
|
+
attributes = self.attributes
|
10
|
+
record = self
|
11
|
+
self.class.class_eval do
|
12
|
+
delete_record(record, attributes, additional_params, &block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
|
18
|
+
def delete_record(record, attributes = {}, params = {}, &block)
|
19
|
+
UIApplication.sharedApplication.setNetworkActivityIndicatorVisible(true)
|
20
|
+
verb = :delete
|
21
|
+
path = prepare_url_and_params(singular_resource_path, id: record.id).first
|
22
|
+
call_block = Proc.new do |result, error|
|
23
|
+
UIApplication.sharedApplication.setNetworkActivityIndicatorVisible(false)
|
24
|
+
prepare_return_object_for_result(result, block)
|
25
|
+
end
|
26
|
+
client.send(:delete, path, params, &call_block)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module KatanaResource
|
2
|
+
|
3
|
+
class Error
|
4
|
+
|
5
|
+
attr_reader :ns_error
|
6
|
+
|
7
|
+
attr_reader :description
|
8
|
+
|
9
|
+
attr_reader :localized_description
|
10
|
+
|
11
|
+
|
12
|
+
def initialize(ns_error)
|
13
|
+
@ns_error = ns_error
|
14
|
+
@description = ns_error.description unless nil?
|
15
|
+
@localized_description = ns_error.localizedDescription unless nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
def code
|
19
|
+
val = description[/(?<=\()\d{3}(?=\))/].to_i
|
20
|
+
return val if val > 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def unauthorized?
|
24
|
+
code == 401
|
25
|
+
end
|
26
|
+
|
27
|
+
def bad_request?
|
28
|
+
code == 400
|
29
|
+
end
|
30
|
+
|
31
|
+
def not_found?
|
32
|
+
code == 404
|
33
|
+
end
|
34
|
+
|
35
|
+
def offline?
|
36
|
+
description.index('The Internet connection appears to be offline.')
|
37
|
+
end
|
38
|
+
|
39
|
+
def nil?
|
40
|
+
ns_error.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
def data
|
44
|
+
json = user_info["com.alamofire.serialization.response.error.data"] || "{}"
|
45
|
+
BW::JSON.parse(json)
|
46
|
+
end
|
47
|
+
|
48
|
+
def user_info
|
49
|
+
ns_error.userInfo
|
50
|
+
end
|
51
|
+
|
52
|
+
def message
|
53
|
+
if !nil? && data["errors"]
|
54
|
+
case data["errors"]
|
55
|
+
when String then data["errors"]
|
56
|
+
when Array then data["errors"].join(", ")
|
57
|
+
else
|
58
|
+
data["errors"].to_s
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module KatanaResource
|
2
|
+
module FreeformRequests
|
3
|
+
|
4
|
+
extend MotionSupport::Concern
|
5
|
+
|
6
|
+
|
7
|
+
def patch(action_name, params = {}, &block)
|
8
|
+
self.class.patch(self, action_name, params, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
alias_method :put, :patch
|
13
|
+
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
def patch(record, action_name, params = {}, &block)
|
18
|
+
resource_action_path = File.join(singular_resource_path, action_name)
|
19
|
+
params_with_primary_key = params.merge(primary_key => record.send(primary_key))
|
20
|
+
url_and_params = prepare_url_and_params(resource_action_path,
|
21
|
+
params_with_primary_key)
|
22
|
+
UIApplication.sharedApplication.setNetworkActivityIndicatorVisible(true)
|
23
|
+
client.put(*url_and_params) do |result, error|
|
24
|
+
UIApplication.sharedApplication.setNetworkActivityIndicatorVisible(false)
|
25
|
+
prepare_return_object_for_result(result, block)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require_relative "error"
|
2
|
+
module KatanaResource
|
3
|
+
|
4
|
+
module Read
|
5
|
+
|
6
|
+
extend MotionSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# TODO: Support finding records where id is not provided
|
11
|
+
def find(key, params = {}, &block)
|
12
|
+
UIApplication.sharedApplication.setNetworkActivityIndicatorVisible(true)
|
13
|
+
client.get(*resource_path_for_key(key, params)) do |result, error|
|
14
|
+
UIApplication.sharedApplication.setNetworkActivityIndicatorVisible(false)
|
15
|
+
prepare_return_object_for_result(result, block)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def all
|
20
|
+
@all ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def all=(value)
|
24
|
+
@all = value
|
25
|
+
end
|
26
|
+
|
27
|
+
def any?
|
28
|
+
all.any?
|
29
|
+
end
|
30
|
+
|
31
|
+
def none?
|
32
|
+
all.none?
|
33
|
+
end
|
34
|
+
|
35
|
+
alias :empty? :none?
|
36
|
+
|
37
|
+
def disabled_method(*)
|
38
|
+
puts "Cannot call :#{__callee__} from #{self}. Is disabled"
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
|
45
|
+
def require_parent_resource!
|
46
|
+
singleton_class.send(:alias_method, :find, :disabled_method)
|
47
|
+
singleton_class.send(:alias_method, :all, :disabled_method)
|
48
|
+
end
|
49
|
+
|
50
|
+
def prepare_return_object_for_result(result, block)
|
51
|
+
puts "No block given for find" and return unless block
|
52
|
+
if result.success?
|
53
|
+
return_val = prepare_success_return_object_for_result(result, block)
|
54
|
+
else
|
55
|
+
return_val = prepare_failure_return_object_for_result(result, block)
|
56
|
+
end
|
57
|
+
block.call(return_val, Error.new(result.error))
|
58
|
+
end
|
59
|
+
|
60
|
+
def prepare_success_return_object_for_result(result, block)
|
61
|
+
if result.object.is_a?(Hash)
|
62
|
+
new(result.object)
|
63
|
+
else
|
64
|
+
result.object.map { |atts| new(atts) }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def prepare_failure_return_object_for_result(result, block)
|
69
|
+
log_result(result) && nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def resource_path_for_key(key, params)
|
73
|
+
case key
|
74
|
+
when Integer, String
|
75
|
+
complete_params = params.merge(primary_key => key)
|
76
|
+
prepare_url_and_params(singular_resource_path, complete_params)
|
77
|
+
when :all
|
78
|
+
prepare_url_and_params(plural_resource_path, params)
|
79
|
+
else
|
80
|
+
raise "FREAKOUT!"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def log_result(result)
|
85
|
+
if RUBYMOTION_ENV == "development"
|
86
|
+
log("#{result.object}.\n#{result.error.description}")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module KatanaResource
|
2
|
+
|
3
|
+
class ResourceAttribute
|
4
|
+
|
5
|
+
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
attr_reader :type
|
9
|
+
|
10
|
+
attr_reader :options
|
11
|
+
|
12
|
+
|
13
|
+
def initialize(name, type, options = {})
|
14
|
+
@name = name
|
15
|
+
@type = type
|
16
|
+
@options = options.with_indifferent_access
|
17
|
+
end
|
18
|
+
|
19
|
+
def collection?
|
20
|
+
options[:collection] == true
|
21
|
+
end
|
22
|
+
|
23
|
+
def singular?
|
24
|
+
!collection?
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module KatanaResource
|
2
|
+
|
3
|
+
module ResourcePaths
|
4
|
+
|
5
|
+
extend MotionSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
def plural_resource_path(value = nil)
|
10
|
+
if value
|
11
|
+
@plural_resource_path = value
|
12
|
+
else
|
13
|
+
@plural_resource_path || default_plural_resource_path
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def singular_resource_path(value = nil)
|
19
|
+
if value
|
20
|
+
@singular_resource_path = value
|
21
|
+
else
|
22
|
+
@singular_resource_path || default_singular_resource_path
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
|
30
|
+
def default_plural_resource_path
|
31
|
+
self.name.underscore.pluralize
|
32
|
+
end
|
33
|
+
|
34
|
+
def default_singular_resource_path
|
35
|
+
self.name.underscore.pluralize + "/:id"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module KatanaResource
|
2
|
+
|
3
|
+
module SingletonRecords
|
4
|
+
|
5
|
+
extend MotionSupport::Concern
|
6
|
+
|
7
|
+
module IncludedClassMethods
|
8
|
+
|
9
|
+
def current=(record)
|
10
|
+
@current = record
|
11
|
+
end
|
12
|
+
|
13
|
+
def current
|
14
|
+
@current
|
15
|
+
end
|
16
|
+
|
17
|
+
def current?
|
18
|
+
!!@current
|
19
|
+
end
|
20
|
+
|
21
|
+
def current_path
|
22
|
+
(App.documents_path + "/#{name}.current").freeze
|
23
|
+
end
|
24
|
+
|
25
|
+
def save_current(value = nil)
|
26
|
+
self.current = value if value
|
27
|
+
NSKeyedArchiver.archiveRootObject(current, toFile: current_path)
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear_current
|
31
|
+
self.current = nil
|
32
|
+
save_current
|
33
|
+
end
|
34
|
+
|
35
|
+
def load_current
|
36
|
+
self.current = NSKeyedUnarchiver.unarchiveObjectWithFile(current_path)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
module ClassMethods
|
42
|
+
|
43
|
+
def singleton_record!
|
44
|
+
extend IncludedClassMethods
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module KatanaResource
|
2
|
+
|
3
|
+
module UrlEncoding
|
4
|
+
|
5
|
+
extend MotionSupport::Concern
|
6
|
+
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# Prepares a url string by replacing named parameters with param values and
|
11
|
+
# removing these from the params.
|
12
|
+
#
|
13
|
+
# Examples
|
14
|
+
#
|
15
|
+
# prepare_url_and_params("users/:id/:foo", {id: 1, foo: 'foo', bar: 'bar'})
|
16
|
+
# # => ["users/1/foo", {bar: "bar"}]
|
17
|
+
#
|
18
|
+
# Returns an Array with url String and params Hash
|
19
|
+
def prepare_url_and_params(url_string, params = {})
|
20
|
+
params, key_names = params.with_indifferent_access, []
|
21
|
+
new_string = url_string.gsub(/\:([^\/\:]+)/) { |match|
|
22
|
+
key_names << match.slice(1..-1)
|
23
|
+
"%{#{$1}}"
|
24
|
+
} % params
|
25
|
+
return "#{new_string}.json", params.delete_if { |key,v| key_names.include?(key) }
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
metadata
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: katana_resource
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.7.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bodacious
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: motion-require
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: afmotion
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.6'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: motion-support
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.1.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.1.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: motion-cocoapods
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.7.8
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.7.8
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bubble-wrap
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.9'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.9'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: webstub
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.1'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.1'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: motion-redgreen
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: bundler
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.10'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1.10'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rake
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '10.0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '10.0'
|
139
|
+
description: A work in progress, please ignore for now :)
|
140
|
+
email:
|
141
|
+
- gavin@gavinmorrice.com
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- ".gitignore"
|
147
|
+
- ".gitmodules"
|
148
|
+
- ".travis.yml"
|
149
|
+
- Gemfile
|
150
|
+
- LICENSE.txt
|
151
|
+
- README.md
|
152
|
+
- Rakefile
|
153
|
+
- app/app_delegate.rb
|
154
|
+
- app/models/bicycle.rb
|
155
|
+
- katana_resource-0.1.0.gem
|
156
|
+
- katana_resource.gemspec
|
157
|
+
- lib/katana_resource.rb
|
158
|
+
- lib/katana_resource/associations.rb
|
159
|
+
- lib/katana_resource/attributes.rb
|
160
|
+
- lib/katana_resource/base.rb
|
161
|
+
- lib/katana_resource/boolean.rb
|
162
|
+
- lib/katana_resource/client.rb
|
163
|
+
- lib/katana_resource/configuration.rb
|
164
|
+
- lib/katana_resource/core_ext/ns_string.rb
|
165
|
+
- lib/katana_resource/create.rb
|
166
|
+
- lib/katana_resource/delete.rb
|
167
|
+
- lib/katana_resource/error.rb
|
168
|
+
- lib/katana_resource/freeform_requests.rb
|
169
|
+
- lib/katana_resource/read.rb
|
170
|
+
- lib/katana_resource/resource_attribute.rb
|
171
|
+
- lib/katana_resource/resource_paths.rb
|
172
|
+
- lib/katana_resource/singleton_records.rb
|
173
|
+
- lib/katana_resource/unknown_attribute_type.rb
|
174
|
+
- lib/katana_resource/url_encoding.rb
|
175
|
+
- lib/katana_resource/version.rb
|
176
|
+
homepage: http://katanacode.com
|
177
|
+
licenses:
|
178
|
+
- MIT
|
179
|
+
metadata: {}
|
180
|
+
post_install_message:
|
181
|
+
rdoc_options: []
|
182
|
+
require_paths:
|
183
|
+
- lib
|
184
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
185
|
+
requirements:
|
186
|
+
- - ">="
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: '0'
|
189
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
|
+
requirements:
|
191
|
+
- - ">="
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: '0'
|
194
|
+
requirements: []
|
195
|
+
rubyforge_project:
|
196
|
+
rubygems_version: 2.5.1
|
197
|
+
signing_key:
|
198
|
+
specification_version: 4
|
199
|
+
summary: A work in progress, please ignore for now
|
200
|
+
test_files: []
|