voog-kit 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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