springcm-sdk 0.3.2 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '08e2b1331f3dcb52b9ae6046ba374275586f58f287e7b47ab21d5d3e2b8733d5'
4
- data.tar.gz: 7a5c41aed3ca626531c71527c1a30cbd2bd445b2f7eb001dadae940df843e1c3
3
+ metadata.gz: 4ad5c7b8f5ee94f60381f7a7c1584dade0d0e3a060f7a2914063e0d5c044db61
4
+ data.tar.gz: b314ab30a1191edf5df3ff00d629cb45184d01e10e12819b425c8a65c87b78a4
5
5
  SHA512:
6
- metadata.gz: 8057fdf959e404fa084073027b9efed06b1ae04c6cb02ce6db7b15b225c961cd0e426a84ddc894fe946fcba4e58ea77781ce24ea25c2b08cb801a284bc32369b
7
- data.tar.gz: c5781be7af9eee9a5493e75af6ecf5d0e1b7edc3df7efddd6379c9b3c1f2e576c2c62662486abe21cdd72e3ed5fc689456610f1a8ecac4cd66a74500f5cd2f21
6
+ metadata.gz: 05db834bd55f338e04f3d8b6275d87aa41727823300f339771934fe190eda689ff1b6e4fc120f9d9b7e00d6a7b4b8cc2961189af1ebde8bf1675d51d91658fde
7
+ data.tar.gz: 2d35b5c934bb4fe530e1f7e7d0ffd5f0424e1f1b8f63da0ae17c757d7d57003acc6c2e69398b42f00606b06489aa8425d8fd07501727381dc7ad346879c3f155
data/CHANGELOG.md CHANGED
@@ -4,6 +4,27 @@ All notable changes to springcm-sdk will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.3.4] - 2019-12-11
8
+ ### Added
9
+ * Folder#delete
10
+ * Document#delete
11
+ * Client#document (by path or UID)
12
+ * Folder#move (by destination folder path or UID)
13
+ * Document#move (by destination folder path or UID)
14
+ * Account#attribute_groups
15
+ * Mixins::Attributes#get_attribute
16
+
17
+ ### Changed
18
+ * Tweaked PageBuilder to support items that don't have a parent folder.
19
+ Instead uses a base HREF to build next/prev/first/last HREFs. Similarly
20
+ modified ResourceBuilder to reference a parent object instead of a parent
21
+ folder. Outside of folders/documents, this parent object will simply be
22
+ the Account.
23
+
24
+ ## [0.3.3] - 2019-11-12
25
+ ### Changed
26
+ * Fix issue with getting root folder by path
27
+
7
28
  ## [0.3.2]
8
29
  ### Changed
9
30
  * Fix issue with JSON library not being required
@@ -48,7 +69,7 @@ All notable changes to springcm-sdk will be documented in this file.
48
69
  ### Added
49
70
  * Initial release to reserve gem name
50
71
 
51
- [Unreleased]: https://github.com/paulholden2/springcm-sdk/compare/0.3.2...HEAD
72
+ [Unreleased]: https://github.com/paulholden2/springcm-sdk/compare/0.3.4...HEAD
52
73
  [0.1.0]: https://github.com/paulholden2/springcm-sdk/releases/tag/0.1.0
53
74
  [0.1.1]: https://github.com/paulholden2/springcm-sdk/releases/tag/0.1.1
54
75
  [0.1.2]: https://github.com/paulholden2/springcm-sdk/releases/tag/0.1.2
@@ -56,3 +77,5 @@ All notable changes to springcm-sdk will be documented in this file.
56
77
  [0.3.0]: https://github.com/paulholden2/springcm-sdk/releases/tag/0.3.0
57
78
  [0.3.1]: https://github.com/paulholden2/springcm-sdk/releases/tag/0.3.1
58
79
  [0.3.2]: https://github.com/paulholden2/springcm-sdk/releases/tag/0.3.2
80
+ [0.3.3]: https://github.com/paulholden2/springcm-sdk/releases/tag/0.3.3
81
+ [0.3.4]: https://github.com/paulholden2/springcm-sdk/releases/tag/0.3.4
@@ -1,6 +1,50 @@
1
1
  require "springcm-sdk/object"
2
+ require "springcm-sdk/attribute_group"
2
3
 
3
4
  module Springcm
4
5
  class Account < Object
6
+ def initialize(data, client)
7
+ super(data, client)
8
+ @all_attribute_groups = nil
9
+ end
10
+
11
+ # Retrieve all attribute groups for this account. Calls #attribute_groups
12
+ # and concatenates results to a cache that is returned from future calls
13
+ # to #all_attribute_groups.
14
+ def all_attribute_groups
15
+ if @all_attribute_groups.nil?
16
+ load_all_attribute_groups
17
+ end
18
+ @all_attribute_groups
19
+ end
20
+
21
+ # Retrieve a page of attribute groups in this account. In most cases,
22
+ # you can call #all_attribute_groups instead, as attribute group
23
+ # configurations do not frequently change.
24
+ def attribute_groups(offset: 0, limit: 20)
25
+ conn = @client.authorized_connection(url: @client.object_api_url)
26
+ res = conn.get do |req|
27
+ req.url "accounts/current/attributegroups"
28
+ req.params["offset"] = offset
29
+ req.params["limit"] = limit
30
+ end
31
+ if res.success?
32
+ data = JSON.parse(res.body)
33
+ ResourceList.new(data, self, AttributeGroup, @client)
34
+ else
35
+ nil
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def load_all_attribute_groups
42
+ @all_attribute_groups = []
43
+ list = attribute_groups(offset: 0, limit: 20)
44
+ while !list.nil?
45
+ @all_attribute_groups.concat(list.items)
46
+ list = list.next
47
+ end
48
+ end
5
49
  end
6
50
  end
@@ -0,0 +1,17 @@
1
+ require "springcm-sdk/resource"
2
+
3
+ module Springcm
4
+ class Attribute < Resource
5
+ def required?
6
+ required
7
+ end
8
+
9
+ def read_only?
10
+ read_only
11
+ end
12
+
13
+ def repeating_attribute?
14
+ repeating_attribute
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,56 @@
1
+ require "springcm-sdk/resource"
2
+ require "springcm-sdk/attribute"
3
+
4
+ module Springcm
5
+ # A configured attribute group in SpringCM.
6
+ class AttributeGroup < Resource
7
+ # Retrieve an attribute set by name.
8
+ def set(name)
9
+ res = @data["Attributes"].select { |attr|
10
+ attr["Attributes"].is_a?(Array) && attr["Name"] == name
11
+ }
12
+ return nil if !res.any?
13
+ res.first["Attributes"].map { |attr|
14
+ Attribute.new(attr, @client)
15
+ }
16
+ end
17
+
18
+ # Retrieve an attribute field by name.
19
+ def field(name)
20
+ res = attributes.select { |attr|
21
+ attr["Name"] == name
22
+ }
23
+ return nil if !res.any?
24
+ # TODO: Assert only one result
25
+ Attribute.new(res.first, @client)
26
+ end
27
+
28
+ def set_for_field(name)
29
+ if sets.map { |set| set["Name"] }.include?(name)
30
+ return nil
31
+ end
32
+ sets.each { |set|
33
+ if set["Attributes"].map { |attr| attr["Name"] }.include?(name)
34
+ return set
35
+ end
36
+ }
37
+ nil
38
+ end
39
+
40
+ def attributes
41
+ @data["Attributes"].map { |attr|
42
+ if attr["Attributes"].is_a?(Array)
43
+ attr["Attributes"]
44
+ else
45
+ [attr]
46
+ end
47
+ }.flatten
48
+ end
49
+
50
+ def sets
51
+ @data["Attributes"].select { |attr|
52
+ attr["Attributes"].is_a?(Array)
53
+ }
54
+ end
55
+ end
56
+ end
@@ -95,6 +95,9 @@ module Springcm
95
95
  if (path.nil? && uid.nil?) || (!path.nil? && !uid.nil?)
96
96
  raise ArgumentError.new("Specify exactly one of: path, uid")
97
97
  end
98
+ if path == "/"
99
+ return root_folder
100
+ end
98
101
  conn = authorized_connection(url: object_api_url)
99
102
  res = conn.get do |req|
100
103
  if !path.nil?
@@ -103,6 +106,9 @@ module Springcm
103
106
  elsif !uid.nil?
104
107
  req.url "folders/#{uid}"
105
108
  end
109
+ Folder.resource_params.each { |key, value|
110
+ req.params[key] = value
111
+ }
106
112
  end
107
113
  if res.success?
108
114
  data = JSON.parse(res.body)
@@ -112,6 +118,30 @@ module Springcm
112
118
  end
113
119
  end
114
120
 
121
+ def document(path: nil, uid: nil)
122
+ if (path.nil? && uid.nil?) || (!path.nil? && !uid.nil?)
123
+ raise ArgumentError.new("Specify exactly one of: path, uid")
124
+ end
125
+ conn = authorized_connection(url: object_api_url)
126
+ res = conn.get do |req|
127
+ if !path.nil?
128
+ req.url "documents"
129
+ req.params["path"] = path
130
+ elsif !uid.nil?
131
+ req.url "documents/#{uid}"
132
+ end
133
+ Document.resource_params.each { |key, value|
134
+ req.params[key] = value
135
+ }
136
+ end
137
+ if res.success?
138
+ data = JSON.parse(res.body)
139
+ return Document.new(data, self)
140
+ else
141
+ nil
142
+ end
143
+ end
144
+
115
145
  # Check if client is successfully authenticated
116
146
  # @return [Boolean] Whether a valid, unexpired access token is held.
117
147
  def authenticated?
@@ -1,8 +1,17 @@
1
1
  require "springcm-sdk/resource"
2
2
  require "springcm-sdk/mixins/access_level"
3
+ require "springcm-sdk/mixins/attributes"
3
4
 
4
5
  module Springcm
5
6
  class Document < Resource
6
- include Springcm::AccessLevel
7
+ include Springcm::Mixins::AccessLevel
8
+ include Springcm::Mixins::ParentFolder
9
+ include Springcm::Mixins::Attributes
10
+
11
+ def self.resource_params
12
+ {
13
+ "expand" => "attributegroups"
14
+ }
15
+ end
7
16
  end
8
17
  end
@@ -7,15 +7,22 @@ require "springcm-sdk/helpers"
7
7
 
8
8
  module Springcm
9
9
  class Folder < Resource
10
- include Springcm::AccessLevel
11
- include Springcm::ParentFolder
12
- include Springcm::Documents
10
+ include Springcm::Mixins::AccessLevel
11
+ include Springcm::Mixins::ParentFolder
12
+ include Springcm::Mixins::Documents
13
13
 
14
+ def self.resource_params
15
+ {
16
+ "expand" => "attributegroups"
17
+ }
18
+ end
19
+
20
+ # Retrieve a page of folders contained in this folder.
14
21
  def folders(offset: 0, limit: 20)
15
22
  Helpers.validate_offset_limit!(offset, limit)
16
23
  conn = @client.authorized_connection(url: @client.object_api_url)
17
24
  res = conn.get do |req|
18
- req.url "folders/#{uid}/folders"
25
+ req.url "#{resource_uri}/folders"
19
26
  req.params["offset"] = offset
20
27
  req.params["limit"] = limit
21
28
  end
@@ -27,12 +34,16 @@ module Springcm
27
34
  end
28
35
  end
29
36
 
37
+ # Retrieve the containing folder.
30
38
  def parent_folder
31
39
  uri = URI(parent_folder_href)
32
40
  url = "#{uri.scheme}://#{uri.host}"
33
41
  conn = @client.authorized_connection(url: url)
34
42
  res = conn.get do |req|
35
43
  req.url uri.path
44
+ resource_params.each { |key, value|
45
+ req.params[key] = value
46
+ }
36
47
  end
37
48
  if res.success?
38
49
  data = JSON.parse(res.body)
@@ -42,6 +53,7 @@ module Springcm
42
53
  end
43
54
  end
44
55
 
56
+ # Retrieve a page of documents in this folder.
45
57
  def documents(offset: 0, limit: 20)
46
58
  Helpers.validate_offset_limit!(offset, limit)
47
59
  uri = URI(documents_href)
@@ -1,5 +1,6 @@
1
1
  module Springcm
2
2
  module Helpers
3
+ # Validate an offset and limit pair.
3
4
  def self.validate_offset_limit!(offset, limit)
4
5
  if !limit.is_a?(Integer) || limit < 1 || limit > 1000
5
6
  raise ArgumentError.new("Limit must be an integer between 1 and 1000 (inclusive).")
@@ -1,37 +1,40 @@
1
1
  module Springcm
2
- module AccessLevel
3
- # @return [Boolean] Does the API user have see permission
4
- def see?
5
- !!access_level.dig("See")
6
- end
2
+ module Mixins
3
+ # Mixin for objects that have security attached, e.g. folders.
4
+ module AccessLevel
5
+ # @return [Boolean] Does the API user have see permission
6
+ def see?
7
+ !!access_level.dig("See")
8
+ end
7
9
 
8
- # @return [Boolean] Does the API user have read permission
9
- def read?
10
- !!access_level.dig("Read")
11
- end
10
+ # @return [Boolean] Does the API user have read permission
11
+ def read?
12
+ !!access_level.dig("Read")
13
+ end
12
14
 
13
- # @return [Boolean] Does the API user have write permission
14
- def write?
15
- !!access_level.dig("Write")
16
- end
15
+ # @return [Boolean] Does the API user have write permission
16
+ def write?
17
+ !!access_level.dig("Write")
18
+ end
17
19
 
18
- # @return [Boolean] Does the API user have move permission
19
- def move?
20
- !!access_level.dig("Move")
21
- end
20
+ # @return [Boolean] Does the API user have move permission
21
+ def move?
22
+ !!access_level.dig("Move")
23
+ end
22
24
 
23
- # @return [Boolean] Does the API user have create permission
24
- def create?
25
- !!access_level.dig("Create")
26
- end
25
+ # @return [Boolean] Does the API user have create permission
26
+ def create?
27
+ !!access_level.dig("Create")
28
+ end
27
29
 
28
- # @return [Boolean] Does the API user have set access permission
29
- def set_access?
30
- !!access_level.dig("SetAccess")
31
- end
30
+ # @return [Boolean] Does the API user have set access permission
31
+ def set_access?
32
+ !!access_level.dig("SetAccess")
33
+ end
32
34
 
33
- def access_level
34
- @data.fetch("AccessLevel")
35
+ def access_level
36
+ @data.fetch("AccessLevel")
37
+ end
35
38
  end
36
39
  end
37
40
  end
@@ -0,0 +1,52 @@
1
+ module Springcm
2
+ module Mixins
3
+ # Mixin for objects that have attributes.
4
+ module Attributes
5
+ def deserialize_field(field)
6
+ type = field["AttributeType"]
7
+ value = field["Value"]
8
+ if type == "String"
9
+ value
10
+ elsif type == "Number"
11
+ value.to_i
12
+ elsif type == "Decimal"
13
+ value.to_f
14
+ elsif type == "Date"
15
+ Date.strptime(value[0..8], "%Y%m%d")
16
+ else
17
+ value
18
+ end
19
+ end
20
+
21
+ def get_attribute(group:, field:)
22
+ group_config = @client.account.all_attribute_groups.select { |g|
23
+ g.name == group
24
+ }.first
25
+ # Non-existent group
26
+ return nil if group_config.nil?
27
+ field_config = group_config.field(field)
28
+ field_set_config = group_config.set_for_field(field)
29
+ # Non-existent field
30
+ return nil if field_config.nil?
31
+ groups = attribute_groups
32
+ # No attribute groups applied
33
+ return nil if groups.nil?
34
+ group_data = groups.fetch(group, nil)
35
+ # Group is not applied
36
+ return nil if group_data.nil?
37
+ # Repeating set
38
+ if !field_set_config.nil? && field_set_config["RepeatingAttribute"]
39
+ set_data = group_data.fetch(field_set_config["Name"], nil)
40
+ return nil if set_data.nil?
41
+ set_data["Items"].map { |item|
42
+ deserialize_field(item[field])
43
+ }
44
+ else
45
+ field_data = group_data.fetch(field, nil)
46
+ return nil if field_data.nil?
47
+ deserialize_field(field_data)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,7 +1,9 @@
1
1
  module Springcm
2
- module Documents
3
- def documents_href
4
- @data.dig("Documents", "Href")
2
+ module Mixins
3
+ module Documents
4
+ def documents_href
5
+ @data.dig("Documents", "Href")
6
+ end
5
7
  end
6
8
  end
7
9
  end
@@ -1,8 +1,30 @@
1
1
  module Springcm
2
- module ParentFolder
3
- def parent_folder_href
4
- # Root folders won't have ParentFolder key
5
- @data.dig("ParentFolder", "Href")
2
+ module Mixins
3
+ # Mixin for objects that have a parent folder.
4
+ module ParentFolder
5
+ def parent_folder_href
6
+ # Root folders won't have ParentFolder key
7
+ @data.dig("ParentFolder", "Href")
8
+ end
9
+
10
+ def move(path: nil, uid: nil)
11
+ parent = @client.folder(path: path, uid: uid)
12
+ body = {
13
+ "ParentFolder" => parent.raw
14
+ }
15
+ conn = @client.authorized_connection(url: @client.object_api_url)
16
+ res = conn.patch do |req|
17
+ req.headers["Content-Type"] = "application/json"
18
+ req.url resource_uri
19
+ req.body = body.to_json
20
+ end
21
+ if res.success?
22
+ data = JSON.parse(res.body)
23
+ self.class.new(data, @client)
24
+ else
25
+ nil
26
+ end
27
+ end
6
28
  end
7
29
  end
8
30
  end
@@ -5,6 +5,15 @@ module Springcm
5
5
  @data = data
6
6
  end
7
7
 
8
+ # Retrieve a top-level property of the object's JSON data.
9
+ #
10
+ # For convenience, top-level properties of a SpringCM object's JSON data
11
+ # are accessible via instance methods (underscore format), e.g.
12
+ # attribute_groups to retrieve JSON for $.AttributeGroups. This can be and
13
+ # is often overridden by inheriting classes by defining a method and
14
+ # extending what it does. Some mixins also provide convenience methods
15
+ # for retrieving data deeper in the JSON document, e.g. documents_href
16
+ # via Springcm::Mixins::Documents.
8
17
  def method_missing(m, *args, &block)
9
18
  key = m.to_s.split("_").map(&:capitalize).join
10
19
  if @data.key?(key)
@@ -13,5 +22,10 @@ module Springcm
13
22
  super
14
23
  end
15
24
  end
25
+
26
+ # Retrieve the raw JSON document for this object.
27
+ def raw
28
+ @data
29
+ end
16
30
  end
17
31
  end
@@ -1,10 +1,65 @@
1
1
  require "springcm-sdk/object"
2
2
 
3
3
  module Springcm
4
+ # A Resource is a SpringCM object that has an auto-assigned GUID.
4
5
  class Resource < Object
5
- # @return [String] The folder unique identifier (UID)
6
+ # @return [String] The object's unique identifier (UID)
6
7
  def uid
7
8
  href[-36..-1]
8
9
  end
10
+
11
+ # Resend a request to the API for this resource and return a new instance.
12
+ def reload
13
+ get
14
+ end
15
+
16
+ # Send a GET request for this resource.
17
+ def get
18
+ conn = @client.authorized_connection(url: @client.object_api_url)
19
+ res = conn.get do |req|
20
+ req.url resource_uri
21
+ resource_params.each { |key, value|
22
+ req.params[key] = value
23
+ }
24
+ end
25
+ if res.success?
26
+ data = JSON.parse(res.body)
27
+ self.class.new(data, @client)
28
+ else
29
+ nil
30
+ end
31
+ end
32
+
33
+ # Send a DELETE request for this resource.
34
+ def delete
35
+ conn = @client.authorized_connection(url: @client.object_api_url)
36
+ res = conn.delete do |req|
37
+ req.url resource_uri
38
+ end
39
+ if res.success?
40
+ data = JSON.parse(res.body)
41
+ reload
42
+ else
43
+ nil
44
+ end
45
+ end
46
+
47
+ # Retrieve the URI for this resource (relative to the base object API
48
+ # URL).
49
+ def resource_uri
50
+ "#{resource_name}/#{uid}"
51
+ end
52
+
53
+ # Some resources have query parameters that must be passed when
54
+ # retrieving it, e.g. expand=attributegroups when retrieving a document.
55
+ def resource_params
56
+ {}
57
+ end
58
+
59
+ # Pluralized resource name, e.g. documents or folders. Used to construct
60
+ # request URLs.
61
+ def resource_name
62
+ "#{self.class.to_s.split("::").last.downcase}s"
63
+ end
9
64
  end
10
65
  end
@@ -1,7 +1,11 @@
1
1
  module Springcm
2
+ # A list object of arbitrary SpringCM resources. Allows for easy navigation
3
+ # of paged resources like documents and attribute groups. All resources
4
+ # that are retrieved in this manner are attached to a parent object, e.g.
5
+ # the account for attribute groups, or a folder for documents.
2
6
  class ResourceList < Object
3
- def initialize(data, parent_folder, kind, client)
4
- @parent_folder = parent_folder
7
+ def initialize(data, parent_object, kind, client)
8
+ @parent_object = parent_object
5
9
  @kind = kind
6
10
  super(data, client)
7
11
  end
@@ -38,7 +42,7 @@ module Springcm
38
42
  query = CGI.parse(uri.query || "")
39
43
  offset = query.fetch("offset", ["0"]).first.to_i
40
44
  limit = query.fetch("limit", ["20"]).first.to_i
41
- @parent_folder.send(method, offset: offset, limit: limit)
45
+ @parent_object.send(method, offset: offset, limit: limit)
42
46
  end
43
47
 
44
48
  def method_for_kind!(kind)
@@ -47,8 +51,10 @@ module Springcm
47
51
  method = :folders
48
52
  elsif kind == Springcm::Document
49
53
  method = :documents
54
+ elsif kind == Springcm::AttributeGroup
55
+ method = :attribute_groups
50
56
  else
51
- raise ArgumentError.new("Resource kind must be one of: Springcm::Document, Springcm::Folder.")
57
+ raise ArgumentError.new("Resource kind must be one of: Springcm::Document, Springcm::Folder, Springcm::AttributeGroup.")
52
58
  end
53
59
  return method
54
60
  end
@@ -1,3 +1,3 @@
1
1
  module Springcm
2
- VERSION = "0.3.2"
2
+ VERSION = "0.3.4"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: springcm-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Holden
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-11-08 00:00:00.000000000 Z
11
+ date: 2019-12-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -160,11 +160,14 @@ files:
160
160
  - exe/springcm
161
161
  - lib/springcm-sdk.rb
162
162
  - lib/springcm-sdk/account.rb
163
+ - lib/springcm-sdk/attribute.rb
164
+ - lib/springcm-sdk/attribute_group.rb
163
165
  - lib/springcm-sdk/client.rb
164
166
  - lib/springcm-sdk/document.rb
165
167
  - lib/springcm-sdk/folder.rb
166
168
  - lib/springcm-sdk/helpers.rb
167
169
  - lib/springcm-sdk/mixins/access_level.rb
170
+ - lib/springcm-sdk/mixins/attributes.rb
168
171
  - lib/springcm-sdk/mixins/documents.rb
169
172
  - lib/springcm-sdk/mixins/parent_folder.rb
170
173
  - lib/springcm-sdk/object.rb
@@ -179,7 +182,7 @@ metadata:
179
182
  allowed_push_host: https://rubygems.org
180
183
  homepage_uri: https://github.com/paulholden2/springcm-sdk
181
184
  source_code_uri: https://github.com/paulholden2/springcm-sdk
182
- documentation_uri: https://rubydoc.info/github/paulholden2/springcm-sdk/0.3.2
185
+ documentation_uri: https://rubydoc.info/github/paulholden2/springcm-sdk/0.3.4
183
186
  changelog_uri: https://github.com/paulholden2/springcm-sdk/blob/master/CHANGELOG.md
184
187
  post_install_message:
185
188
  rdoc_options: []