fronde 0.4.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ext/nil_time.rb +3 -6
  3. data/lib/ext/time.rb +10 -17
  4. data/lib/ext/time_no_time.rb +27 -0
  5. data/lib/fronde/cli/commands.rb +18 -14
  6. data/lib/fronde/cli/data/fish_completion +20 -0
  7. data/lib/fronde/cli/data/gitignore +0 -1
  8. data/lib/fronde/cli/helpers.rb +0 -2
  9. data/lib/fronde/cli/opt_parse.rb +15 -18
  10. data/lib/fronde/cli/throbber.rb +35 -18
  11. data/lib/fronde/cli.rb +4 -3
  12. data/lib/fronde/config/data/org-config.el +3 -2
  13. data/lib/fronde/config/data/ox-fronde.el +91 -46
  14. data/lib/fronde/config/data/themes/umaneti/css/htmlize.css +364 -0
  15. data/lib/fronde/config/data/themes/umaneti/css/style.css +250 -0
  16. data/lib/fronde/config/data/themes/umaneti/img/bottom.png +0 -0
  17. data/lib/fronde/config/data/themes/umaneti/img/content.png +0 -0
  18. data/lib/fronde/config/data/themes/umaneti/img/tic.png +0 -0
  19. data/lib/fronde/config/data/themes/umaneti/img/top.png +0 -0
  20. data/lib/fronde/config/helpers.rb +1 -19
  21. data/lib/fronde/config/lisp.rb +14 -7
  22. data/lib/fronde/config.rb +47 -31
  23. data/lib/fronde/emacs.rb +23 -9
  24. data/lib/fronde/index/atom_generator.rb +1 -1
  25. data/lib/fronde/index/data/all_tags.org +6 -1
  26. data/lib/fronde/index/data/template.org +8 -4
  27. data/lib/fronde/index/org_generator.rb +10 -6
  28. data/lib/fronde/index.rb +19 -17
  29. data/lib/fronde/org/file.rb +71 -39
  30. data/lib/fronde/org/file_extracter.rb +23 -12
  31. data/lib/fronde/org.rb +14 -12
  32. data/lib/fronde/slug.rb +39 -12
  33. data/lib/fronde/source/gemini.rb +4 -9
  34. data/lib/fronde/source/html.rb +9 -9
  35. data/lib/fronde/source.rb +17 -12
  36. data/lib/fronde/sync/neocities.rb +220 -0
  37. data/lib/fronde/sync/rsync.rb +46 -0
  38. data/lib/fronde/sync.rb +32 -0
  39. data/lib/fronde/templater.rb +35 -51
  40. data/lib/fronde/version.rb +1 -1
  41. data/lib/tasks/cli.rake +45 -13
  42. data/lib/tasks/org.rake +30 -35
  43. data/lib/tasks/site.rake +63 -41
  44. data/lib/tasks/sync.rake +19 -50
  45. data/lib/tasks/tags.rake +2 -2
  46. data/locales/en.yml +143 -81
  47. data/locales/fr.yml +153 -89
  48. metadata +56 -17
  49. data/lib/ext/r18n.rb +0 -17
data/lib/fronde/org.rb CHANGED
@@ -10,7 +10,7 @@ module Fronde
10
10
  class << self
11
11
  def current_version
12
12
  # Do not crash if Org is not yet installed (and thus return nil)
13
- Dir['lib/org-*'].first&.delete_prefix('lib/org-')
13
+ Dir.glob('lib/org-*').first&.delete_prefix('lib/org-')
14
14
  end
15
15
 
16
16
  # Fetch and return the last published version of Org.
@@ -55,23 +55,25 @@ module Fronde
55
55
  # @param destination [String] where to save the org-mode tarball
56
56
  # @return [String] the downloaded org-mode version
57
57
  def download(destination = 'var/tmp')
58
- # Remove version number in dest file to allow easy rake file
59
- # task naming
60
- dest_file = ::File.expand_path('org.tar.gz', destination)
61
58
  org_last_version = last_version(force: false, cookie_dir: destination)
62
59
  tarball = "org-mode-release_#{org_last_version}.tar.gz"
63
60
  uri = URI("https://git.savannah.gnu.org/cgit/emacs/org-mode.git/snapshot/#{tarball}")
64
61
  # Will crash on purpose if anything goes wrong
65
62
  Net::HTTP.start(uri.host) do |http|
66
- request = Net::HTTP::Get.new uri
63
+ fetch_org_tarball http, Net::HTTP::Get.new(uri), destination
64
+ end
65
+ org_last_version
66
+ end
67
67
 
68
- http.request request do |response|
69
- ::File.open(dest_file, 'w') do |io|
70
- response.read_body { |chunk| io.write chunk }
71
- end
68
+ def fetch_org_tarball(http, request, destination)
69
+ # Remove version number in dest file to allow easy rake file
70
+ # task naming
71
+ dest_file = ::File.expand_path('org.tar.gz', destination)
72
+ http.request request do |response|
73
+ ::File.open(dest_file, 'w') do |io|
74
+ response.read_body { |chunk| io.write chunk }
72
75
  end
73
76
  end
74
- org_last_version
75
77
  end
76
78
 
77
79
  def make_org_cmd(org_dir, target, verbose: false)
@@ -95,8 +97,8 @@ module Fronde
95
97
  FileUtils.mv "org-mode-release_#{version}", target
96
98
  # Fix a weird unknown package version
97
99
  ::File.write("#{target}/mk/version.mk", "ORGVERSION ?= #{version}")
98
- system(*make_org_cmd(target, 'compile', verbose: verbose))
99
- system(*make_org_cmd(target, 'autoloads', verbose: verbose))
100
+ system(*make_org_cmd(target, 'compile', verbose:))
101
+ system(*make_org_cmd(target, 'autoloads', verbose:))
100
102
  end
101
103
  end
102
104
  end
data/lib/fronde/slug.rb CHANGED
@@ -5,23 +5,50 @@ module Fronde
5
5
  module Slug
6
6
  class << self
7
7
  def slug(title)
8
- title.downcase.tr(' ', '-')
8
+ title.downcase
9
9
  .encode('ascii', fallback: ->(k) { translit(k) })
10
- .gsub(/[^\w-]/, '').delete_suffix('-')
10
+ .encode('utf-8') # Convert back to utf-8 string
11
+ .gsub(/[^\w-]/, '-')
12
+ .squeeze('-')
13
+ .delete_suffix('-')
11
14
  end
12
15
 
16
+ # rubocop:disable Metrics/CyclomaticComplexity
17
+ # rubocop:disable Metrics/MethodLength
13
18
  def translit(char)
14
- return 'a' if %w[á à â ä ǎ ã å].include?(char)
15
- return 'e' if %w[é è ê ë ě ẽ].include?(char)
16
- return 'i' if %w[í ì î ï ǐ ĩ].include?(char)
17
- return 'o' if %w[ó ò ô ö ǒ õ].include?(char)
18
- return 'u' if %w[ú ù û ü ǔ ũ].include?(char)
19
- return 'y' if %w[ý ŷ ÿ ỹ].include?(char)
20
- return 'c' if char == 'ç'
21
- return 'n' if char == 'ñ'
22
-
23
- '-'
19
+ case char
20
+ when 'á', 'à', 'â', 'ä', 'ǎ', 'ã', 'å'
21
+ 'a'
22
+ when 'é', 'è', 'ê', 'ë', 'ě', 'ẽ', '€'
23
+ 'e'
24
+ when 'í', 'ì', 'î', 'ï', 'ǐ', 'ĩ'
25
+ 'i'
26
+ when 'ó', 'ò', 'ô', 'ö', 'ǒ', 'õ', 'ø'
27
+ 'o'
28
+ when 'ú', 'ù', 'û', 'ü', 'ǔ', 'ũ'
29
+ 'u'
30
+ when 'ý', 'ỳ', 'ŷ', 'ÿ', 'ỹ'
31
+ 'y'
32
+ when 'ç', '©', '🄯'
33
+ 'c'
34
+ when 'ñ'
35
+ 'n'
36
+ when 'ß'
37
+ 'ss'
38
+ when 'œ'
39
+ 'oe'
40
+ when 'æ'
41
+ 'ae'
42
+ when '®'
43
+ 'r'
44
+ when '™'
45
+ 'tm'
46
+ else
47
+ '-'
48
+ end
24
49
  end
50
+ # rubocop:enable Metrics/CyclomaticComplexity
51
+ # rubocop:enable Metrics/MethodLength
25
52
  end
26
53
  end
27
54
  end
@@ -4,18 +4,13 @@ module Fronde
4
4
  class Source
5
5
  # Specific settings for Gemini {Fronde::Source}
6
6
  class Gemini < Source
7
- def blog?
8
- # TODO: See how to support blog/indexes with gemini
9
- false
10
- end
11
-
12
7
  class << self
13
8
  def org_default_postamble
14
9
  format(
15
10
  "📅 %<date>s\n📝 %<author>s %<creator>s",
16
- author: R18n.t.fronde.org.postamble.written_by,
17
- creator: R18n.t.fronde.org.postamble.with_emacs,
18
- date: R18n.t.fronde.org.postamble.last_modification
11
+ author: I18n.t('fronde.org.postamble.written_by'),
12
+ creator: I18n.t('fronde.org.postamble.with_emacs'),
13
+ date: I18n.t('fronde.org.postamble.last_modification')
19
14
  )
20
15
  end
21
16
  end
@@ -25,7 +20,7 @@ module Fronde
25
20
  def fill_in_specific_config
26
21
  @config.merge!(
27
22
  'type' => 'gemini', 'ext' => '.gmi', 'mime_type' => 'text/gemini',
28
- 'folder' => CONFIG.get('gemini_public_folder')
23
+ 'folder' => File.expand_path(CONFIG.get('gemini_public_folder'))
29
24
  )
30
25
  end
31
26
 
@@ -13,9 +13,9 @@ module Fronde
13
13
  class << self
14
14
  def org_default_postamble
15
15
  <<~POSTAMBLE
16
- <p><span class="author">#{R18n.t.fronde.org.postamble.written_by}</span>
17
- #{R18n.t.fronde.org.postamble.with_emacs_html}</p>
18
- <p class="date">#{R18n.t.fronde.org.postamble.last_modification}</p>
16
+ <p><span class="author">#{I18n.t('fronde.org.postamble.written_by')}</span>
17
+ #{I18n.t('fronde.org.postamble.with_emacs_html')}</p>
18
+ <p class="date">#{I18n.t('fronde.org.postamble.last_modification')}</p>
19
19
  <p class="validation">%v</p>
20
20
  POSTAMBLE
21
21
  end
@@ -26,7 +26,7 @@ module Fronde
26
26
  def fill_in_specific_config
27
27
  @config.merge!(
28
28
  'type' => 'html', 'ext' => '.html', 'mime_type' => 'text/html',
29
- 'folder' => CONFIG.get('html_public_folder')
29
+ 'folder' => File.expand_path(CONFIG.get('html_public_folder'))
30
30
  )
31
31
  end
32
32
 
@@ -40,12 +40,12 @@ module Fronde
40
40
  super
41
41
  end
42
42
 
43
- def org_default_options # rubocop:disable Metrics/MethodLength
43
+ def org_default_options
44
44
  defaults = {
45
45
  'publishing-function' => 'org-html-publish-to-html',
46
46
  'html-head-include-default-style' => 't',
47
47
  'html-head-include-scripts' => 't',
48
- 'html-head' => '{{ atom_feed }}',
48
+ 'html-head' => '%F',
49
49
  'html-postamble' => Html.org_default_postamble
50
50
  }
51
51
  return defaults if @config['theme'] == 'default'
@@ -55,10 +55,10 @@ module Fronde
55
55
  'html-head-include-scripts' => 'nil',
56
56
  'html-head' => <<~HTMLHEAD
57
57
  <link rel="stylesheet" type="text/css" media="screen"
58
- href="{{ domain }}/assets/{{ theme }}/css/style.css">
58
+ href="%h/assets/%o/css/style.css">
59
59
  <link rel="stylesheet" type="text/css" media="screen"
60
- href="{{ domain }}/assets/{{ theme }}/css/htmlize.css">
61
- {{ atom_feed }}
60
+ href="%h/assets/%o/css/htmlize.css">
61
+ %F
62
62
  HTMLHEAD
63
63
  )
64
64
  end
data/lib/fronde/source.rb CHANGED
@@ -58,11 +58,11 @@ module Fronde
58
58
  def source_for(file_name)
59
59
  relative_file_path = file_name.delete_prefix "#{publication_path}/"
60
60
  # file_name does not begin with source path.
61
- return nil if relative_file_path == file_name
61
+ return if relative_file_path == file_name
62
62
 
63
63
  # Looks like a file at a deeper level, but current source is not
64
64
  # recursive.
65
- return nil if relative_file_path.include?('/') && !recursive?
65
+ return if relative_file_path.include?('/') && !recursive?
66
66
 
67
67
  # Looks like a match. But does a source file for this one actually
68
68
  # exists?
@@ -70,7 +70,7 @@ module Fronde
70
70
  /#{@config['ext']}\z/, '.org'
71
71
  )
72
72
  source_path = File.join(@config['path'], relative_source_path)
73
- return nil unless File.file?(source_path)
73
+ return unless File.file?(source_path)
74
74
 
75
75
  source_path
76
76
  end
@@ -111,7 +111,7 @@ module Fronde
111
111
  def publication_path
112
112
  return @config['publication_path'] if @config['publication_path']
113
113
 
114
- publish_in = [File.expand_path(@config['folder']), @config['target']]
114
+ publish_in = [@config['folder'], @config['target']]
115
115
  @config['publication_path'] = publish_in.join('/').delete_suffix('/')
116
116
  end
117
117
 
@@ -147,7 +147,7 @@ module Fronde
147
147
 
148
148
  def clean_config
149
149
  fill_in_specific_config
150
- @config['name'] ||= @config['path'].sub(/^[.~]*\//, '').tr('/.', '-')
150
+ @config['name'] ||= @config['path'].sub(%r{^[.~]*/}, '').tr('/.', '-')
151
151
  @config['title'] ||= @config['path']
152
152
  @config['target'] ||= File.basename(@config['path']).delete_prefix '.'
153
153
  @config['target'] = '' if @config['target'] == '.'
@@ -168,12 +168,16 @@ module Fronde
168
168
  end
169
169
 
170
170
  def render_heading
171
- heading_key = "#{@config['type']}-head"
172
- heading = @config.dig 'org-options', heading_key
173
- @config['org-options'][heading_key] = \
174
- Config::Helpers.render_liquid_template(
175
- heading, to_h
176
- )
171
+ %w[head head-extra preamble postamble].each do |kind|
172
+ heading_key = "#{@config['type']}-#{kind}"
173
+ heading = @config.dig 'org-options', heading_key
174
+ next unless heading
175
+
176
+ @config['org-options'][heading_key] =
177
+ heading.gsub('%F', @config['atom_feed'])
178
+ .gsub('%h', @config['domain'])
179
+ .gsub('%o', @config['theme'])
180
+ end
177
181
  end
178
182
 
179
183
  def org_project_config
@@ -181,7 +185,8 @@ module Fronde
181
185
  'base-directory' => @config['path'],
182
186
  'base-extension' => 'org',
183
187
  'publishing-directory' => publication_path,
184
- 'recursive' => @config['recursive']
188
+ 'recursive' => @config['recursive'],
189
+ 'fronde-base-uri' => "#{@config['domain']}#{public_absolute_path}"
185
190
  }.merge(@config['org-options'])
186
191
  exclude = @config['exclude']
187
192
  attributes['exclude'] = exclude if exclude
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'time'
5
+ require 'yaml'
6
+ require 'net/http'
7
+ require 'fileutils'
8
+ require 'digest/sha1'
9
+
10
+ module Fronde
11
+ module Sync
12
+ # Everything needed to connect to neocities
13
+ class Neocities
14
+ PROTECTED_FILES = %w[index.html neocities.png not_found.html].freeze
15
+
16
+ def initialize(connection_spec, public_folder, verbose: false, &block)
17
+ @verbose = verbose
18
+ @endpoint = @website_name = @authorization = nil
19
+ extract_connection_details connection_spec
20
+ @public_folder = public_folder
21
+ @connection = init_connection
22
+ return unless block
23
+
24
+ yield self
25
+ finish
26
+ end
27
+
28
+ def remote_list
29
+ remote = call build_request('/list')
30
+ JSON.parse(remote.body)['files'].map do |stat|
31
+ stat['updated_at'] = Time.parse(stat['updated_at'])
32
+ stat
33
+ end
34
+ end
35
+
36
+ def local_list
37
+ Dir.chdir(@public_folder) do
38
+ Dir.glob('**/*').map { |file| neocities_stat(file) }
39
+ end
40
+ end
41
+
42
+ def info
43
+ info = call build_request('/info')
44
+ JSON.parse(info.body)['info']
45
+ end
46
+
47
+ def finish
48
+ @connection.finish if @connection.started?
49
+ end
50
+
51
+ def pull(test: false)
52
+ file_list = remote_list
53
+ finish
54
+ orphans = select_orphans(file_list, local_list) do |path|
55
+ puts I18n.t('fronde.neocities.deleting', path:) if @verbose
56
+
57
+ "#{@public_folder}/#{path}"
58
+ end
59
+ File.unlink(*orphans) unless test
60
+ download_all(file_list, test:)
61
+ nil # Mute this method
62
+ end
63
+
64
+ def push(test: false)
65
+ file_list = local_list
66
+ remove_remote_orphans(file_list, test:)
67
+ upload_all(file_list, test:)
68
+ finish
69
+ end
70
+
71
+ private
72
+
73
+ def neocities_stat(file)
74
+ stat = File.stat(file)
75
+ data = {
76
+ 'path' => file,
77
+ 'is_directory' => stat.directory?,
78
+ 'updated_at' => stat.mtime.round.utc
79
+ }
80
+ return data if data['is_directory']
81
+
82
+ data['size'] = stat.size
83
+ data['sha1_hash'] = Digest::SHA1.hexdigest File.read(file)
84
+ data
85
+ end
86
+
87
+ def select_orphans(to_apply, current_list, &)
88
+ paths_to_apply = to_apply.map { _1['path'] }
89
+ current_paths = current_list.map { _1['path'] }
90
+ (current_paths - paths_to_apply).filter_map(&)
91
+ end
92
+
93
+ def remove_remote_orphans(file_list, test: false)
94
+ request = build_request '/delete', :post
95
+ orphan_paths = select_orphans(file_list, remote_list) do |path|
96
+ # Never remove the following files. If needed you can still
97
+ # overwrite them. And in any case, neocities will not allow
98
+ # the index.html file to be removed.
99
+ next if PROTECTED_FILES.include? path
100
+
101
+ puts I18n.t('fronde.neocities.deleting', path:) if @verbose
102
+ path
103
+ end
104
+ request.form_data = { 'filenames[]' => orphan_paths }
105
+ return if test
106
+
107
+ call request
108
+ end
109
+
110
+ def download_all(file_list, test: false)
111
+ publish_domain = "#{@website_name}.#{@endpoint.host}"
112
+ Dir.chdir(@public_folder) do
113
+ Net::HTTP.start(publish_domain, use_ssl: true) do |http|
114
+ file_list.each do |file_data|
115
+ path = file_data['path']
116
+ file_data['uri'] = "https://#{publish_domain}/#{path}"
117
+ download_file http, file_data, test:
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ def download_file(http, file_data, test: false)
124
+ path = file_data['path']
125
+ if file_data['is_directory']
126
+ puts "#{path}/" if @verbose
127
+ FileUtils.mkdir_p path unless test
128
+ return
129
+ end
130
+
131
+ puts path if @verbose
132
+
133
+ content = fetch_file_content(
134
+ http, file_data['uri'], file_data['sha1_hash']
135
+ )
136
+ return unless content && !test
137
+
138
+ save_file path, content, file_data['updated_at']
139
+ end
140
+
141
+ def fetch_file_content(http, uri, sha1sum)
142
+ # Neocities redirect HTML file to location without extension and
143
+ # redirect index.html file to /
144
+ uri = uri.delete_suffix('index.html').delete_suffix('.html')
145
+ content = http.get(uri).body
146
+ check = Digest::SHA1.hexdigest content
147
+ return content if check == sha1sum
148
+
149
+ warn I18n.t('fronde.neocities.sha1_differ', uri:)
150
+ end
151
+
152
+ def save_file(path, content, updated_at)
153
+ File.write path, content
154
+ FileUtils.touch path, mtime: updated_at
155
+ path
156
+ end
157
+
158
+ def prepare_files_to_upload(file_list)
159
+ Dir.chdir(@public_folder) do
160
+ file_list.filter_map do |file_data|
161
+ # No need to push intermediary directories, they are created
162
+ # on the fly
163
+ next if file_data['is_directory']
164
+
165
+ path = file_data['path']
166
+ puts path if @verbose
167
+ [path, File.new(path)]
168
+ end
169
+ end
170
+ end
171
+
172
+ def upload_all(file_list, test: false)
173
+ form_data = prepare_files_to_upload file_list
174
+ return if test
175
+
176
+ request = build_request '/upload', :post
177
+ request.set_form form_data, 'multipart/form-data'
178
+ call request
179
+ end
180
+
181
+ def extract_connection_details(connection_spec)
182
+ # Do not put your password into the fronde config. The password
183
+ # is expectfed to be found in a specific config file (not to be
184
+ # shared).
185
+ @website_name, endpoint = connection_spec.split('@', 2)
186
+ endpoint ||= 'neocities.org'
187
+ @endpoint = URI("https://#{endpoint}/api")
188
+ # Will raise Errno::ENOENT if file does not exist
189
+ credentials = YAML.load_file('.credentials')
190
+ # Will raise KeyError if not set
191
+ password = credentials.fetch("#{@website_name}_neocities_pass")
192
+ authorization = [[@website_name, password].join(':')].pack('m0')
193
+ @authorization = "Basic #{authorization}"
194
+ end
195
+
196
+ def build_request(path, method = :get)
197
+ uri = @endpoint.dup
198
+ uri.path += path
199
+ klass = Kernel.const_get "Net::HTTP::#{method.to_s.capitalize}"
200
+ klass.new uri
201
+ end
202
+
203
+ def call(request)
204
+ request['Authorization'] = @authorization
205
+ @connection.start unless @connection.started?
206
+ outcome = @connection.request request
207
+ return outcome if outcome.is_a? Net::HTTPSuccess
208
+
209
+ raise JSON.parse(outcome.body).inspect
210
+ end
211
+
212
+ def init_connection
213
+ http = Net::HTTP.new @endpoint.host, 443
214
+ http.use_ssl = true
215
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
216
+ http
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../config'
4
+
5
+ module Fronde
6
+ module Sync
7
+ # Everything needed to push or pull data with rsync
8
+ class Rsync
9
+ def initialize(remote_path, local_path, verbose: false)
10
+ @verbose = verbose
11
+ @remote_path = remote_path
12
+ @local_path = "#{local_path}/"
13
+ end
14
+
15
+ def pull(test: false)
16
+ run command(test:) + [@remote_path, @local_path]
17
+ end
18
+
19
+ def push(test: false)
20
+ run command(test:) + [@local_path, @remote_path]
21
+ end
22
+
23
+ private
24
+
25
+ def run(cmd)
26
+ puts cmd.join(' ') if @verbose
27
+ # Be precise about Kernel to allow mock in rspec
28
+ Kernel.system(*cmd)
29
+ end
30
+
31
+ def command(test: false)
32
+ rsync_command = Fronde::CONFIG.get('rsync')
33
+ return rsync_command unless rsync_command.nil?
34
+
35
+ optstring = []
36
+ optstring << 'n' if test
37
+ if @verbose
38
+ optstring << 'v'
39
+ else
40
+ optstring << 'q'
41
+ end
42
+ ['rsync', "-#{optstring.join}rlt", '--delete']
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'config'
4
+ require_relative 'sync/rsync'
5
+ require_relative 'sync/neocities'
6
+
7
+ module Fronde
8
+ # Entrypoint for synchronization with remote public server
9
+ module Sync
10
+ class Error < ::StandardError; end
11
+
12
+ ALLOWED_SYNCER = %w[rsync neocities].freeze
13
+
14
+ def self.extract_method_and_remote(type)
15
+ remote_path = Fronde::CONFIG.get("#{type}_remote")
16
+ raise Error, "No #{type} remote path set" if remote_path.nil?
17
+
18
+ method, remote = remote_path.split(':', 2)
19
+ return [method, remote] if ALLOWED_SYNCER.include?(method)
20
+
21
+ ['rsync', remote_path]
22
+ end
23
+
24
+ def self.pull_or_push(direction, type, test: false, verbose: false)
25
+ method, remote_path = extract_method_and_remote type
26
+ public_folder = Fronde::CONFIG.get("#{type}_public_folder")
27
+ klass = Kernel.const_get("::Fronde::Sync::#{method.capitalize}")
28
+ syncer = klass.new(remote_path, public_folder, verbose:)
29
+ Thread.new { syncer.send(direction, test:) }
30
+ end
31
+ end
32
+ end