fronde 0.4.0 → 0.6.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 +4 -4
- data/lib/ext/nil_time.rb +3 -6
- data/lib/ext/time.rb +10 -17
- data/lib/ext/time_no_time.rb +27 -0
- data/lib/fronde/cli/commands.rb +18 -14
- data/lib/fronde/cli/data/fish_completion +20 -0
- data/lib/fronde/cli/data/gitignore +0 -1
- data/lib/fronde/cli/helpers.rb +0 -2
- data/lib/fronde/cli/opt_parse.rb +15 -18
- data/lib/fronde/cli/throbber.rb +35 -18
- data/lib/fronde/cli.rb +4 -3
- data/lib/fronde/config/data/org-config.el +3 -2
- data/lib/fronde/config/data/ox-fronde.el +91 -46
- data/lib/fronde/config/data/themes/umaneti/css/htmlize.css +364 -0
- data/lib/fronde/config/data/themes/umaneti/css/style.css +250 -0
- data/lib/fronde/config/data/themes/umaneti/img/bottom.png +0 -0
- data/lib/fronde/config/data/themes/umaneti/img/content.png +0 -0
- data/lib/fronde/config/data/themes/umaneti/img/tic.png +0 -0
- data/lib/fronde/config/data/themes/umaneti/img/top.png +0 -0
- data/lib/fronde/config/helpers.rb +1 -19
- data/lib/fronde/config/lisp.rb +14 -7
- data/lib/fronde/config.rb +47 -31
- data/lib/fronde/emacs.rb +23 -9
- data/lib/fronde/index/atom_generator.rb +1 -1
- data/lib/fronde/index/data/all_tags.org +6 -1
- data/lib/fronde/index/data/template.org +8 -4
- data/lib/fronde/index/org_generator.rb +10 -6
- data/lib/fronde/index.rb +19 -17
- data/lib/fronde/org/file.rb +71 -39
- data/lib/fronde/org/file_extracter.rb +23 -12
- data/lib/fronde/org.rb +14 -12
- data/lib/fronde/slug.rb +39 -12
- data/lib/fronde/source/gemini.rb +4 -9
- data/lib/fronde/source/html.rb +9 -9
- data/lib/fronde/source.rb +17 -12
- data/lib/fronde/sync/neocities.rb +220 -0
- data/lib/fronde/sync/rsync.rb +46 -0
- data/lib/fronde/sync.rb +32 -0
- data/lib/fronde/templater.rb +35 -51
- data/lib/fronde/version.rb +1 -1
- data/lib/tasks/cli.rake +45 -13
- data/lib/tasks/org.rake +30 -35
- data/lib/tasks/site.rake +63 -41
- data/lib/tasks/sync.rake +19 -50
- data/lib/tasks/tags.rake +2 -2
- data/locales/en.yml +143 -81
- data/locales/fr.yml +153 -89
- metadata +56 -17
- 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
|
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
|
-
|
63
|
+
fetch_org_tarball http, Net::HTTP::Get.new(uri), destination
|
64
|
+
end
|
65
|
+
org_last_version
|
66
|
+
end
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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:
|
99
|
-
system(*make_org_cmd(target, 'autoloads', 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
|
8
|
+
title.downcase
|
9
9
|
.encode('ascii', fallback: ->(k) { translit(k) })
|
10
|
-
.
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
data/lib/fronde/source/gemini.rb
CHANGED
@@ -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:
|
17
|
-
creator:
|
18
|
-
date:
|
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
|
|
data/lib/fronde/source/html.rb
CHANGED
@@ -13,9 +13,9 @@ module Fronde
|
|
13
13
|
class << self
|
14
14
|
def org_default_postamble
|
15
15
|
<<~POSTAMBLE
|
16
|
-
<p><span class="author">#{
|
17
|
-
#{
|
18
|
-
<p class="date">#{
|
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
|
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' => '
|
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="
|
58
|
+
href="%h/assets/%o/css/style.css">
|
59
59
|
<link rel="stylesheet" type="text/css" media="screen"
|
60
|
-
href="
|
61
|
-
|
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
|
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
|
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
|
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 = [
|
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(
|
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
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
data/lib/fronde/sync.rb
ADDED
@@ -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
|