voog-kit 0.1.8

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.
@@ -0,0 +1,784 @@
1
+ require 'voog_api'
2
+ require 'net/http'
3
+ require 'json'
4
+ require 'fileutils'
5
+ require 'git'
6
+ require 'mime/types'
7
+
8
+ module Voog::Dtk
9
+ class FileManager
10
+ attr_accessor :notifier
11
+ def initialize(client, verbose=false, silent=false)
12
+ @notifier = Voog::Dtk::Notifier.new($stderr, silent)
13
+ @client = client
14
+ @verbose = verbose
15
+ end
16
+
17
+ def read_manifest
18
+ JSON.parse(File.read('manifest.json')).to_h
19
+ end
20
+
21
+ def write_manifest(manifest)
22
+ File.open('manifest.json', 'w+') do |file|
23
+ file << JSON.pretty_generate(manifest)
24
+ end
25
+ end
26
+
27
+ def in_manifest?(file, manifest=nil)
28
+ @manifest = manifest || read_manifest
29
+ filenames = @manifest['layouts'].map{|l| l.fetch('file', '')}
30
+ filenames += @manifest['assets'].map{|a| a.fetch('filename', '')}
31
+ filenames.include? file
32
+ end
33
+
34
+ def add_to_manifest(files = nil)
35
+ return if files.nil?
36
+ @manifest = read_manifest
37
+ files = (files.is_a? String) ? [files] : files
38
+ files.uniq.each do |file|
39
+ next if in_manifest?(file, @manifest)
40
+ match = /^(component|layout|image|javascript|asset|stylesheet)s\/(.*)/.match(file)
41
+ next if match.nil?
42
+ type, filename = match[1], match[2]
43
+ if %w(component layout).include? type
44
+ component = type == 'component'
45
+ name = filename.split('.').first
46
+ title = component ? name : name.gsub('_', ' ').capitalize
47
+ layout = {
48
+ 'title' => component ? name : title,
49
+ 'layout_name' => name,
50
+ 'content_type' => component ? 'component' : 'page',
51
+ 'component' => component,
52
+ 'file' => file
53
+ }
54
+ @manifest['layouts'] << layout
55
+ elsif %w(image javascript asset stylesheet).include? type
56
+ asset = {
57
+ 'content_type' => begin
58
+ MIME::Types.type_for(filename).first.content_type
59
+ rescue
60
+ 'text/unknown'
61
+ end,
62
+ 'kind' => "#{type}s",
63
+ 'file' => file,
64
+ 'filename' => filename
65
+ }
66
+ @manifest['assets'] << asset
67
+ end
68
+ @notifier.newline
69
+ @notifier.info "Added #{file} to manifest.json"
70
+ end
71
+ write_manifest @manifest
72
+ @notifier.newline
73
+ end
74
+
75
+ def remove_from_manifest(files = nil)
76
+ return if files.nil?
77
+ @manifest = read_manifest
78
+ files = (files.is_a? String) ? [files] : files
79
+ files.uniq.each do |file|
80
+ match = /^(component|layout|image|javascript|asset|stylesheet)s\/(.*)/.match(file)
81
+ next if match.nil?
82
+ type, filename = match[1], match[2]
83
+ if %w(component layout).include? type
84
+ @manifest['layouts'].delete_if do |layout|
85
+ match = layout.fetch('file', nil) == file
86
+ @notifier.info "Removed #{file} from manifest.json\n" if match
87
+ match
88
+ end
89
+ elsif %w(image javascript asset stylesheet).include? type
90
+ @manifest['assets'].delete_if do |asset|
91
+ match = asset.fetch('file', nil) == file
92
+ @notifier.info "Removed #{file} from manifest.json\n" if match
93
+ match
94
+ end
95
+ end
96
+ end
97
+ write_manifest @manifest
98
+ end
99
+
100
+ def delete_remote_files(files)
101
+ return if files.nil?
102
+ files = (files.is_a? String) ? [files] : files
103
+ layouts = layout_id_map
104
+ assets = asset_id_map
105
+ files.uniq.each do |file|
106
+
107
+ end
108
+ end
109
+
110
+ def get_layouts
111
+ @client.layouts(per_page: 10000)
112
+ end
113
+
114
+ def get_layout_assets
115
+ @client.layout_assets(per_page: 10000)
116
+ end
117
+
118
+ def get_layout(id)
119
+ @client.layout id
120
+ end
121
+
122
+ def get_layout_asset(id)
123
+ @client.layout_asset id
124
+ end
125
+
126
+ def update_layout(id, data)
127
+ @client.update_layout(id, body: data)
128
+ end
129
+
130
+ def update_layout_asset(id, data)
131
+ @client.update_layout_asset(id, data: data)
132
+ end
133
+
134
+ def valid?(item)
135
+ if item.is_a? String
136
+ begin
137
+ valid? JSON.parse(item)
138
+ rescue
139
+ false
140
+ end
141
+ else
142
+ if item.is_a? Array
143
+ item.each do |subitem|
144
+ return valid? subitem
145
+ end
146
+ else
147
+ if item.respond_to?('[]') && item.respond_to?('key?')
148
+ return ( %w(title content_type component).map do |key|
149
+ (item.key? key) || (item.key? key.to_sym)
150
+ end.all? || %w(asset_type content_type filename).map do |key|
151
+ (item.key? key) || (item.key? key.to_sym)
152
+ end.all?)
153
+ else
154
+ return ( %i(title content_type component).map do |key|
155
+ item.respond_to?(key)
156
+ end.all? || %i(asset_type content_type filename).map do |key|
157
+ item.respond_to?(key)
158
+ end.all?)
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ def generate_local_manifest(verbose=false, silent=false)
165
+ unless %w(layouts components).map { |f| Dir.exists? f }.all?
166
+ @notifier.error 'Cannot find any local layout files! (See `kit help init`)'
167
+ return false
168
+ end
169
+
170
+ begin
171
+ @old_manifest = JSON.parse(File.read('manifest.json', :encoding => 'UTF-8')).to_h if File.exists? 'manifest.json'
172
+ rescue JSON::ParserError => e
173
+ @notifier.error "Invalid JSON in current manifest file!"
174
+ @notifier.newline
175
+ end
176
+
177
+ @notifier.info 'Reading local files...'
178
+ layouts_dir = Dir.new('layouts')
179
+ layouts = layouts_dir.entries.select do |file|
180
+ file =~ /(.*)\.tpl/
181
+ end
182
+ layouts = layouts.map do |l|
183
+ attrs = {
184
+ "content_type" => "page",
185
+ "component" => false,
186
+ "file" => "layouts/#{l}",
187
+ "layout_name" => "page_default",
188
+ "title" => l.split(".").first.gsub('_', " ").capitalize
189
+ }
190
+ if @old_manifest && @old_manifest.fetch('layouts')
191
+ old_layout = @old_manifest.fetch('layouts').reject(&:nil?).select { |ol| ol.fetch('file').include? l}.first || {}
192
+ attrs.merge! old_layout
193
+ end
194
+ attrs
195
+ end
196
+ components_dir = Dir.new('components')
197
+ components = components_dir.entries.select do |file|
198
+ file =~/(.*)\.tpl/
199
+ end
200
+ components = components.map do |c|
201
+ name = c.split(".").first.gsub('_', ' ')
202
+ attrs = {
203
+ "content_type" => "component",
204
+ "component" => true,
205
+ "file" => "components/#{c}",
206
+ "layout_name" => name,
207
+ "title" => name
208
+ }
209
+ if @old_manifest && @old_manifest.fetch('layouts')
210
+ old_component = @old_manifest.fetch('layouts').reject(&:nil?).select { |ol| ol.fetch('file').include? c}.first || {}
211
+ attrs.merge! old_component
212
+ end
213
+ attrs
214
+ end
215
+ assets = []
216
+ asset_dirs = %w(assets images javascripts stylesheets)
217
+ asset_dirs.each do |dir|
218
+ next unless Dir.exists? dir
219
+ current_dir = Dir.new(dir)
220
+ current_dir.entries.each do |file|
221
+ next if file =~ /^\.\.?$/
222
+ attrs = {
223
+ "content_type" => begin
224
+ MIME::Types.type_for(file).first.content_type
225
+ rescue
226
+ 'text/unknown'
227
+ end,
228
+ "file" => "#{dir}/#{file}",
229
+ "kind" => dir,
230
+ "filename" => file
231
+ }
232
+ if @old_manifest && @old_manifest.fetch('assets')
233
+ old_asset = @old_manifest.fetch('assets').reject(&:nil?).select { |ol| ol.fetch('file').include? file}.first || {}
234
+ attrs.merge! old_asset
235
+ end
236
+ assets << attrs
237
+ end
238
+ end
239
+ manifest = {
240
+ "description" => "New design",
241
+ "name" => "New design",
242
+ "preview_medium" => "",
243
+ "preview_small" => "",
244
+ "author" => "",
245
+ "layouts" => layouts + components,
246
+ "assets" => assets
247
+ }
248
+ if @old_manifest
249
+ old_meta = @old_manifest.tap{ |m| m.delete("assets") }.tap{ |m| m.delete("layouts") }
250
+ manifest.merge! old_meta
251
+ end
252
+ @notifier.newline
253
+ @notifier.info 'Writing layout files to new manifest.json file...'
254
+ write_manifest(manifest)
255
+ @notifier.success 'Done!'
256
+ @notifier.newline
257
+ return true
258
+ end
259
+
260
+ def generate_remote_manifest
261
+ generate_manifest get_layouts, get_layout_assets
262
+ end
263
+
264
+ def generate_manifest(layouts = nil, layout_assets = nil)
265
+ layouts = layouts || get_layouts
266
+ layout_assets = layout_assets || get_layout_assets
267
+
268
+ unless (layouts && layout_assets && !layouts.empty? && !layout_assets.empty?)
269
+ @notifier.error 'No remote layouts found to generate manifest from!'
270
+ @notifier.newline
271
+ return false
272
+ end
273
+
274
+ unless valid?(layouts) && valid?(layout_assets)
275
+ @notifier.error 'No valid layouts found to generate manifest from!'
276
+ @notifier.newline
277
+ return false
278
+ end
279
+
280
+ @notifier.info 'Writing remote layouts to new manifest.json file...'
281
+ manifest = Hash.new
282
+ manifest[:layouts] = layouts.inject(Array.new) do |memo, l|
283
+ memo << {
284
+ title: l.title,
285
+ layout_name: l.title.gsub(/[^\w\.\-]/, '_').downcase,
286
+ content_type: l.content_type,
287
+ component: l.component,
288
+ file: "#{(l.component ? 'components' : 'layouts')}/#{l.title.gsub(/[^\w\.\-]/, '_').downcase}.tpl"
289
+ }
290
+ end
291
+
292
+ manifest[:assets] = layout_assets.inject(Array.new) do |memo, a|
293
+ folder = if %w(unknown font).include? a.asset_type
294
+ "assets"
295
+ else
296
+ "#{a.asset_type}s"
297
+ end
298
+ memo << {
299
+ kind: a.asset_type,
300
+ filename: a.filename,
301
+ file: "#{folder}/#{a.filename}",
302
+ content_type: a.content_type
303
+ }
304
+ end
305
+
306
+ write_manifest(manifest)
307
+ @notifier.success 'Done!'
308
+ @notifier.newline
309
+ end
310
+
311
+ def create_folders
312
+ @notifier.newline
313
+ @notifier.info 'Creating folder structure...'
314
+ folders = %w(stylesheets images assets javascripts components layouts)
315
+ folders.each { |folder| Dir.mkdir(folder) unless Dir.exists?(folder) }
316
+ @notifier.success 'Done!'
317
+ @notifier.newline
318
+ end
319
+
320
+ def create_files(layouts = nil, layout_assets = nil)
321
+ if layouts.nil? && layout_assets.nil?
322
+ layouts = get_layouts
323
+ layout_assets = get_layout_assets
324
+ end
325
+ create_layouts(layouts.map(&:id))
326
+ create_assets(layout_assets.map(&:id))
327
+ end
328
+
329
+ def create_assets(ids)
330
+ @notifier.info "Creating assets#{'...' if @verbose}"
331
+ ids.uniq.each do |id|
332
+ create_asset(get_layout_asset id)
333
+ end
334
+ @notifier.newline if @verbose
335
+ @notifier.success 'Done!'
336
+ @notifier.newline
337
+ end
338
+
339
+ def create_asset(asset = nil)
340
+ valid = asset && asset.respond_to?(:asset_type) \
341
+ && asset.respond_to?(:filename) \
342
+ && asset.respond_to?(:asset_type) \
343
+ && (asset.respond_to?(:public_url) || asset.respond_to?(:data))
344
+
345
+ folder_names = {
346
+ 'image' => 'images',
347
+ 'stylesheet' => 'stylesheets',
348
+ 'javascript' => 'javascripts',
349
+ 'font' => 'assets',
350
+ 'unknown' => 'assets'
351
+ }
352
+
353
+ if valid
354
+ folder = folder_names.fetch(asset.asset_type, 'assets')
355
+
356
+ Dir.mkdir(folder) unless Dir.exists?(folder)
357
+ Dir.chdir(folder)
358
+
359
+ overwritten = File.exists? asset.filename
360
+
361
+ if @verbose
362
+ @notifier.newline
363
+ if overwritten
364
+ @notifier.warning " + #{folder}/#{asset.filename}"
365
+ else
366
+ @notifier.success " + #{folder}/#{asset.filename}"
367
+ end
368
+ else
369
+ @notifier.success '.'
370
+ end
371
+
372
+ if %w(stylesheet javascript).include? asset.asset_type
373
+ open(asset.filename, 'wb') { |file| file.write(asset.data) }
374
+ else
375
+ url = URI(asset.public_url)
376
+ Net::HTTP.start(url.hostname) do |http|
377
+ resp = http.get(url.path)
378
+ open(asset.filename, 'wb') { |file| file.write(resp.body) }
379
+ end
380
+ end
381
+ Dir.chdir('..')
382
+ else
383
+ unless @verbose
384
+ @notifier.error '!'
385
+ end
386
+ end
387
+ end
388
+
389
+ def create_layouts(ids)
390
+ @notifier.info "Creating layouts#{'...' if @verbose}"
391
+ ids.each do |id|
392
+ create_layout get_layout id
393
+ end
394
+ @notifier.newline if @verbose
395
+ @notifier.success 'Done!'
396
+ @notifier.newline
397
+ end
398
+
399
+ def create_layout(layout = nil)
400
+ valid = layout &&
401
+ layout.respond_to?(:component) &&
402
+ layout.respond_to?(:title) &&
403
+ layout.respond_to?(:body)
404
+
405
+ if valid
406
+ folder = layout.component ? 'components' : 'layouts'
407
+ filename = "#{layout.title.gsub(/[^\w\.\-]/, '_').downcase}.tpl"
408
+ Dir.chdir(folder)
409
+ overwritten = File.exists? filename
410
+
411
+ if @verbose
412
+ @notifier.newline
413
+ if overwritten
414
+ @notifier.warning " + #{folder}/#{filename}"
415
+ else
416
+ @notifier.success " + #{folder}/#{filename}"
417
+ end
418
+ else
419
+ @notifier.success '.'
420
+ end
421
+
422
+ File.open(filename, 'w') { |file| file.write layout.body }
423
+
424
+ Dir.chdir('..')
425
+ else
426
+ unless @verbose
427
+ @notifier.error '!'
428
+ end
429
+ end
430
+ end
431
+
432
+ def check
433
+ ok_char = "."
434
+ not_ok_char = "!"
435
+ @notifier.info "Checking manifest.json..."
436
+
437
+ # Check for manifest
438
+ if File.exists? 'manifest.json'
439
+ @manifest = read_manifest
440
+ @notifier.success 'OK!'
441
+ else
442
+ @notifier.error 'Manifest file not found! Use the \'manifest\' command to generate one.'
443
+ return false
444
+ end
445
+
446
+ # Check for files in manifest
447
+ layouts = @manifest.fetch('layouts', [])
448
+ missing_layouts = %w()
449
+
450
+ @notifier.newline
451
+ @notifier.info "Checking layouts and components"
452
+ layouts.reject(&:nil?).each do |layout|
453
+ if File.exists? layout['file']
454
+ @notifier.success ok_char
455
+ else
456
+ missing_layouts << layout['file']
457
+ @notifier.error not_ok_char
458
+ end
459
+ end
460
+
461
+ if missing_layouts.count > 0
462
+ @notifier.error " (#{missing_layouts.count} missing)"
463
+ @notifier.newline
464
+ missing_layouts.each do |a|
465
+ @notifier.normal " #{a}"
466
+ @notifier.newline
467
+ end if @verbose
468
+ else
469
+ @notifier.success 'OK!'
470
+ end
471
+
472
+ assets = @manifest['assets']
473
+ missing_assets = %w()
474
+
475
+ @notifier.newline
476
+ @notifier.info "Checking assets"
477
+ assets.reject(&:nil?).each do |asset|
478
+ if File.exists? asset['file']
479
+ @notifier.success ok_char
480
+ else
481
+ missing_assets << asset['file']
482
+ @notifier.error not_ok_char
483
+ end
484
+ end
485
+
486
+ if missing_assets.count > 0
487
+ @notifier.error " (#{missing_assets.count} missing)"
488
+ @notifier.newline
489
+ missing_assets.each do |a|
490
+ @notifier.normal " #{a}"
491
+ @notifier.newline
492
+ end if @verbose
493
+ else
494
+ @notifier.success 'OK!'
495
+ end
496
+ end
497
+
498
+ def fetch_boilerplate(dst='tmp')
499
+ @notifier.info 'Fetching design boilerplate...'
500
+ @notifier.newline
501
+
502
+ FileUtils.rm_r 'tmp' if Dir.exists? 'tmp'
503
+
504
+ begin
505
+ Git.clone 'git@github.com:Edicy/design-boilerplate.git', dst
506
+ rescue
507
+ @notifier.error 'An error occurred!'
508
+ return false
509
+ end
510
+
511
+ if Dir.exists? 'tmp'
512
+ Dir.chdir 'tmp'
513
+ @notifier.info 'Copying boilerplate files to working directory...'
514
+ @notifier.newline
515
+ Dir.new('.').entries.each do |f|
516
+ unless f =~ /^\..*$/
517
+ if Dir.exists?('../' + f) || File.exists?('../' + f)
518
+ FileUtils.rm_r '../' + f
519
+ end
520
+ FileUtils.mv f, '..'
521
+ end
522
+ end
523
+ Dir.chdir '..'
524
+ FileUtils.rm_r 'tmp'
525
+ end
526
+ @notifier.success 'Done!'
527
+ @notifier.newline
528
+ return true
529
+ end
530
+
531
+ # Returns filename=>id hash for layout files
532
+ def layout_id_map(layouts=nil)
533
+ layouts ||= get_layouts
534
+ remote_layouts = layouts.inject(Hash.new) do |memo, l|
535
+ memo[l.title.downcase] = l.id
536
+ memo
537
+ end
538
+
539
+ @manifest = read_manifest
540
+ fail "Manifest not found! (See `kit help push` for more info)".red unless @manifest
541
+ layouts = @manifest.fetch('layouts').reject(&:nil?)
542
+ layouts.inject(Hash.new) do |memo, l|
543
+ remote_exists = remote_layouts.key?(l.fetch('title').downcase)
544
+ memo[l.fetch('file')] = remote_layouts.fetch(l.fetch('title').downcase, nil) if remote_exists
545
+ memo
546
+ end
547
+ end
548
+
549
+ # Returns filename=>id hash for layout assets
550
+ def layout_asset_id_map(assets=nil)
551
+ assets ||= get_layout_assets
552
+ assets.inject(Hash.new) do |memo, a|
553
+ memo[a.public_url.gsub("http://#{@client.host}/", '')] = a.id
554
+ memo
555
+ end
556
+ end
557
+
558
+ def upload_files(files)
559
+ if files.length == 0
560
+ @notifier.info "Pushing all files...\n"
561
+ files = %w(layouts components stylesheets javascripts images assets)
562
+ end
563
+
564
+ layout_assets = layout_asset_id_map
565
+ layouts = layout_id_map
566
+
567
+ # Find if provided file is a directory instead
568
+ files.each_with_index do |file, index|
569
+ next if file.is_a? Array
570
+ if Dir.exists? file
571
+ subfiles = Dir.new(file).entries.reject{|e| e =~ /^(\.|\.\.)$/ } # Keep only normal subfiles
572
+ subfiles.map!{ |subfile| subfile = "#{file[/[^\/]*/]}/#{subfile}"} # Prepend folder name
573
+ files[index] = subfiles # Insert as Array so sub-subfolders won't get processed again
574
+ end
575
+ end
576
+ files.flatten! # If every folder is processed, flatten the array
577
+
578
+ @manifest = read_manifest
579
+ local_layouts = @manifest.fetch('layouts', []).reject(&:nil?).map{ |l| l.fetch('file','') }
580
+ local_assets = @manifest.fetch('assets', []).reject(&:nil?).map{ |a| a.fetch('file','') }
581
+
582
+ files.each_with_index do |file, index|
583
+ @notifier.newline if index > 0
584
+ if File.exist?(file)
585
+ if uploadable?(file)
586
+ if file =~ /^(layout|component)s\/[^\s\/]+\.tpl$/ # if layout/component
587
+ if local_layouts.include?(file)
588
+ if layouts.key?(file)
589
+ @notifier.info "Updating layout file #{file}..."
590
+ if update_layout(layouts[file], File.read(file, :encoding => 'UTF-8'))
591
+ @notifier.success 'OK!'
592
+ else
593
+ @notifier.error "Cannot update layout file #{file}!"
594
+ end
595
+ else
596
+ @notifier.warning "Remote file #{file} not found!"
597
+ @notifier.info "\nTrying to create layout file #{file}..."
598
+ if create_remote_layout(file)
599
+ @notifier.success 'OK!'
600
+ else
601
+ @notifier.error "Unable to create layout file #{file}!"
602
+ end
603
+ end
604
+ else
605
+ @notifier.warning "Layout file #{file} not found in manifest! Skipping."
606
+ end
607
+ elsif file =~ /^(asset|image|stylesheet|javascript)s\/[^\s\/]+\..+$/ # if other asset
608
+ if local_assets.include? file
609
+ if layout_assets.key? file
610
+ if is_editable?(file)
611
+ @notifier.info "Updating layout asset file #{file}..."
612
+ if update_layout_asset(layout_assets[file], File.read(file, :encoding => 'UTF-8'))
613
+ @notifier.success 'OK!'
614
+ else
615
+ @notifier.error "Unable to update file #{file}!"
616
+ end
617
+ else
618
+ @notifier.warning "Not allowed to update file #{file}! Skipping."
619
+ end
620
+ else
621
+ @notifier.warning "Remote file #{file} not found!"
622
+ @notifier.info "\nTrying to create file #{file}..."
623
+ if create_remote_file(file)
624
+ @notifier.success 'OK!'
625
+ else
626
+ @notifier.error "Unable to create file #{file}!"
627
+ end
628
+ end
629
+ else
630
+ @notifier.warning "Asset file #{file} not found in manifest! Skipping."
631
+ end
632
+ elsif Dir.exists? file
633
+ @notifier.warning "Not allowed to push subfolder #{file}!"
634
+ else
635
+ @notifier.warning "Not allowed to push file #{file}!"
636
+ end
637
+ else
638
+ @notifier.error "Cannot upload file #{file}!"
639
+ end
640
+ else
641
+ @notifier.error "File #{file} not found!"
642
+ end
643
+ end
644
+ @notifier.newline
645
+ end
646
+
647
+ def is_editable?(file)
648
+ folder = file.split('/').first
649
+ extension = file.split('/').last.split('.').last
650
+
651
+ (%w(stylesheets javascripts).include? folder) && (%w(js css).include? extension)
652
+ end
653
+
654
+ def content_type_for(file)
655
+ folder = file.split('/').first
656
+ if is_editable?(file)
657
+ if folder == 'stylesheets'
658
+ 'text/css'
659
+ elsif folder == 'javascripts'
660
+ 'text/javascript'
661
+ end
662
+ else
663
+ if folder == 'images'
664
+ "image/#{file.split("/").last.split(".").last}"
665
+ elsif folder == 'assets'
666
+ 'unknown/unknown'
667
+ end
668
+ end
669
+ end
670
+
671
+ def create_remote_layout(file)
672
+ @manifest = read_manifest if File.exists? 'manifest.json'
673
+ layouts = @manifest.fetch('layouts', []).reject(&:nil?)
674
+ layout = layouts.select { |l| file == l.fetch('file') }.first
675
+
676
+ if @manifest && layouts && layout
677
+ data = {
678
+ title: layout.fetch('title'),
679
+ content_type: layout.fetch('content_type'),
680
+ component: layout.fetch('component'),
681
+ body: File.exists?(layout.fetch('file')) ? File.read(layout.fetch('file'), :encoding => 'UTF-8') : ''
682
+ }
683
+ else
684
+ name = file.split('/').last.split('.').first
685
+ component = (file.split('/').first =~ /^layouts$/).nil?
686
+ body = File.read(file, :encoding => 'UTF-8')
687
+ data = {
688
+ title: component ? name : name.capitalize,
689
+ content_type: 'page',
690
+ component: component,
691
+ body: body
692
+ }
693
+ end
694
+
695
+ @client.create_layout(data)
696
+ end
697
+
698
+ def create_remote_file(file)
699
+ data = {
700
+ filename: file.split('/').last,
701
+ content_type: content_type_for(file)
702
+ }
703
+
704
+ if is_editable?(file)
705
+ data[:data] = File.read(file, :encoding => 'UTF-8')
706
+ else
707
+ data[:file] = file
708
+ end
709
+
710
+ @client.create_layout_asset(data)
711
+ end
712
+
713
+ def uploadable?(file)
714
+ if file.is_a? String
715
+ !(file =~ /^(component|layout|image|asset|javascript|stylesheet)s\/([^\s]+)/).nil?
716
+ else
717
+ begin
718
+ uploadable? file.try(:to_s)
719
+ rescue
720
+ fail "Cannot upload file '#{file}'!".red
721
+ end
722
+ end
723
+ end
724
+
725
+ def find_layouts(names)
726
+ layouts = get_layouts
727
+ @manifest = read_manifest if File.exist? 'manifest.json'
728
+ results = []
729
+
730
+ names.each do |name|
731
+ name = name.split('/').last.split('.').first
732
+ if @manifest
733
+ layout = @manifest.fetch('layouts', []).reject(&:nil?).find{ |l| l['file'].split('/').last.split('.').first == name }
734
+ if layout # layout file is in manifest
735
+ layout = layouts.find{ |l| l.title == layout['title'] }
736
+ else # not found in manifest
737
+ layout = layouts.find{ |l| l.title == name }
738
+ end
739
+ id = layout.id if layout
740
+ else
741
+ layout = layouts.find{ |l| l.title.gsub(/[^\w\.]/, '_').downcase == name}
742
+ id = layout['id'] if layout
743
+ end
744
+ results << id if id
745
+ end
746
+
747
+ results
748
+ end
749
+
750
+ def find_assets(names)
751
+ assets = get_layout_assets
752
+ results = []
753
+ names.each do |name|
754
+ name = name.split('/').last
755
+ layout = assets.find{ |l| l.filename == name }
756
+ results << layout.id if layout
757
+ end
758
+ results
759
+ end
760
+
761
+ def pull_files(names)
762
+ # TODO: pull whole folders, e.g "pull images"
763
+ layout_ids = find_layouts(names)
764
+ asset_ids = find_assets(names)
765
+
766
+ found = layout_ids.length + asset_ids.length
767
+ if found > 0 && found < names.length
768
+ @notifier.warning "Unable to find some specified files!"
769
+ @notifier.newline
770
+ ret = true
771
+ elsif found == names.length
772
+ ret = true
773
+ elsif found == 0
774
+ @notifier.error "Unable to find any specified files!"
775
+ ret = false
776
+ end
777
+
778
+ create_layouts(layout_ids) unless layout_ids.empty?
779
+ create_assets(asset_ids) unless asset_ids.empty?
780
+
781
+ ret
782
+ end
783
+ end
784
+ end