posthaven_theme 0.1.0
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 +5 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +21 -0
- data/README.md +87 -0
- data/Rakefile +14 -0
- data/bin/phtheme +24 -0
- data/lib/posthaven_theme/cli.rb +332 -0
- data/lib/posthaven_theme/version.rb +3 -0
- data/lib/posthaven_theme.rb +180 -0
- data/posthaven_theme.gemspec +32 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/unit/cli_spec.rb +68 -0
- metadata +160 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ed80c2c7459827fc24ad85f8a2dc41c022b0bd86
|
4
|
+
data.tar.gz: 9e5db4f8afb073fe26d361df97988833264369a6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d4e53ab12a6670aa455366f7a68ab5c64d36d5b3e3383b3cea282324b8e4707377194e58d6c84e6f19846b584df2a6f518cb610ca0c658cd4b6383e9c437c4c3
|
7
|
+
data.tar.gz: 29c343541e6c298ca40bdbf8196742d64408460e5224def74e30121848c0f1a065f536cd430bff0f9a6c58e414ff0c2d25a34a336c1cf787c7a589df50750d4d
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2011 Shopify
|
2
|
+
Copyright (c) 2016 Posthaven
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# Posthaven Theme
|
2
|
+
|
3
|
+
The `posthaven_theme` gem provides command line tools for developing a Posthaven theme on your computer and pushing updates to Posthaven.
|
4
|
+
|
5
|
+
# Requirements
|
6
|
+
|
7
|
+
## Ruby
|
8
|
+
|
9
|
+
This gem requires Ruby 2.0 or above.
|
10
|
+
|
11
|
+
## Posthaven API Key
|
12
|
+
|
13
|
+
Log in and get your [Posthaven account API key here](https://posthaven.com/account/theme_api_key).
|
14
|
+
|
15
|
+
|
16
|
+
# Installation
|
17
|
+
|
18
|
+
To install the `posthaven_theme` gem use 'gem install' (you might have use 'sudo gem install')
|
19
|
+
|
20
|
+
```
|
21
|
+
gem install posthaven_theme
|
22
|
+
```
|
23
|
+
|
24
|
+
to update to the latest version
|
25
|
+
|
26
|
+
```
|
27
|
+
gem update posthaven_theme
|
28
|
+
```
|
29
|
+
|
30
|
+
# Usage
|
31
|
+
|
32
|
+
The gem installs the `phtheme` CLI program.
|
33
|
+
|
34
|
+
### List available commands
|
35
|
+
```
|
36
|
+
phtheme help
|
37
|
+
```
|
38
|
+
|
39
|
+
### Generate a configuration file. For your API key see [above](#posthaven_account).
|
40
|
+
|
41
|
+
```
|
42
|
+
phtheme configure api-key
|
43
|
+
|
44
|
+
```
|
45
|
+
### Upload all files
|
46
|
+
|
47
|
+
```
|
48
|
+
phtheme upload
|
49
|
+
```
|
50
|
+
|
51
|
+
### Upload a single theme file
|
52
|
+
|
53
|
+
```
|
54
|
+
phtheme upload layouts/theme.liquid
|
55
|
+
```
|
56
|
+
|
57
|
+
### Remove a theme file
|
58
|
+
|
59
|
+
```
|
60
|
+
phtheme remove assets/layout.liquid
|
61
|
+
```
|
62
|
+
|
63
|
+
### Completely remove all old theme files and replace them with current local versions
|
64
|
+
|
65
|
+
```
|
66
|
+
phtheme replace
|
67
|
+
```
|
68
|
+
|
69
|
+
### Watch the current theme directory and upload any files as they change
|
70
|
+
```
|
71
|
+
phtheme watch
|
72
|
+
```
|
73
|
+
|
74
|
+
# Configuration
|
75
|
+
|
76
|
+
Running `phtheme configure` generates `config.yml` file in the base directory of your theme. If you are storing your theme in version control it is **highly recommended that you DO NOT** store this file in version control, e.g. in git add it to your `.gitignore`.
|
77
|
+
|
78
|
+
`config.yml` has the following options:
|
79
|
+
|
80
|
+
* `api_key` – Your Posthaven API key
|
81
|
+
* `theme_id` – The ID of the theme to edit. The easiest way to populate the theme id is via the `configure` command above.
|
82
|
+
|
83
|
+
See the `phtheme configure` command above for one step setup of the `config.yml` file.
|
84
|
+
|
85
|
+
# Thanks
|
86
|
+
|
87
|
+
A huge thanks to [Shopify](https://www.shopify.com) for their [shopify_theme](https://github.com/shopify/shopify_theme) gem upon which this is based.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
task :default => [:spec]
|
6
|
+
|
7
|
+
Rake::TestTask.new 'spec' do |t|
|
8
|
+
ENV['test'] = 'true'
|
9
|
+
t.libs = ['lib', 'spec']
|
10
|
+
t.ruby_opts << '-rubygems'
|
11
|
+
t.verbose = true
|
12
|
+
t.test_files = FileList['spec/**/*_spec.rb']
|
13
|
+
end
|
14
|
+
|
data/bin/phtheme
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This allows posthaven_theme to run easily from a git checkout without install.
|
4
|
+
# Hat tip to chriseppstein of Compass fame for the code for this
|
5
|
+
def fallback_load_path(path)
|
6
|
+
retried = false
|
7
|
+
begin
|
8
|
+
yield
|
9
|
+
rescue LoadError
|
10
|
+
unless retried
|
11
|
+
$: << path
|
12
|
+
retried = true
|
13
|
+
retry
|
14
|
+
end
|
15
|
+
raise
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
fallback_load_path(File.join(File.dirname(__FILE__), '..', 'lib')) do
|
20
|
+
require 'posthaven_theme'
|
21
|
+
require 'posthaven_theme/cli'
|
22
|
+
end
|
23
|
+
|
24
|
+
PosthavenTheme::Cli.start(ARGV)
|
@@ -0,0 +1,332 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'yaml'
|
3
|
+
YAML::ENGINE.yamler = 'syck' if defined? Syck
|
4
|
+
require 'abbrev'
|
5
|
+
require 'base64'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'json'
|
8
|
+
require 'filewatcher'
|
9
|
+
require 'mimemagic'
|
10
|
+
|
11
|
+
module PosthavenTheme
|
12
|
+
EXTENSIONS = [
|
13
|
+
{mimetype: 'application/x-liquid', extensions: %w(liquid), parents: 'text/plain'},
|
14
|
+
{mimetype: 'application/json', extensions: %w(json), parents: 'text/plain'},
|
15
|
+
{mimetype: 'application/js', extensions: %w(map), parents: 'text/plain'},
|
16
|
+
{mimetype: 'application/vnd.ms-fontobject', extensions: %w(eot)},
|
17
|
+
{mimetype: 'image/svg+xml', extensions: %w(svg svgz)}
|
18
|
+
]
|
19
|
+
|
20
|
+
def self.configure_mime_magic
|
21
|
+
PosthavenTheme::EXTENSIONS.each do |extension|
|
22
|
+
MimeMagic.add(extension.delete(:mimetype), extension)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Cli < Thor
|
27
|
+
include Thor::Actions
|
28
|
+
|
29
|
+
IGNORE = %w(config.yml)
|
30
|
+
DEFAULT_WHITELIST = %w(layouts/ assets/ config/ snippets/ templates/)
|
31
|
+
TIMEFORMAT = "%H:%M:%S"
|
32
|
+
|
33
|
+
NON_CONFIG_COMMANDS = %w{help configure systeminfo}
|
34
|
+
|
35
|
+
def initialize(args = [], local_options = {}, config = {})
|
36
|
+
unless NON_CONFIG_COMMANDS.include?(config[:current_command].name)
|
37
|
+
setup_config
|
38
|
+
validate_config!
|
39
|
+
end
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "check", "Check configuration"
|
44
|
+
def check
|
45
|
+
say('Configuration [OK]', :green) if PosthavenTheme.asset_list
|
46
|
+
rescue PosthavenTheme::APIError => e
|
47
|
+
report_error('Configuration [FAIL]', e)
|
48
|
+
end
|
49
|
+
|
50
|
+
desc 'configure API_KEY',
|
51
|
+
'Generate a config.yml file – See https://github.com/posthaven/posthaven_theme/#posthaven ' +
|
52
|
+
'for information on how to get your api key. Interactively selects (or creates) a theme' +
|
53
|
+
'on your account to edit.'
|
54
|
+
def configure(api_key=nil, theme_id=nil)
|
55
|
+
if api_key.nil?
|
56
|
+
say('An api key is required!', :red)
|
57
|
+
help(:configure)
|
58
|
+
else
|
59
|
+
config = {api_key: api_key, theme_id: theme_id}
|
60
|
+
PosthavenTheme.config = config
|
61
|
+
themes = PosthavenTheme.theme_list
|
62
|
+
config[:theme_id] ||= select_theme(themes)
|
63
|
+
create_file('config.yml', config.to_yaml)
|
64
|
+
end
|
65
|
+
rescue PosthavenTheme::APIError
|
66
|
+
say('Configuration Failed – Your API Key is Likely Incorrect', :red)
|
67
|
+
end
|
68
|
+
|
69
|
+
desc 'upload FILE', 'Upload all theme assets to site'
|
70
|
+
method_option :quiet, type: :boolean, default: false
|
71
|
+
def upload(*paths)
|
72
|
+
assets = paths.empty? ? local_assets_list : paths
|
73
|
+
assets.each do |asset|
|
74
|
+
send_asset(asset, options['quiet'])
|
75
|
+
end
|
76
|
+
say("Done.", :green) unless options['quiet']
|
77
|
+
rescue PosthavenTheme::APIError => e
|
78
|
+
report_error('Upload Failed.', e)
|
79
|
+
end
|
80
|
+
|
81
|
+
desc 'replace FILE', 'Completely replace site theme assets with local theme assets'
|
82
|
+
method_option :quiet, type: :boolean, default: false
|
83
|
+
def replace(*paths)
|
84
|
+
say('Are you sure you want to completely replace your site theme assets? ' +
|
85
|
+
'This is not undoable.',
|
86
|
+
:yellow)
|
87
|
+
if ask('Continue? (Y/N): ') == 'Y'
|
88
|
+
# only delete files on remote that are not present locally
|
89
|
+
# files present on remote and present locally get overridden anyway
|
90
|
+
remote_assets = if paths.empty?
|
91
|
+
(PosthavenTheme.asset_list.map { |a| a['path'] } - local_assets_list)
|
92
|
+
else
|
93
|
+
paths
|
94
|
+
end
|
95
|
+
remote_assets.each do |asset|
|
96
|
+
unless PosthavenTheme.ignore_files.any? { |regex| regex =~ asset }
|
97
|
+
delete_asset(asset, options['quiet'])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
local_assets = paths.empty? ? local_assets_list : paths
|
101
|
+
local_assets.each do |asset|
|
102
|
+
send_asset(asset, options['quiet'])
|
103
|
+
end
|
104
|
+
say("Done.", :green) unless options['quiet']
|
105
|
+
end
|
106
|
+
rescue PosthavenTheme::APIError => e
|
107
|
+
report_error('Replacement failed.', e)
|
108
|
+
end
|
109
|
+
|
110
|
+
desc 'remove FILE', 'Remove theme asset'
|
111
|
+
method_option :quiet, type: :boolean, default: false
|
112
|
+
def remove(*paths)
|
113
|
+
paths.each do |path|
|
114
|
+
delete_asset(path, options['quiet'])
|
115
|
+
end
|
116
|
+
say("Done.", :green) unless options['quiet']
|
117
|
+
rescue PosthavenTheme::APIError => e
|
118
|
+
report_error("Could not remove.", e)
|
119
|
+
end
|
120
|
+
|
121
|
+
desc 'watch',
|
122
|
+
'upload and delete individual theme assets as they change, ' +
|
123
|
+
'use the --keep_files flag to disable remote file deletion'
|
124
|
+
method_option :quiet, type: :boolean, default: false
|
125
|
+
method_option :keep_files, type: :boolean, default: false
|
126
|
+
def watch
|
127
|
+
puts "Watching current folder: #{Dir.pwd}"
|
128
|
+
watcher do |filename, event|
|
129
|
+
filename = filename.gsub("#{Dir.pwd}/", '')
|
130
|
+
|
131
|
+
next unless local_assets_list.include?(filename)
|
132
|
+
action = if [:changed, :new].include?(event)
|
133
|
+
:send_asset
|
134
|
+
elsif event == :delete
|
135
|
+
:delete_asset
|
136
|
+
else
|
137
|
+
raise NotImplementedError, "Unknown event -- #{event} -- #{filename}"
|
138
|
+
end
|
139
|
+
|
140
|
+
begin
|
141
|
+
send(action, filename, options['quiet'])
|
142
|
+
rescue PosthavenTheme::APIError => e
|
143
|
+
verb = action == :send_asset ? 'save' : 'delete'
|
144
|
+
report_error("Unable to #{verb} asset.", e)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
desc 'systeminfo',
|
150
|
+
'print out system information and actively loaded libraries for aiding in submitting bug reports'
|
151
|
+
def systeminfo
|
152
|
+
ruby_version = "#{RUBY_VERSION}"
|
153
|
+
ruby_version += "-p#{RUBY_PATCHLEVEL}" if RUBY_PATCHLEVEL
|
154
|
+
puts "Ruby: v#{ruby_version}"
|
155
|
+
puts "Operating System: #{RUBY_PLATFORM}"
|
156
|
+
%w(Thor Listen HTTParty).each do |lib|
|
157
|
+
require "#{lib.downcase}/version"
|
158
|
+
puts "#{lib}: v" + Kernel.const_get("#{lib}::VERSION")
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
protected
|
163
|
+
|
164
|
+
def config
|
165
|
+
@config ||= YAML.load_file 'config.yml'
|
166
|
+
end
|
167
|
+
|
168
|
+
def site_theme_url
|
169
|
+
url = config[:site]
|
170
|
+
url += "?preview_theme_id=#{config[:theme_id]}" if config[:theme_id] && config[:theme_id].to_i > 0
|
171
|
+
url
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
def watcher
|
177
|
+
FileWatcher.new(Dir.pwd).watch() do |filename, event|
|
178
|
+
yield(filename, event)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def local_assets_list
|
183
|
+
local_files.reject do |p|
|
184
|
+
@permitted_files ||= (DEFAULT_WHITELIST | PosthavenTheme.whitelist_files).map{|pattern| Regexp.new(pattern)}
|
185
|
+
@permitted_files.none? { |regex| regex =~ p } || PosthavenTheme.ignore_files.any? { |regex| regex =~ p }
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def local_files
|
190
|
+
Dir.glob(File.join('**', '*')).reject do |f|
|
191
|
+
File.directory?(f)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def download_asset(path)
|
196
|
+
return unless valid?(path)
|
197
|
+
asset = PosthavenTheme.get_asset(path)
|
198
|
+
if asset['value']
|
199
|
+
# For CRLF line endings
|
200
|
+
content = asset['value'].gsub("\r", "")
|
201
|
+
format = "w"
|
202
|
+
elsif asset['attachment']
|
203
|
+
content = Base64.decode64(asset['attachment'])
|
204
|
+
format = "w+b"
|
205
|
+
end
|
206
|
+
|
207
|
+
FileUtils.mkdir_p(File.dirname(path))
|
208
|
+
File.open(path, format) {|f| f.write content} if content
|
209
|
+
rescue PosthavenTheme::APIError => e
|
210
|
+
report_error(Time.now, "Could not download #{path}", e)
|
211
|
+
end
|
212
|
+
|
213
|
+
def send_asset(asset, quiet=false)
|
214
|
+
return unless valid?(asset)
|
215
|
+
data = {path: asset}
|
216
|
+
content = File.read(asset)
|
217
|
+
if binary_file?(asset) || PosthavenTheme.is_binary_data?(content)
|
218
|
+
content = File.open(asset, "rb") { |io| io.read }
|
219
|
+
data.merge!(attachment: Base64.encode64(content))
|
220
|
+
else
|
221
|
+
data.merge!(value: content)
|
222
|
+
end
|
223
|
+
|
224
|
+
response = show_during("[#{timestamp}] Uploading: #{asset}", quiet) do
|
225
|
+
PosthavenTheme.send_asset(data)
|
226
|
+
end
|
227
|
+
if response.success?
|
228
|
+
say("[#{timestamp}] Uploaded: #{asset}", :green) unless quiet
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def delete_asset(path, quiet=false)
|
233
|
+
return unless valid?(path)
|
234
|
+
response = show_during("[#{timestamp}] Removing: #{path}", quiet) do
|
235
|
+
PosthavenTheme.delete_asset(path)
|
236
|
+
end
|
237
|
+
if response.success?
|
238
|
+
say("[#{timestamp}] Removed: #{path}", :green) unless quiet
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def select_theme(existing)
|
243
|
+
opt = ask_with_options('Configure the theme to edit:', [
|
244
|
+
'Create a new theme',
|
245
|
+
'Select an existing theme',
|
246
|
+
])
|
247
|
+
case opt
|
248
|
+
when 0
|
249
|
+
create_theme['id']
|
250
|
+
when 1
|
251
|
+
existing.sort_by! { |t| t['name'] }
|
252
|
+
n = ask_with_options("Select from your existing themes:", existing.map { |t| t['name'] })
|
253
|
+
existing[n]['id']
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# Options are strings to be numbered. Index of selected option is returned.
|
258
|
+
def ask_with_options(prompt, options)
|
259
|
+
say(prompt)
|
260
|
+
options.each.with_index do |o, idx|
|
261
|
+
say(" %2d: %s" % [idx + 1, o])
|
262
|
+
end
|
263
|
+
n = ask('Select>', limited_to: (1..options.size).map(&:to_s))
|
264
|
+
n.to_i - 1
|
265
|
+
end
|
266
|
+
|
267
|
+
def create_theme
|
268
|
+
name = ''
|
269
|
+
begin
|
270
|
+
if (name = ask('What would you like to name your theme?')).size == 0
|
271
|
+
say('Oops, the theme needs a name (you can change it later).', :red)
|
272
|
+
end
|
273
|
+
end until name.size > 0
|
274
|
+
PosthavenTheme.create_theme(name: name)
|
275
|
+
end
|
276
|
+
|
277
|
+
def notify_and_sleep(message)
|
278
|
+
say(message, :red)
|
279
|
+
PosthavenTheme.sleep
|
280
|
+
end
|
281
|
+
|
282
|
+
def valid?(path)
|
283
|
+
return true if DEFAULT_WHITELIST.include?(path.split('/').first + "/")
|
284
|
+
say("'#{path}' is not in a valid file for theme uploads", :yellow)
|
285
|
+
say("Files need to be in one of the following subdirectories: #{DEFAULT_WHITELIST.join(' ')}", :yellow)
|
286
|
+
false
|
287
|
+
end
|
288
|
+
|
289
|
+
def binary_file?(path)
|
290
|
+
mime = MimeMagic.by_path(path)
|
291
|
+
say("'#{path}' is an unknown file-type, uploading asset as binary", :yellow) if mime.nil? && ENV['TEST'] != 'true'
|
292
|
+
mime.nil? || !mime.text?
|
293
|
+
end
|
294
|
+
|
295
|
+
def report_error(message, error)
|
296
|
+
say("[#{timestamp(Time.now)}] Error: #{message}", :red)
|
297
|
+
say("Error Details: #{error.message}", :yellow)
|
298
|
+
end
|
299
|
+
|
300
|
+
def show_during(message = '', quiet = false, &block)
|
301
|
+
print(message) unless quiet
|
302
|
+
result = yield
|
303
|
+
print("\r#{' ' * message.length}\r") unless quiet
|
304
|
+
result
|
305
|
+
rescue
|
306
|
+
print("\n") unless quiet
|
307
|
+
raise
|
308
|
+
end
|
309
|
+
|
310
|
+
def timestamp(time = Time.now)
|
311
|
+
time.strftime(TIMEFORMAT)
|
312
|
+
end
|
313
|
+
|
314
|
+
def setup_config
|
315
|
+
if File.exist?('config.yml')
|
316
|
+
PosthavenTheme.config = YAML.load(File.read('config.yml'))
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def validate_config!
|
321
|
+
if PosthavenTheme.config.empty?
|
322
|
+
say 'config.yml does not exist or is empty!', :red
|
323
|
+
exit
|
324
|
+
elsif %i{api_key theme_id}.any? { |k| PosthavenTheme.config[k].nil? }
|
325
|
+
say 'config.yml must include :api_key: and :theme_id:!', :red
|
326
|
+
exit
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
PosthavenTheme.configure_mime_magic
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'posthaven_theme/version'
|
2
|
+
require 'httparty'
|
3
|
+
|
4
|
+
module PosthavenTheme
|
5
|
+
include HTTParty
|
6
|
+
@@current_api_call_count = 0
|
7
|
+
@@total_api_calls = 40
|
8
|
+
|
9
|
+
headers "User-Agent" => "posthaven_theme #{PosthavenTheme::VERSION}"
|
10
|
+
|
11
|
+
class APIError < StandardError
|
12
|
+
attr_accessor :response
|
13
|
+
|
14
|
+
def initialize(response)
|
15
|
+
@response = response
|
16
|
+
end
|
17
|
+
|
18
|
+
def message
|
19
|
+
"#{response_message} – #{response_error_message(response)}"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def response_message
|
25
|
+
if @response.response && @response.response.message && @response.response.message.size > 0
|
26
|
+
@response.response.message
|
27
|
+
else
|
28
|
+
@response.code
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def response_error_message(response)
|
33
|
+
errors = @response.parsed_response ? @response.parsed_response["errors"] : @response.body
|
34
|
+
case errors
|
35
|
+
when NilClass
|
36
|
+
''
|
37
|
+
when String
|
38
|
+
errors.strip
|
39
|
+
when Array
|
40
|
+
errors.join(", ")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
TIMER_RESET = 10
|
46
|
+
PERMIT_LOWER_LIMIT = 3
|
47
|
+
|
48
|
+
DEFAULT_API_ENDPOINT = 'https://api.posthaven.com/v1'
|
49
|
+
|
50
|
+
def self.test?
|
51
|
+
ENV['test']
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.manage_timer(response)
|
55
|
+
return unless response.headers['x-posthaven-api-call-limit']
|
56
|
+
@@current_api_call_count, @@total_api_calls = response.headers['x-posthaven-api-call-limit']
|
57
|
+
.split('/')
|
58
|
+
@@current_timer = Time.now if @current_timer.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.critical_permits?
|
62
|
+
@@total_api_calls.to_i - @@current_api_call_count.to_i < PERMIT_LOWER_LIMIT
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.passed_api_refresh?
|
66
|
+
delta_seconds > TIMER_RESET
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.delta_seconds
|
70
|
+
Time.now.to_i - @@current_timer.to_i
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.needs_sleep?
|
74
|
+
critical_permits? && !passed_api_refresh?
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.sleep
|
78
|
+
if needs_sleep?
|
79
|
+
Kernel.sleep(TIMER_RESET - delta_seconds)
|
80
|
+
@current_timer = nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.api_usage
|
85
|
+
"[API Limit: #{@@current_api_call_count || "??"}/#{@@total_api_calls || "??"}]"
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.theme_list
|
89
|
+
handle_response(get(themes_path))
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.create_theme(data)
|
93
|
+
handle_response(post(themes_path, body: {theme: data}))
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.asset_list
|
97
|
+
handle_response(get(assets_path))
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.get_asset(asset)
|
101
|
+
handle_response(get(asset_path(asset)))
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.send_asset(data)
|
105
|
+
handle_response(put(asset_path(data[:path]), body: {asset: data}))
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.delete_asset(asset)
|
109
|
+
handle_response(delete(asset_path(asset)))
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.config
|
113
|
+
@config
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.config=(config)
|
117
|
+
@config = config && Hash[config.map { |k, v| [k.to_sym, v] }]
|
118
|
+
setup
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.themes_path
|
122
|
+
'/themes.json'
|
123
|
+
end
|
124
|
+
def self.theme_path(theme_id = config[:theme_id])
|
125
|
+
"/themes/#{theme_id}"
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.assets_path
|
129
|
+
theme_path + "/assets.json"
|
130
|
+
end
|
131
|
+
def self.asset_path(path)
|
132
|
+
theme_path + "/asset.json?path=#{path}"
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.ignore_files
|
136
|
+
(config[:ignore_files] || []).compact.map { |r| Regexp.new(r) }
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.whitelist_files
|
140
|
+
(config[:whitelist_files] || []).compact
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.is_binary_data?(string)
|
144
|
+
if string.respond_to?(:encoding)
|
145
|
+
string.encoding == "US-ASCII"
|
146
|
+
else
|
147
|
+
unless string.empty?
|
148
|
+
(string.count("^ -~", "^\r\n").fdiv(string.size) > 0.3 || string.index("\x00"))
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def self.handle_response(response)
|
156
|
+
manage_timer(response)
|
157
|
+
|
158
|
+
if response.success?
|
159
|
+
if response.parsed_response
|
160
|
+
response.parsed_response["data"]
|
161
|
+
else
|
162
|
+
response
|
163
|
+
end
|
164
|
+
else
|
165
|
+
raise APIError.new(response)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.setup
|
170
|
+
# Basics
|
171
|
+
headers 'Authorization' => "Token #{config[:api_key]}"
|
172
|
+
base_uri (config[:api_endpoint] || ENV['PHTHEME_ENDPOINT'] || DEFAULT_API_ENDPOINT)
|
173
|
+
|
174
|
+
# Dev
|
175
|
+
require 'resolv-replace' if base_uri.include?(':')
|
176
|
+
|
177
|
+
debug_output $stdout if config[:debug] || ENV['PHTHEME_DEBUG']
|
178
|
+
default_options.update(verify: false) if ENV['PHTHEME_IGNORE_SSL_VERIFY']
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "posthaven_theme/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "posthaven_theme"
|
7
|
+
s.version = PosthavenTheme::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Brett Gibson"]
|
10
|
+
s.email = ["help@posthaven.com"]
|
11
|
+
s.homepage = "https://github.com/posthaven/posthaven_theme"
|
12
|
+
s.summary = %q{Command line tool for developing themes}
|
13
|
+
s.description = %q{Command line tool to help with developing Posthaven themes. Provides simple commands to download, upload and delete files from a theme. Also includes the watch command to watch a directory and upload files as they change.}
|
14
|
+
s.license = 'MIT'
|
15
|
+
|
16
|
+
s.required_ruby_version = '>= 2.0'
|
17
|
+
|
18
|
+
s.rubyforge_project = "posthaven_theme"
|
19
|
+
s.add_dependency('thor', '>= 0.14.4')
|
20
|
+
s.add_dependency('httparty', '~> 0.13.0')
|
21
|
+
s.add_dependency('json', '~> 1.8.0')
|
22
|
+
s.add_dependency('mimemagic')
|
23
|
+
s.add_dependency('filewatcher')
|
24
|
+
|
25
|
+
s.add_development_dependency 'rake'
|
26
|
+
s.add_development_dependency 'minitest', '>= 5.0.0'
|
27
|
+
|
28
|
+
s.files = `git ls-files`.split("\n")
|
29
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
30
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
31
|
+
s.require_paths = ['lib']
|
32
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'posthaven_theme'
|
4
|
+
require 'posthaven_theme/cli'
|
5
|
+
|
6
|
+
module PosthavenTheme
|
7
|
+
describe "Cli" do
|
8
|
+
|
9
|
+
class CliDouble < Cli
|
10
|
+
attr_writer :local_files, :mock_config
|
11
|
+
|
12
|
+
desc "",""
|
13
|
+
def config
|
14
|
+
@mock_config || super
|
15
|
+
end
|
16
|
+
desc "",""
|
17
|
+
def binary_file?(file)
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "", ""
|
22
|
+
def local_files
|
23
|
+
@local_files
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:command_name) { 'help' }
|
28
|
+
let(:command) { Thor::Command.new(command_name, nil, nil, nil, {}) }
|
29
|
+
let(:cli) { CliDouble.new([], {}, {current_command: command}) }
|
30
|
+
|
31
|
+
before { PosthavenTheme.config = {} }
|
32
|
+
|
33
|
+
it "should remove assets that are not a part of the white list" do
|
34
|
+
cli.local_files = ['assets/image.png', 'config.yml', 'layouts/theme.liquid']
|
35
|
+
local_assets_list = cli.send(:local_assets_list)
|
36
|
+
assert_equal 2, local_assets_list.length
|
37
|
+
assert_equal false, local_assets_list.include?('config.yml')
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should remove assets that are part of the ignore list" do
|
41
|
+
PosthavenTheme.config = {ignore_files: ['config/settings.html']}
|
42
|
+
cli.local_files = ['assets/image.png', 'layouts/theme.liquid', 'config/settings.html']
|
43
|
+
local_assets_list = cli.send(:local_assets_list)
|
44
|
+
assert_equal 2, local_assets_list.length
|
45
|
+
assert_equal false, local_assets_list.include?('config/settings.html')
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should report binary files as such" do
|
49
|
+
extensions = %w(png gif jpg jpeg eot svg ttf woff otf swf ico pdf)
|
50
|
+
extensions.each do |ext|
|
51
|
+
assert cli.binary_file?("hello.#{ext}"), "#{ext.upcase}s are binary files"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should report unknown files as binary files" do
|
56
|
+
assert cli.binary_file?('omg.wut'), "Unknown filetypes are assumed to be binary"
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should not report text based files as binary" do
|
60
|
+
refute cli.binary_file?('theme.liquid'), "liquid files are not binary"
|
61
|
+
refute cli.binary_file?('style.sass.liquid'), "sass.liquid files are not binary"
|
62
|
+
refute cli.binary_file?('style.css'), 'CSS files are not binary'
|
63
|
+
refute cli.binary_file?('application.js'), 'Javascript files are not binary'
|
64
|
+
refute cli.binary_file?('settings_data.json'), 'JSON files are not binary'
|
65
|
+
refute cli.binary_file?('applicaton.js.map'), 'Javascript Map files are not binary'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
metadata
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: posthaven_theme
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brett Gibson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-08-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.14.4
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.14.4
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: httparty
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.13.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.13.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: json
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.8.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.8.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mimemagic
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: filewatcher
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: minitest
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 5.0.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 5.0.0
|
111
|
+
description: Command line tool to help with developing Posthaven themes. Provides
|
112
|
+
simple commands to download, upload and delete files from a theme. Also includes
|
113
|
+
the watch command to watch a directory and upload files as they change.
|
114
|
+
email:
|
115
|
+
- help@posthaven.com
|
116
|
+
executables:
|
117
|
+
- phtheme
|
118
|
+
extensions: []
|
119
|
+
extra_rdoc_files: []
|
120
|
+
files:
|
121
|
+
- ".gitignore"
|
122
|
+
- Gemfile
|
123
|
+
- MIT-LICENSE
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- bin/phtheme
|
127
|
+
- lib/posthaven_theme.rb
|
128
|
+
- lib/posthaven_theme/cli.rb
|
129
|
+
- lib/posthaven_theme/version.rb
|
130
|
+
- posthaven_theme.gemspec
|
131
|
+
- spec/spec_helper.rb
|
132
|
+
- spec/unit/cli_spec.rb
|
133
|
+
homepage: https://github.com/posthaven/posthaven_theme
|
134
|
+
licenses:
|
135
|
+
- MIT
|
136
|
+
metadata: {}
|
137
|
+
post_install_message:
|
138
|
+
rdoc_options: []
|
139
|
+
require_paths:
|
140
|
+
- lib
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '2.0'
|
146
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
requirements: []
|
152
|
+
rubyforge_project: posthaven_theme
|
153
|
+
rubygems_version: 2.6.4
|
154
|
+
signing_key:
|
155
|
+
specification_version: 4
|
156
|
+
summary: Command line tool for developing themes
|
157
|
+
test_files:
|
158
|
+
- spec/spec_helper.rb
|
159
|
+
- spec/unit/cli_spec.rb
|
160
|
+
has_rdoc:
|