versacommerce-theme_api_client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 990ff9457b19aff2773db59aa619d9a7a96879c6
4
+ data.tar.gz: 5a23a121d1f16b738575552839d33fab9f98197e
5
+ SHA512:
6
+ metadata.gz: ea312975266e5522796eec6c7ff332627678e0b928c7651a8ffc51526b58b88a1a54c17c7d80429b56a3518fec70b24bc54050119d147209567b10061870cce5
7
+ data.tar.gz: 0d04b8c38e6a91098c97dabacc951b6fa6740f4b29b1b6cb451fb02ed3916ac87209ba2781f3550f8cfe23af990c69cf334f7eb1c6a669fe7df0853ecc782e7f
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 VersaCommerce
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,229 @@
1
+ # Versacommerce::ThemeAPIClient
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/versacommerce-theme_api_client.svg)](http://badge.fury.io/rb/versacommerce-theme_api_client)
4
+
5
+ Versacommerce::ThemeAPIClient is a library to consume the VersaCommerce Theme API.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'versacommerce-theme_api_client'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```sh
18
+ $ bundle install
19
+ ```
20
+
21
+ Or install it yourself as:
22
+
23
+ ```sh
24
+ $ gem install versacommerce-theme_api_client
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ Create a client object to work on:
30
+
31
+ ```ruby
32
+ authorization = 'YOUR_AUTHORIZATION'
33
+ client = Versacommerce::ThemeAPIClient.new(authorization: authorization)
34
+ ```
35
+
36
+ The client object is tied to a single theme, depending on the authorization. You can get an authorization from your shop's admin section.
37
+
38
+ ### Working with Directories
39
+
40
+ #### Finding Directories
41
+
42
+ Calling `#directories` on the client object will get you a `DirectoryRelation` object that responds to `#each`. As it includes the `Enumerable` module, the following will get you an array of all directories:
43
+
44
+ ```ruby
45
+ directories = client.directories.to_a
46
+ ```
47
+
48
+ If you want to get an array of a directory's child directories, call:
49
+
50
+ ```ruby
51
+ directories = client.directories.in_path('path/to/directory').to_a
52
+ ```
53
+
54
+ This is practically the same as calling:
55
+
56
+ ```ruby
57
+ directories = client.directories(path: 'path/to/directory')
58
+ ```
59
+
60
+ These calls are recursive and will return all directory children by default. If you don't want this behaviour and instead only get direct child directories, turn it off by calling:
61
+
62
+ ```ruby
63
+ directories = client.directories(recursive: false)
64
+
65
+ # respectively:
66
+ directories = client.directories.in_path('', recursive: false)
67
+ ```
68
+
69
+ #### Finding a single Directory
70
+
71
+ If you want to find a single directory, call:
72
+ ```ruby
73
+ directory = client.directories.find('path/to/directory')
74
+ ```
75
+
76
+ Nesting with the relation is also possible. The following will try to find the directory `assets/font`:
77
+
78
+ ```ruby
79
+ directory = client.directories(path: 'assets').find('font')
80
+ ```
81
+
82
+ If the directory could be found, a `Directory` instance is returned, else a `RecordNotFoundError` is raised. A `Directory` instance has a `path` instance variable representing its path and responds to `#files`, which returns a `FileRelation` with the directory's path as base for the files.
83
+
84
+ #### Creating a Directory
85
+
86
+ In order to create a directory, use `#build` in combination with `#save` or use `#create`:
87
+
88
+ ```ruby
89
+ directory = client.directories.build(path: 'special_font')
90
+ directory.save # => true or false
91
+
92
+ # which is basically the same as:
93
+ directory = client.directories(path: 'assets/font').create(path: 'special_fonts')
94
+ ```
95
+
96
+ If the directory could not be saved, `false` will be returned and the `directory.errors` object populated with the errors that occured.
97
+
98
+ #### Updating a Directory
99
+
100
+ Updating a directory is similar to creating one:
101
+
102
+ ```ruby
103
+ directory.update(path: 'new/path')
104
+
105
+ # which is basically the same as:
106
+ directory.path = 'new/path'
107
+ directory.save
108
+ ```
109
+
110
+ #### Deleting a Directory
111
+
112
+ Delete a directory by calling:
113
+
114
+ ```ruby
115
+ directory.delete
116
+ ```
117
+
118
+ If you don't want to prefetch a directory in order to delete it, there's also:
119
+
120
+ ```ruby
121
+ client.directories.delete('path/to/directory')
122
+ ```
123
+
124
+ ### Working with Files
125
+
126
+ #### Finding Files
127
+
128
+ Working with files is like dealing with directories. You can retrieve all files by calling:
129
+
130
+ ```ruby
131
+ files = client.files.to_a
132
+ ```
133
+
134
+ This will return an array of all files. A significant difference between a directory and a file is that a file can have content. Finding files using the method described above won't load the files' contents though. Reloading the content is possible:
135
+
136
+ ```ruby
137
+ file = client.files.to_a.first
138
+ file.content # => nil
139
+
140
+ file.reload_content
141
+ file.content # => returns the content as String
142
+ ```
143
+
144
+ Filtering files works like with a directory.
145
+
146
+ ```ruby
147
+ files = client.files(path: 'assets').to_a
148
+
149
+ # or
150
+ files = client.files.in_path('assets').to_a
151
+ ```
152
+
153
+ will return all files in (or below) the `assets` directory.
154
+
155
+ #### Finding a single File
156
+
157
+ ```ruby
158
+ file = client.files(path: 'assets').find('fonts/some_font.svg')
159
+ ```
160
+
161
+ will find the file located at `assets/fonts/some_font.svg`. As this will be a text file, the content will be loaded automatically. If the file is an image (or any other file not in text format) the content will not be loaded. If you want to make sure the content is loaded, provide a `load_content` keyword argument:
162
+
163
+ ```ruby
164
+ file = client.files.find('assets/images/banner.png', load_content: true)
165
+ ```
166
+
167
+ or load the content afterwards manually if it is missing:
168
+
169
+ ```ruby
170
+ file = client.files.find('assets/images/banner.png')
171
+ file.reload_content unless file.has_content?
172
+ ```
173
+
174
+ #### Creating a File
175
+
176
+ In order to create a file, use `#build` in combination with `#save` or use `#create`:
177
+
178
+ ```ruby
179
+ file = client.directories.build(path: 'assets/fonts/some_font.svg', content: 'content here')
180
+ file.save # => true or false
181
+
182
+ # which is basically the same as:
183
+ file = client.directories.create(path: 'assets/font', content: 'content here')
184
+ ```
185
+
186
+ If the file could not be saved, `false` will be returned and the `file.errors` object populated with the errors that occured.
187
+
188
+ #### Updating a File
189
+
190
+ Updating a file is similar to creating one:
191
+
192
+ ```ruby
193
+ file.update(path: 'new/path', content: 'new content')
194
+
195
+ # which is basically the same as:
196
+ file.path = 'new/path'
197
+ file.content = 'new content'
198
+ file.save
199
+ ```
200
+
201
+ #### Deleting a file
202
+
203
+ Delete a file by calling:
204
+
205
+ ```ruby
206
+ file.delete
207
+ ```
208
+
209
+ If you don't want to prefetch a file in order to delete it, there's also:
210
+
211
+ ```ruby
212
+ client.files.delete('path/to/file')
213
+ ```
214
+
215
+ ## Development
216
+
217
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
218
+
219
+ ## Contributing
220
+
221
+ 1. Fork it (https://github.com/versacommerce/versacommerce-theme_api_client/fork)
222
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
223
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
224
+ 4. Push to the branch (`git push origin my-new-feature`)
225
+ 5. Create a new Pull Request
226
+
227
+ ## License
228
+
229
+ [MIT](https://github.com/versacommerce/versacommerce-theme_api_client/blob/master/LICENSE.txt "MIT License").
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
data/bin/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'versacommerce/theme_api_client'
5
+ require 'pry'
6
+
7
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,61 @@
1
+ require 'theme_api_client/fetcher'
2
+ require 'theme_api_client/resources/directory'
3
+ require 'theme_api_client/relations/directory_relation'
4
+ require 'theme_api_client/resources/file'
5
+ require 'theme_api_client/relations/file_relation'
6
+ require 'theme_api_client/version'
7
+
8
+ module Versacommerce
9
+ class ThemeAPIClient
10
+ attr_accessor :authorization
11
+ attr_writer :base_url, :fetcher
12
+
13
+ def initialize(attributes = {})
14
+ attributes.each do |key, value|
15
+ public_send("#{key}=", value)
16
+ end
17
+
18
+ yield(self) if block_given?
19
+ end
20
+
21
+ def base_url
22
+ @base_url || 'https://theme-api.versacommerce.de'
23
+ end
24
+
25
+ def directories(path: '', recursive: true)
26
+ Relations::DirectoryRelation.new(self, path: path, recursive: recursive)
27
+ end
28
+
29
+ def directory_class
30
+ @directory_class ||= Class.new(Resources::Directory).tap { |dir| dir.client = self }
31
+ end
32
+
33
+ def files(path: '', recursive: true)
34
+ Relations::FileRelation.new(self, path: path, recursive: recursive)
35
+ end
36
+
37
+ def file_class
38
+ @file_class ||= Class.new(Resources::File).tap { |dir| dir.client = self }
39
+ end
40
+
41
+ def fetcher
42
+ @fetcher ||= Fetcher.new(self)
43
+ end
44
+
45
+ def directory_path(path = '')
46
+ Pathname.new('directories').join(path)
47
+ end
48
+
49
+ def file_path(path = '')
50
+ Pathname.new('files').join(path)
51
+ end
52
+
53
+ def download_path(path = '')
54
+ Pathname.new('download').join(path)
55
+ end
56
+
57
+ def tree_path(path = '')
58
+ Pathname.new('tree').join(path)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,101 @@
1
+ require 'json'
2
+ require 'http'
3
+ require 'active_support/core_ext/module/delegation'
4
+
5
+ module Versacommerce
6
+ class ThemeAPIClient
7
+ class Fetcher
8
+ RecordNotFoundError = Class.new(StandardError)
9
+ UnauthorizedError = Class.new(StandardError)
10
+
11
+ delegate :authorization, to: :client
12
+
13
+ attr_reader :client
14
+
15
+ def initialize(client)
16
+ @client = client
17
+ end
18
+
19
+ def get(path)
20
+ url = url_for_path(path)
21
+ response = with_headers.get(url)
22
+ handle_response(response, url)
23
+ end
24
+
25
+ def head(path)
26
+ url = url_for_path(path)
27
+ response = with_headers.head(url)
28
+ handle_response(response, url)
29
+ end
30
+
31
+ def post(path, json = {})
32
+ url = url_for_path(path)
33
+ response = with_headers.post(url_for_path(path), json: json)
34
+ handle_response(response, url)
35
+ end
36
+
37
+ def post_form(path, form_data = {})
38
+ url = url_for_path(path)
39
+ response = with_headers.post(url_for_path(path), form: form_data)
40
+ handle_response(response, url)
41
+ end
42
+
43
+ def patch(path, json = {})
44
+ url = url_for_path(path)
45
+ response = with_headers.patch(url_for_path(path), json: json)
46
+ handle_response(response, url)
47
+ end
48
+
49
+ def patch_form(path, form_data = {})
50
+ url = url_for_path(path)
51
+ response = with_headers.patch(url_for_path(path), form: form_data)
52
+ handle_response(response, url)
53
+ end
54
+
55
+ def delete(path = {})
56
+ url = url_for_path(path)
57
+ response = with_headers.delete(url)
58
+ handle_response(response, url)
59
+ end
60
+
61
+ private
62
+
63
+ def handle_response(response, url)
64
+ case response.status
65
+ when ->(s) { s.unauthorized? }
66
+ handle_unauthorized(response)
67
+ when ->(s) { s.not_found? }
68
+ raise RecordNotFoundError.new('Record for URL %s could not be found.' % url)
69
+ else
70
+ response
71
+ end
72
+ end
73
+
74
+ def handle_unauthorized(response)
75
+ if response.json.key?('error')
76
+ raise UnauthorizedError.new('Could not be authorized: %s' % response.json['error'])
77
+ else
78
+ raise UnauthorizedError.new('You could not be authorized.')
79
+ end
80
+ end
81
+
82
+ def url_for_path(path)
83
+ '%s/%s' % [client.base_url, path.to_s]
84
+ end
85
+
86
+ def with_headers
87
+ HTTP.with_headers(accept: 'application/json', 'Theme-Authorization' => authorization)
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ class HTTP::Response
94
+ def json
95
+ @json ||= if mime_type == 'application/json'
96
+ JSON.parse(to_s)
97
+ else
98
+ {}
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,68 @@
1
+ module Versacommerce
2
+ class ThemeAPIClient
3
+ class Relation
4
+ include Enumerable
5
+
6
+ attr_accessor :recursive
7
+ attr_reader :client, :path
8
+
9
+ def initialize(client, attributes = {})
10
+ @client = client
11
+
12
+ attributes.each do |key, value|
13
+ public_send("#{key}=", value)
14
+ end
15
+ end
16
+
17
+ def find(path, **options)
18
+ resource_class.find(combined_path(path), **options)
19
+ end
20
+
21
+ def in_path(path, **options)
22
+ tap do |relation|
23
+ relation.path = path
24
+ relation.recursive = options[:recursive] if options.key?(:recursive)
25
+ end
26
+ end
27
+
28
+ def build(attributes = {})
29
+ path = combined_path(attributes[:path])
30
+ resource_class.new(attributes.merge(path: path))
31
+ end
32
+
33
+ def create(attributes = {})
34
+ build(attributes).tap do |object|
35
+ object.save
36
+ end
37
+ end
38
+
39
+ def delete(path)
40
+ resource_class.delete(combined_path(path))
41
+ end
42
+
43
+ def each(&block)
44
+ files = resource_class.in_path(path, recursive)
45
+ files.each(&block)
46
+ end
47
+
48
+ def path=(value)
49
+ @path = case value
50
+ when Pathname
51
+ value
52
+ else
53
+ Pathname.new(value.gsub(/\A\/*/, ''))
54
+ end
55
+ end
56
+
57
+ def inspect
58
+ to_s
59
+ end
60
+
61
+ private
62
+
63
+ def combined_path(path)
64
+ self.path.join(path || '')
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,13 @@
1
+ require 'versacommerce/theme_api_client/relation'
2
+
3
+ module Versacommerce
4
+ class ThemeAPIClient
5
+ module Relations
6
+ class DirectoryRelation < Relation
7
+ def resource_class
8
+ client.directory_class
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'versacommerce/theme_api_client/relation'
2
+
3
+ module Versacommerce
4
+ class ThemeAPIClient
5
+ module Relations
6
+ class FileRelation < Relation
7
+ def resource_class
8
+ client.file_class
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,60 @@
1
+ require 'active_model'
2
+ require 'active_support/core_ext/module/delegation'
3
+
4
+ module Versacommerce
5
+ class ThemeAPIClient
6
+ class Resource
7
+ include ActiveModel::Model
8
+ include ActiveModel::Dirty
9
+
10
+ class << self
11
+ attr_accessor :client
12
+ delegate :fetcher, to: :client
13
+ end
14
+
15
+ attr_writer :new_record
16
+
17
+ def initialize(attributes = {})
18
+ assign_attributes(attributes)
19
+ clear_changes_information unless new_record?
20
+ end
21
+
22
+ def assign_attributes(attributes = {})
23
+ attributes.each do |key, value|
24
+ public_send("#{key}=", value)
25
+ end
26
+ end
27
+
28
+ def save
29
+ if valid?(current_context) && commit
30
+ self.new_record = false
31
+ changes_applied
32
+ true
33
+ else
34
+ false
35
+ end
36
+ end
37
+
38
+ def update(attributes = {})
39
+ assign_attributes(attributes)
40
+ save
41
+ end
42
+
43
+ def new_record?
44
+ defined?(@new_record) ? @new_record : true
45
+ end
46
+
47
+ private
48
+
49
+ def add_errors(errors)
50
+ errors.each do |key, errors|
51
+ errors.each { |error| self.errors[key] << error }
52
+ end
53
+ end
54
+
55
+ def current_context
56
+ new_record? ? :create : :update
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,104 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+ require 'versacommerce/theme_api_client/fetcher'
3
+ require 'versacommerce/theme_api_client/resources/file_behaviour'
4
+ require 'versacommerce/theme_api_client/relations/file_relation'
5
+ require 'versacommerce/theme_api_client/resource'
6
+
7
+ module Versacommerce
8
+ class ThemeAPIClient
9
+ module Resources
10
+ class Directory < Resource
11
+ include FileBehaviour
12
+
13
+ delegate :client, :fetcher, :directory_path, to: :class
14
+
15
+ class << self
16
+ delegate :fetcher, :directory_path, :tree_path, to: :client
17
+
18
+ def in_path(path = '', recursive = true)
19
+ recursive ? fetch_recursive(path) : fetch_non_recursive(path)
20
+ end
21
+
22
+ def find(path, **options)
23
+ fetcher.head(directory_path(path))
24
+ new(path: path, new_record: false)
25
+ end
26
+
27
+ def delete(path)
28
+ fetcher.delete(directory_path(path))
29
+ clear_changes_information
30
+ self.new_record = true
31
+ end
32
+
33
+ def model_name
34
+ ActiveModel::Name.new(self, nil, 'Directory')
35
+ end
36
+
37
+ private
38
+
39
+ def fetch_recursive(path)
40
+ directory = fetcher.get(tree_path(path)).json
41
+ directories_from_tree(directory)
42
+ end
43
+
44
+ def fetch_non_recursive(path)
45
+ fetcher.get(directory_path(path)).json['directories'].map do |directory|
46
+ new(directory.merge(new_record: false))
47
+ end
48
+ end
49
+
50
+ def directories_from_tree(directory, directories = [])
51
+ directories.tap do |dirs|
52
+ directory.each do |child|
53
+ if child['type'] == 'directory'
54
+ dirs << new(child.slice('path').merge(new_record: false))
55
+ directories_from_tree(child['children'], dirs)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ def directories(recursive: false)
63
+ client.directories(path: path, recursive: recursive)
64
+ end
65
+
66
+ def files(recursive: false)
67
+ client.files(path: path, recursive: recursive)
68
+ end
69
+
70
+ def delete
71
+ unless new_record?
72
+ response = fetcher.delete(directory_path(path))
73
+ response.status.no_content?
74
+ end
75
+ end
76
+
77
+ def inspect
78
+ '#<Directory:0x%x>' % (object_id << 1)
79
+ end
80
+
81
+ private
82
+
83
+ def commit
84
+ response = if new_record?
85
+ fetcher.post(directory_path, serialized_attributes)
86
+ else
87
+ fetcher.patch(directory_path(path_was), serialized_attributes)
88
+ end
89
+
90
+ if response.status.unprocessable_entity?
91
+ add_errors(response.json['errors']) if response.json.key?('errors')
92
+ false
93
+ else
94
+ true
95
+ end
96
+ end
97
+
98
+ def serialized_attributes
99
+ {directory: {path: path.to_s}}
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,135 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+ require 'versacommerce/theme_api_client/fetcher'
3
+ require 'versacommerce/theme_api_client/resources/file_behaviour'
4
+ require 'versacommerce/theme_api_client/resource'
5
+
6
+ module Versacommerce
7
+ class ThemeAPIClient
8
+ module Resources
9
+ class File < Resource
10
+ include FileBehaviour
11
+
12
+ delegate :client, :fetcher, :file_path, :download_path, to: :class
13
+ define_attribute_methods :content
14
+
15
+ class << self
16
+ delegate :fetcher, :file_path, :tree_path, :download_path, to: :client
17
+
18
+ def in_path(path = '', recursive = true)
19
+ recursive ? fetch_recursive(path) : fetch_non_recursive(path)
20
+ end
21
+
22
+ def find(path, load_content: false)
23
+ response = fetcher.get(file_path(path))
24
+ attributes = response.json
25
+
26
+ new(attributes.merge(new_record: false)).tap do |file|
27
+ file.reload_content if load_content && !file.has_content?
28
+ end
29
+ end
30
+
31
+ def delete(path)
32
+ fetcher.delete(file_path(path))
33
+ true
34
+ end
35
+
36
+ def model_name
37
+ ActiveModel::Name.new(self, nil, 'File')
38
+ end
39
+
40
+ private
41
+
42
+ def fetch_recursive(path)
43
+ directory = fetcher.get(tree_path(path)).json
44
+ files_from_tree(directory)
45
+ end
46
+
47
+ def fetch_non_recursive(path)
48
+ directory = fetcher.get(tree_path(path)).json
49
+
50
+ directory.map do |child|
51
+ if child['type'] == 'file'
52
+ new(child.slice('path', 'stats').merge(new_record: false))
53
+ end
54
+ end.compact
55
+ end
56
+
57
+ def files_from_tree(directory, files = [])
58
+ files.tap do |f|
59
+ directory.each do |child|
60
+ case child['type']
61
+ when 'directory'
62
+ files_from_tree(child['children'], f)
63
+ when 'file'
64
+ f << new(child.slice('path', 'content').merge(new_record: false))
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ attr_reader :content
72
+ attr_accessor :stats
73
+
74
+ def delete
75
+ unless new_record?
76
+ response = fetcher.delete(file_path(path))
77
+ content_will_change!
78
+ self.new_record = true
79
+ end
80
+ end
81
+
82
+ def content=(value)
83
+ unless value == @content
84
+ content_will_change!
85
+ @content = value.to_s
86
+ end
87
+ end
88
+
89
+ def has_content?
90
+ !!@content
91
+ end
92
+
93
+ def reload_content
94
+ unless new_record?
95
+ response = fetcher.get(download_path(path))
96
+ self.content = response.to_s
97
+ clear_attribute_changes(:content)
98
+ end
99
+
100
+ self
101
+ end
102
+
103
+ def inspect
104
+ '#<File:0x%x>' % (object_id << 1)
105
+ end
106
+
107
+ private
108
+
109
+ def commit
110
+ response = if new_record?
111
+ fetcher.post_form(file_path, serialized_attributes)
112
+ else
113
+ fetcher.patch_form(file_path(path_was), serialized_attributes)
114
+ end
115
+
116
+ if response.status.unprocessable_entity?
117
+ add_errors(response.json['errors']) if response.json.key?('errors')
118
+ false
119
+ else
120
+ true
121
+ end
122
+ end
123
+
124
+ def serialized_attributes
125
+ if has_content? && content_changed?
126
+ content_file = HTTP::FormData::File.new(StringIO.new(content), filename: name)
127
+ {'file[path]' => path.to_s, 'file[content]' => content_file}
128
+ else
129
+ {'file[path]' => path.to_s}
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,36 @@
1
+ require 'active_support/concern'
2
+
3
+ module Versacommerce
4
+ class ThemeAPIClient
5
+ module Resources
6
+ module FileBehaviour
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ define_attribute_method :path
11
+ attr_reader :path
12
+
13
+ validates :path, presence: true
14
+ validates :name, length: {in: 3..64}, if: proc { name.present? }
15
+ end
16
+
17
+ def path=(value)
18
+ path = Pathname.new(value.sub(/\A\/*/, ''))
19
+
20
+ unless path == @path
21
+ path_will_change!
22
+ @path = path
23
+ end
24
+ end
25
+
26
+ def name
27
+ path.basename.to_s
28
+ end
29
+
30
+ def ==(other)
31
+ other.kind_of?(self.class) && path == other.path
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,5 @@
1
+ module Versacommerce
2
+ class ThemeAPIClient
3
+ VERSION = Gem::Version.new('0.1.0')
4
+ end
5
+ end
@@ -0,0 +1,61 @@
1
+ require 'versacommerce/theme_api_client/fetcher'
2
+ require 'versacommerce/theme_api_client/resources/directory'
3
+ require 'versacommerce/theme_api_client/relations/directory_relation'
4
+ require 'versacommerce/theme_api_client/resources/file'
5
+ require 'versacommerce/theme_api_client/relations/file_relation'
6
+ require 'versacommerce/theme_api_client/version'
7
+
8
+ module Versacommerce
9
+ class ThemeAPIClient
10
+ attr_accessor :authorization
11
+ attr_writer :base_url, :fetcher
12
+
13
+ def initialize(attributes = {})
14
+ attributes.each do |key, value|
15
+ public_send("#{key}=", value)
16
+ end
17
+
18
+ yield(self) if block_given?
19
+ end
20
+
21
+ def base_url
22
+ @base_url || 'https://theme-api.versacommerce.de'
23
+ end
24
+
25
+ def directories(path: '', recursive: true)
26
+ Relations::DirectoryRelation.new(self, path: path, recursive: recursive)
27
+ end
28
+
29
+ def directory_class
30
+ @directory_class ||= Class.new(Resources::Directory).tap { |dir| dir.client = self }
31
+ end
32
+
33
+ def files(path: '', recursive: true)
34
+ Relations::FileRelation.new(self, path: path, recursive: recursive)
35
+ end
36
+
37
+ def file_class
38
+ @file_class ||= Class.new(Resources::File).tap { |dir| dir.client = self }
39
+ end
40
+
41
+ def fetcher
42
+ @fetcher ||= Fetcher.new(self)
43
+ end
44
+
45
+ def directory_path(path = '')
46
+ Pathname.new('directories').join(path)
47
+ end
48
+
49
+ def file_path(path = '')
50
+ Pathname.new('files').join(path)
51
+ end
52
+
53
+ def download_path(path = '')
54
+ Pathname.new('download').join(path)
55
+ end
56
+
57
+ def tree_path(path = '')
58
+ Pathname.new('tree').join(path)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'lib/versacommerce/theme_api_client/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'versacommerce-theme_api_client'
5
+ spec.version = Versacommerce::ThemeAPIClient::VERSION
6
+ spec.authors = ['Tobias Bühlmann']
7
+ spec.email = ['buehlmann@versacommerce.de']
8
+
9
+ spec.summary = 'API Client for the VersaCommercer Theme API.'
10
+ spec.description = 'This Gem acts as an API Client for the VersaCommerce Theme API.'
11
+ spec.homepage = 'https://github.com/versacommerce/versacommerce-theme_api_client'
12
+ spec.license = 'MIT'
13
+
14
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
15
+ spec.bindir = 'exe'
16
+ spec.executables = spec.files.grep(/\Aexe/) { |f| File.basename(f) }
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.required_ruby_version = '>= 2.0.0'
20
+
21
+ spec.add_runtime_dependency 'http', '0.7.2'
22
+ spec.add_runtime_dependency 'activesupport', '~> 4.2'
23
+ spec.add_runtime_dependency 'activemodel', '~> 4.2'
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.8'
26
+ spec.add_development_dependency 'rake', '~> 10.4'
27
+ spec.add_development_dependency 'pry', '0.10.1'
28
+ spec.add_development_dependency 'pry-stack_explorer', '0.4.9.2'
29
+ end
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: versacommerce-theme_api_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tobias Bühlmann
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-03-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: http
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.7.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.7.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activemodel
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 0.10.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 0.10.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry-stack_explorer
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '='
102
+ - !ruby/object:Gem::Version
103
+ version: 0.4.9.2
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '='
109
+ - !ruby/object:Gem::Version
110
+ version: 0.4.9.2
111
+ description: This Gem acts as an API Client for the VersaCommerce Theme API.
112
+ email:
113
+ - buehlmann@versacommerce.de
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - Gemfile
120
+ - LICENSE.txt
121
+ - README.md
122
+ - Rakefile
123
+ - bin/console
124
+ - bin/setup
125
+ - lib/versacommerce/theme_api_client.rb
126
+ - lib/versacommerce/theme_api_client/client.rb
127
+ - lib/versacommerce/theme_api_client/fetcher.rb
128
+ - lib/versacommerce/theme_api_client/relation.rb
129
+ - lib/versacommerce/theme_api_client/relations/directory_relation.rb
130
+ - lib/versacommerce/theme_api_client/relations/file_relation.rb
131
+ - lib/versacommerce/theme_api_client/resource.rb
132
+ - lib/versacommerce/theme_api_client/resources/directory.rb
133
+ - lib/versacommerce/theme_api_client/resources/file.rb
134
+ - lib/versacommerce/theme_api_client/resources/file_behaviour.rb
135
+ - lib/versacommerce/theme_api_client/version.rb
136
+ - versacommerce-theme_api_client.gemspec
137
+ homepage: https://github.com/versacommerce/versacommerce-theme_api_client
138
+ licenses:
139
+ - MIT
140
+ metadata: {}
141
+ post_install_message:
142
+ rdoc_options: []
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: 2.0.0
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubyforge_project:
157
+ rubygems_version: 2.4.5
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: API Client for the VersaCommercer Theme API.
161
+ test_files: []