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/config.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'yaml'
|
4
|
-
require '
|
4
|
+
require 'i18n'
|
5
5
|
require 'singleton'
|
6
6
|
|
7
7
|
require_relative 'config/lisp'
|
@@ -39,16 +39,16 @@ module Fronde
|
|
39
39
|
|
40
40
|
def initialize
|
41
41
|
@default_settings = {
|
42
|
-
'author' =>
|
42
|
+
'author' => ENV['USER'] || '',
|
43
43
|
'domain' => '',
|
44
44
|
'lang' => Fronde::Config::Helpers.extract_lang_from_env('en'),
|
45
45
|
'html_public_folder' => 'public_html',
|
46
46
|
'gemini_public_folder' => 'public_gmi',
|
47
47
|
'templates' => [], 'theme' => 'default'
|
48
48
|
}.freeze
|
49
|
-
@org_version = @sources = nil
|
50
|
-
@config = load_settings
|
51
49
|
# Do not load sources now to avoid dependency loop on config
|
50
|
+
@sources = nil
|
51
|
+
@config = load_settings
|
52
52
|
end
|
53
53
|
|
54
54
|
include Fronde::Config::Lisp
|
@@ -93,7 +93,7 @@ module Fronde
|
|
93
93
|
def reset
|
94
94
|
# Reload config, taking default settings into account
|
95
95
|
@config = load_settings
|
96
|
-
@
|
96
|
+
@sources = nil
|
97
97
|
@sources = load_sources
|
98
98
|
end
|
99
99
|
|
@@ -109,7 +109,7 @@ module Fronde
|
|
109
109
|
# @return [Fronde::Config::Store] self
|
110
110
|
def load_test(config)
|
111
111
|
@config = @default_settings.merge config
|
112
|
-
@
|
112
|
+
@sources = nil
|
113
113
|
@sources = load_sources
|
114
114
|
self
|
115
115
|
end
|
@@ -141,33 +141,57 @@ module Fronde
|
|
141
141
|
get('sources', default_sources).filter_map do |source_conf|
|
142
142
|
config = Source.canonical_config source_conf.dup
|
143
143
|
unless config['path']
|
144
|
-
warn
|
144
|
+
warn I18n.t('fronde.error.source.no_path', source: config.inspect)
|
145
145
|
next
|
146
146
|
end
|
147
147
|
Source.new_from_config config
|
148
148
|
end
|
149
149
|
end
|
150
150
|
|
151
|
+
def check_duplicate_and_warn(collection, source, type)
|
152
|
+
path = source['path']
|
153
|
+
return path unless collection[type].has_key?(path)
|
154
|
+
|
155
|
+
warn(
|
156
|
+
I18n.t(
|
157
|
+
'fronde.error.source.duplicate',
|
158
|
+
source: source['name'], type: type
|
159
|
+
)
|
160
|
+
)
|
161
|
+
end
|
162
|
+
|
151
163
|
def remove_duplicate(sources)
|
152
164
|
check_paths = {}
|
153
165
|
sources.each do |source|
|
154
166
|
type = source.type
|
155
167
|
check_paths[type] ||= {}
|
156
|
-
path = source
|
168
|
+
path = check_duplicate_and_warn check_paths, source, type
|
157
169
|
# Avoid duplicate
|
158
|
-
|
159
|
-
|
160
|
-
R18n.t.fronde.error.source.duplicate(
|
161
|
-
source: source['name'], type: type
|
162
|
-
)
|
163
|
-
)
|
164
|
-
next
|
165
|
-
end
|
170
|
+
next unless path
|
171
|
+
|
166
172
|
check_paths[type][path] = source
|
167
173
|
end
|
168
174
|
check_paths
|
169
175
|
end
|
170
176
|
|
177
|
+
def filter_possible_matchs(path, other_paths_list)
|
178
|
+
other_paths_list.select do |other_path|
|
179
|
+
path != other_path && other_path.start_with?(path)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def warn_on_existing_inclusion(type, other, possible_matchs, sources)
|
184
|
+
possible_matchs.each do |match|
|
185
|
+
warn(
|
186
|
+
I18n.t(
|
187
|
+
'fronde.error.source.inclusion',
|
188
|
+
source: sources[match]['title'],
|
189
|
+
other_source: other, type: type
|
190
|
+
)
|
191
|
+
)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
171
195
|
def remove_inclusion(check_paths)
|
172
196
|
check_paths.map do |type, sources_by_path|
|
173
197
|
skip_paths = []
|
@@ -183,21 +207,13 @@ module Fronde
|
|
183
207
|
next source unless source.recursive?
|
184
208
|
|
185
209
|
# Ensure that the current source does not embed another one
|
186
|
-
possible_matchs =
|
187
|
-
path != other_path && other_path.start_with?(path)
|
188
|
-
end
|
210
|
+
possible_matchs = filter_possible_matchs path, sorted_paths
|
189
211
|
next source if possible_matchs.empty?
|
190
212
|
|
191
213
|
skip_paths += possible_matchs
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
R18n.t.fronde.error.source.inclusion(
|
196
|
-
source: other_source['title'], type: type,
|
197
|
-
other_source: source['title']
|
198
|
-
)
|
199
|
-
)
|
200
|
-
end
|
214
|
+
warn_on_existing_inclusion(
|
215
|
+
type, source['title'], possible_matchs, sources_by_path
|
216
|
+
)
|
201
217
|
end
|
202
218
|
end.flatten
|
203
219
|
end
|
@@ -207,8 +223,8 @@ module Fronde
|
|
207
223
|
CONFIG = Config::Store.instance
|
208
224
|
end
|
209
225
|
|
210
|
-
|
211
|
-
|
212
|
-
|
226
|
+
i18n_glob = File.expand_path('../../locales', __dir__)
|
227
|
+
I18n.load_path = Dir.glob("#{i18n_glob}/*.yml")
|
228
|
+
I18n.default_locale = Fronde::CONFIG.get('lang')
|
213
229
|
|
214
230
|
Fronde::CONFIG.load_sources
|
data/lib/fronde/emacs.rb
CHANGED
@@ -10,8 +10,22 @@ module Fronde
|
|
10
10
|
@command = nil
|
11
11
|
end
|
12
12
|
|
13
|
-
def publish(project = 'website')
|
14
|
-
|
13
|
+
def publish(project = 'website', force: false)
|
14
|
+
if force
|
15
|
+
build_command %[(org-publish "#{project}" t)]
|
16
|
+
else
|
17
|
+
build_command %[(org-publish "#{project}")]
|
18
|
+
end
|
19
|
+
run_command
|
20
|
+
end
|
21
|
+
|
22
|
+
def publish_file(file_path, force: false)
|
23
|
+
if force
|
24
|
+
build_command '(org-publish-current-file t)'
|
25
|
+
else
|
26
|
+
build_command '(org-publish-current-file)'
|
27
|
+
end
|
28
|
+
@command.insert(-2, %(--visit "#{file_path}"))
|
15
29
|
run_command
|
16
30
|
end
|
17
31
|
|
@@ -20,21 +34,21 @@ module Fronde
|
|
20
34
|
def run_command
|
21
35
|
cmd = @command.join(' ')
|
22
36
|
if @verbose
|
23
|
-
|
24
|
-
return system(cmd, exception: true)
|
37
|
+
puts cmd
|
38
|
+
return system(cmd, err: $stdout, exception: true)
|
25
39
|
end
|
26
|
-
system cmd, out:
|
40
|
+
system cmd, out: File::NULL, err: File::NULL, exception: true
|
27
41
|
end
|
28
42
|
|
29
43
|
def build_command(org_action)
|
30
44
|
default_emacs = Fronde::CONFIG.get('emacs')
|
31
|
-
@command = [
|
32
|
-
|
33
|
-
|
34
|
-
'--eval \'(setq enable-dir-local-variables nil)\'',
|
45
|
+
@command = [
|
46
|
+
default_emacs || 'emacs -Q --batch -nw',
|
47
|
+
'--eval \'(setq inhibit-message t)\'',
|
35
48
|
'-l ./var/lib/org-config.el',
|
36
49
|
"--eval '#{org_action}'"
|
37
50
|
]
|
51
|
+
@command.delete_at(1) if @verbose
|
38
52
|
end
|
39
53
|
end
|
40
54
|
end
|
@@ -22,7 +22,7 @@ module Fronde
|
|
22
22
|
FileUtils.mkdir_p "#{@project.publication_path}/feeds"
|
23
23
|
@index.each_key do |tag|
|
24
24
|
write_atom(tag)
|
25
|
-
|
25
|
+
puts I18n.t('fronde.index.atom_generated', tag:) if verbose
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
@@ -4,11 +4,16 @@
|
|
4
4
|
{% for index in indexes %}
|
5
5
|
* {{ index.title }}
|
6
6
|
:PROPERTIES:
|
7
|
-
|
7
|
+
{%- if project_type == 'html' %}
|
8
|
+
:HTML_CONTAINER_CLASS: index-tags{% endif %}
|
8
9
|
:UNNUMBERED: notoc
|
9
10
|
:END:
|
10
11
|
|
11
12
|
{% for tag in index.tags -%}
|
13
|
+
{%- if project_type == 'gemini' -%}
|
14
|
+
[[{{ domain }}{{ project_path }}tags/{{ tag.slug }}.gmi][{{ tag.title }} ({{ tag.weight }})]]
|
15
|
+
{%- else -%}
|
12
16
|
- [[{{ domain }}{{ project_path }}tags/{{ tag.slug }}.html][{{ tag.title }}]] ({{ tag.weight }})
|
17
|
+
{%- endif %}
|
13
18
|
{% endfor %}
|
14
19
|
{%- endfor -%}
|
@@ -1,22 +1,26 @@
|
|
1
1
|
#+title: {{ title }}
|
2
2
|
#+author: {{ author }}
|
3
3
|
#+language: {{ lang }}
|
4
|
-
{%-
|
4
|
+
{%- if project_type == 'html' and slug != '__HOME_PAGE__' %}
|
5
5
|
#+html_head_extra: <link rel="alternate" type="application/atom+xml" title="{{ title }}" href="{{ domain }}{{ project_path }}feeds/{{ slug }}.xml" />
|
6
|
-
{%
|
6
|
+
{% endif -%}
|
7
7
|
{%- assign last_year = 0 -%}
|
8
8
|
{% for article in entries %}
|
9
9
|
{% assign cur_year = article.timekey | slice: 0, 4 %}
|
10
10
|
{%- unless cur_year == last_year %}
|
11
11
|
* {% if cur_year == "0000" %}{{ unsorted }}{% else %}{{ cur_year }}{% endif %}
|
12
12
|
:PROPERTIES:
|
13
|
-
|
13
|
+
{%- if project_type == 'html' %}
|
14
|
+
:HTML_CONTAINER_CLASS: index-year{% endif %}
|
14
15
|
:UNNUMBERED: notoc
|
15
16
|
:END:
|
16
17
|
{% assign last_year = cur_year %}
|
17
18
|
{% endunless -%}
|
19
|
+
{%- if project_type == 'gemini' -%}
|
20
|
+
[[{{ article.url }}][{% if article.published != '' %}{{ article.published_gemini_index }} {% endif %}{{ article.title }}]]
|
21
|
+
{%- else -%}
|
18
22
|
- *[[{{ article.url }}][{{ article.title }}]]*
|
19
23
|
{%- if article.published != '' %} / {{ article.published }}{% endif -%}
|
20
24
|
{%- if article.excerpt != '' %} \\
|
21
|
-
{{ article.excerpt }}{% endif %}
|
25
|
+
{{ article.excerpt }}{% endif %}{% endif %}
|
22
26
|
{%- endfor %}
|
@@ -30,7 +30,7 @@ module Fronde
|
|
30
30
|
FileUtils.mkdir_p "#{@project['path']}/tags"
|
31
31
|
@index.each_key do |tag|
|
32
32
|
write_org(tag)
|
33
|
-
|
33
|
+
puts I18n.t('fronde.index.index_generated', tag:) if verbose
|
34
34
|
end
|
35
35
|
write_blog_home_page(verbose)
|
36
36
|
end
|
@@ -47,7 +47,9 @@ module Fronde
|
|
47
47
|
entries.map! do |article|
|
48
48
|
published = article['published']
|
49
49
|
unless published == ''
|
50
|
-
article['published'] =
|
50
|
+
article['published'] = I18n.with_locale(article['lang']) do
|
51
|
+
I18n.t('fronde.index.published_on', date: published)
|
52
|
+
end
|
51
53
|
end
|
52
54
|
article
|
53
55
|
end
|
@@ -56,10 +58,11 @@ module Fronde
|
|
56
58
|
'title' => title,
|
57
59
|
'slug' => slug,
|
58
60
|
'project_path' => @project.public_absolute_path,
|
61
|
+
'project_type' => @project.type,
|
59
62
|
'domain' => Fronde::CONFIG.get('domain'),
|
60
63
|
'lang' => Fronde::CONFIG.get('lang'),
|
61
64
|
'author' => Fronde::CONFIG.get('author'),
|
62
|
-
'unsorted' =>
|
65
|
+
'unsorted' => I18n.t('fronde.index.unsorted'),
|
63
66
|
'entries' => entries
|
64
67
|
)
|
65
68
|
end
|
@@ -72,15 +75,16 @@ module Fronde
|
|
72
75
|
'weight' => @index[tag].length
|
73
76
|
}
|
74
77
|
end
|
75
|
-
{ 'title' =>
|
78
|
+
{ 'title' => I18n.t("fronde.index.#{title}"), 'tags' => all_tags }
|
76
79
|
end
|
77
80
|
Config::Helpers.render_liquid_template(
|
78
81
|
File.read(File.expand_path('./data/all_tags.org', __dir__)),
|
79
|
-
'title' =>
|
82
|
+
'title' => I18n.t('fronde.index.all_tags'),
|
80
83
|
'lang' => Fronde::CONFIG.get('lang'),
|
81
84
|
'author' => Fronde::CONFIG.get('author'),
|
82
85
|
'domain' => Fronde::CONFIG.get('domain'),
|
83
86
|
'project_path' => @project.public_absolute_path,
|
87
|
+
'project_type' => @project.type,
|
84
88
|
'indexes' => indexes
|
85
89
|
)
|
86
90
|
end
|
@@ -88,7 +92,7 @@ module Fronde
|
|
88
92
|
def write_blog_home_page(verbose)
|
89
93
|
orgdest = format('%<root>s/index.org', root: @project['path'])
|
90
94
|
if verbose
|
91
|
-
|
95
|
+
puts I18n.t('fronde.org.generate_blog_index', name: @project['name'])
|
92
96
|
end
|
93
97
|
File.write(orgdest, blog_home_page)
|
94
98
|
end
|
data/lib/fronde/index.rb
CHANGED
@@ -8,7 +8,7 @@ module Fronde
|
|
8
8
|
# Generates website indexes and atom feeds for all the org documents
|
9
9
|
# keywords.
|
10
10
|
class Index
|
11
|
-
attr_reader :date
|
11
|
+
attr_reader :date, :project
|
12
12
|
|
13
13
|
def initialize(project)
|
14
14
|
@project = project
|
@@ -30,26 +30,28 @@ module Fronde
|
|
30
30
|
|
31
31
|
def sort_by(kind)
|
32
32
|
accepted_values = %i[name weight]
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end.reverse
|
33
|
+
unless accepted_values.include?(kind)
|
34
|
+
error_msg = I18n.t(
|
35
|
+
'fronde.error.index.wrong_sort_kind',
|
36
|
+
kind: kind, accepted_values: accepted_values.inspect
|
37
|
+
)
|
38
|
+
raise ArgumentError, error_msg
|
40
39
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
40
|
+
sort_tags_by_name_and_weight[:"by_#{kind}"].map do |tag|
|
41
|
+
@tags_names[tag] + " (#{@index[tag].length})"
|
42
|
+
end.reverse
|
43
|
+
# Reverse in order to have most important or A near next prompt
|
44
|
+
# and avoid to scroll to find the beginning of the list.
|
45
|
+
end
|
46
|
+
|
47
|
+
def emacs_keywords
|
48
|
+
@tags_names.map { |slug, title| "#{title}\x1f#{slug}" }.join("\x1e")
|
45
49
|
end
|
46
50
|
|
47
51
|
class << self
|
48
|
-
def
|
52
|
+
def all_blog_index(&block)
|
49
53
|
all_blogs = CONFIG.sources.filter_map do |project|
|
50
|
-
|
51
|
-
|
52
|
-
Index.new(project)
|
54
|
+
Index.new(project) if project.blog?
|
53
55
|
end
|
54
56
|
return all_blogs unless block
|
55
57
|
|
@@ -94,7 +96,7 @@ module Fronde
|
|
94
96
|
all_keys = all_tags
|
95
97
|
{
|
96
98
|
by_name: all_keys.sort,
|
97
|
-
by_weight: all_keys.sort_by {
|
99
|
+
by_weight: all_keys.sort_by { [-@index[_1].length, _1] }
|
98
100
|
}
|
99
101
|
end
|
100
102
|
end
|
data/lib/fronde/org/file.rb
CHANGED
@@ -109,6 +109,21 @@ module Fronde
|
|
109
109
|
@data[:date].strftime('%Y%m%d%H%M%S')
|
110
110
|
end
|
111
111
|
|
112
|
+
# Returns the path to the published version of this document.
|
113
|
+
#
|
114
|
+
# By default, this method returns the relative path to the published
|
115
|
+
# file. If the ~absolute~ argument is true, it will return the absolute
|
116
|
+
# path to the published file.
|
117
|
+
#
|
118
|
+
# @param absolute [Boolean] whether to display absolute or relative
|
119
|
+
# published file path (default false)
|
120
|
+
# @return [String] the document key
|
121
|
+
def pub_file(absolute: false)
|
122
|
+
return @data[:pub_file] unless absolute
|
123
|
+
|
124
|
+
"#{@project['folder']}#{@data[:pub_file]}"
|
125
|
+
end
|
126
|
+
|
112
127
|
# Formats given ~string~ with values of the current Org::File.
|
113
128
|
#
|
114
129
|
# This method expects to find percent-tags in the given ~string~
|
@@ -118,42 +133,60 @@ module Fronde
|
|
118
133
|
#
|
119
134
|
# *** Format:
|
120
135
|
#
|
121
|
-
# - %a :: the raw author name
|
136
|
+
# - %a :: the raw author name.
|
122
137
|
# - %A :: the HTML rendering of the author name, equivalent to
|
123
|
-
# ~<span class="author">%a</span
|
138
|
+
# ~<span class="author">%a</span>~.
|
124
139
|
# - %d :: the ~:short~ date HTML representation, equivalent
|
125
|
-
# to ~<time datetime="%I">%i</time
|
126
|
-
# - %D :: the ~:full~ date and time HTML representation
|
127
|
-
# - %
|
128
|
-
#
|
129
|
-
# - %
|
130
|
-
#
|
131
|
-
# - %
|
140
|
+
# to ~<time datetime="%I">%i</time>~.
|
141
|
+
# - %D :: the ~:full~ date and time HTML representation.
|
142
|
+
# - %F :: the ~link~ HTML tag for the main Atom feed of the
|
143
|
+
# current file source.
|
144
|
+
# - %h :: the declared host/domain name, taken from the
|
145
|
+
# {Fronde::Config#settings}.
|
146
|
+
# - %i :: the raw ~:short~ date and time.
|
147
|
+
# - %I :: the raw ~:iso8601~ date and time.
|
148
|
+
# - %k :: the document keywords separated by commas.
|
149
|
+
# - %K :: the HTML list rendering of the keywords.
|
150
|
+
# - %l :: the lang of the document.
|
132
151
|
# - %L :: the license information, taken from the
|
133
|
-
# {Fronde::Config#settings}
|
134
|
-
# - %n :: the
|
135
|
-
# - %N :: the
|
136
|
-
# home on the name
|
137
|
-
# - %
|
138
|
-
# - %
|
139
|
-
# - %
|
140
|
-
# - %
|
152
|
+
# {Fronde::Config#settings}.
|
153
|
+
# - %n :: the fronde name and version.
|
154
|
+
# - %N :: the fronde name and version with a link to the project
|
155
|
+
# home on the name.
|
156
|
+
# - %o :: the theme name (~o~ as in Outfit) of the current file source.
|
157
|
+
# - %s :: the subtitle of the document (from ~#+subtitle:~).
|
158
|
+
# - %t :: the title of the document (from ~#+title:~).
|
159
|
+
# - %u :: the URL to the related published HTML document.
|
160
|
+
# - %x :: the raw description (~x~ as in eXcerpt) of the document
|
161
|
+
# (from ~#+description:~).
|
141
162
|
# - %X :: the description, enclosed in an HTML ~p~ tag, equivalent
|
142
|
-
# to ~<p>%x</p
|
163
|
+
# to ~<p>%x</p>~.
|
143
164
|
#
|
144
165
|
# @example
|
145
166
|
# org_file.format("Article written by %a the %d")
|
146
167
|
# => "Article written by Alice Smith the Wednesday 3rd July"
|
147
168
|
#
|
169
|
+
# @param string [String] the template text to edit
|
148
170
|
# @return [String] the given ~string~ after replacement occurs
|
149
|
-
# rubocop:disable Metrics/MethodLength
|
150
171
|
# rubocop:disable Layout/LineLength
|
151
172
|
def format(string)
|
173
|
+
project_data = @project.to_h
|
174
|
+
# NOTE: The following keycode are reserved by Org itself:
|
175
|
+
# %a (author), %c (creator), %C (input-file), %d (date),
|
176
|
+
# %e (email), %s (subtitle), %t (title), %T (timestamp),
|
177
|
+
# %v (html validation link)
|
178
|
+
localized_dates = I18n.with_locale(@data[:lang]) do
|
179
|
+
{ short: @data[:date].l18n_short_date_string,
|
180
|
+
short_html: @data[:date].l18n_short_date_html,
|
181
|
+
long_html: @data[:date].l18n_long_date_html }
|
182
|
+
end
|
152
183
|
string.gsub('%a', @data[:author])
|
153
184
|
.gsub('%A', "<span class=\"author\">#{@data[:author]}</span>")
|
154
|
-
.gsub('%d',
|
155
|
-
.gsub('%D',
|
156
|
-
.gsub('%
|
185
|
+
.gsub('%d', localized_dates[:short_html])
|
186
|
+
.gsub('%D', localized_dates[:long_html])
|
187
|
+
.gsub('%F', project_data['atom_feed'] || '')
|
188
|
+
.gsub('%h', project_data['domain'] || '')
|
189
|
+
.gsub('%i', localized_dates[:short])
|
157
190
|
.gsub('%I', @data[:date].xmlschema)
|
158
191
|
.gsub('%k', @data[:keywords].join(', '))
|
159
192
|
.gsub('%K', keywords_to_html)
|
@@ -161,6 +194,7 @@ module Fronde
|
|
161
194
|
.gsub('%L', Fronde::CONFIG.get('license', '').gsub(/\s+/, ' ').strip)
|
162
195
|
.gsub('%n', "Fronde #{Fronde::VERSION}")
|
163
196
|
.gsub('%N', "<a href=\"https://git.umaneti.net/fronde/about/\">Fronde</a> #{Fronde::VERSION}")
|
197
|
+
.gsub('%o', project_data['theme'] || '')
|
164
198
|
.gsub('%s', @data[:subtitle])
|
165
199
|
.gsub('%t', @data[:title])
|
166
200
|
.gsub('%u', @data[:url] || '')
|
@@ -168,7 +202,6 @@ module Fronde
|
|
168
202
|
.gsub('%X', "<p>#{@data[:excerpt]}</p>")
|
169
203
|
end
|
170
204
|
# rubocop:enable Layout/LineLength
|
171
|
-
# rubocop:enable Metrics/MethodLength
|
172
205
|
|
173
206
|
# Writes the current Org::File content to the underlying file.
|
174
207
|
#
|
@@ -179,18 +212,17 @@ module Fronde
|
|
179
212
|
def write
|
180
213
|
if ::File.directory? @file
|
181
214
|
if @data[:title] == ''
|
182
|
-
raise
|
215
|
+
raise I18n.t('fronde.error.org_file.no_file_or_title')
|
183
216
|
end
|
184
217
|
|
185
218
|
@file = ::File.join @file, "#{Slug.slug(@data[:title])}.org"
|
186
219
|
else
|
187
|
-
|
188
|
-
FileUtils.mkdir_p file_dir
|
220
|
+
FileUtils.mkdir_p ::File.dirname(@file)
|
189
221
|
end
|
190
222
|
::File.write @file, @data[:content]
|
191
223
|
end
|
192
224
|
|
193
|
-
def method_missing(method_name, *args, &
|
225
|
+
def method_missing(method_name, *args, &)
|
194
226
|
reader_method = method_name.to_s.delete_suffix('=').to_sym
|
195
227
|
if @data.has_key? reader_method
|
196
228
|
return @data[reader_method] if reader_method == method_name
|
@@ -210,11 +242,14 @@ module Fronde
|
|
210
242
|
end
|
211
243
|
|
212
244
|
def to_h
|
213
|
-
fields = %w[author excerpt keywords timekey title url]
|
245
|
+
fields = %w[author excerpt keywords lang timekey title url]
|
214
246
|
data = fields.to_h { |key| [key, send(key)] }
|
215
247
|
data['published_body'] = extract_published_body
|
216
248
|
pub_date = @data[:date]
|
217
|
-
data['published'] =
|
249
|
+
data['published'] = I18n.with_locale(@data[:lang]) do
|
250
|
+
pub_date.l18n_long_date_no_year_string
|
251
|
+
end
|
252
|
+
data['published_gemini_index'] = pub_date.strftime('%Y-%m-%d')
|
218
253
|
data['published_xml'] = pub_date.xmlschema
|
219
254
|
data['updated_xml'] = @data[:updated]&.xmlschema
|
220
255
|
data
|
@@ -228,14 +263,14 @@ module Fronde
|
|
228
263
|
else
|
229
264
|
source = find_source_for_publication_file
|
230
265
|
end
|
231
|
-
|
232
|
-
|
266
|
+
return source if source
|
267
|
+
|
268
|
+
short_file = @file.sub(/^#{Dir.pwd}/, '.')
|
269
|
+
warn I18n.t('fronde.error.org_file.no_project', file: short_file)
|
233
270
|
end
|
234
271
|
|
235
272
|
def find_source_for_org_file
|
236
|
-
Fronde::CONFIG.sources.find
|
237
|
-
project.source_for? @file
|
238
|
-
end
|
273
|
+
Fronde::CONFIG.sources.find { _1.source_for? @file }
|
239
274
|
end
|
240
275
|
|
241
276
|
def find_source_for_publication_file
|
@@ -250,20 +285,17 @@ module Fronde
|
|
250
285
|
def init_empty_file
|
251
286
|
@data = {
|
252
287
|
title: @options[:title] || '', subtitle: '', excerpt: '',
|
253
|
-
date: Time.now,
|
254
288
|
author: @options[:author] || Fronde::CONFIG.get('author'),
|
255
|
-
keywords: [],
|
256
289
|
lang: @options[:lang] || Fronde::CONFIG.get('lang'),
|
257
|
-
pub_file: nil, url: nil
|
290
|
+
date: Time.now, keywords: [], pub_file: nil, url: nil
|
258
291
|
}
|
259
|
-
body = @options[:content] || ''
|
260
292
|
@data[:content] = @options[:raw_content] || <<~ORG
|
261
293
|
#+title: #{@data[:title]}
|
262
294
|
#+date: <#{@data[:date].strftime('%Y-%m-%d %a. %H:%M:%S')}>
|
263
295
|
#+author: #{@data[:author]}
|
264
296
|
#+language: #{@data[:lang]}
|
265
297
|
|
266
|
-
#{
|
298
|
+
#{@options[:content]}
|
267
299
|
ORG
|
268
300
|
end
|
269
301
|
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
using TimePatch
|
4
4
|
|
5
|
+
require_relative '../../ext/time_no_time'
|
6
|
+
|
5
7
|
module Fronde
|
6
8
|
module Org
|
7
9
|
# This module holds extracter methods for the {Fronde::Org::File}
|
@@ -14,37 +16,42 @@ module Fronde
|
|
14
16
|
def extract_data
|
15
17
|
@data = { content: ::File.read(@file), pub_file: nil, url: nil }
|
16
18
|
%i[title subtitle date author keywords lang excerpt].each do |param|
|
17
|
-
@data[param] = send("extract_#{param}"
|
19
|
+
@data[param] = send(:"extract_#{param}")
|
18
20
|
end
|
19
21
|
return unless @project
|
20
22
|
|
23
|
+
warn_if_dangerous_code_block
|
21
24
|
@data[:updated] = ::File.mtime(@file)
|
22
25
|
@data[:pub_file] = @project.target_for @file
|
23
26
|
@data[:url] = Fronde::CONFIG.get('domain') + @data[:pub_file]
|
24
27
|
end
|
25
28
|
|
29
|
+
def warn_if_dangerous_code_block
|
30
|
+
code_block_rx = /^#\+begin_src.+:exports (?:results|both).*$/i
|
31
|
+
return unless code_block_rx.match?(@data[:content])
|
32
|
+
|
33
|
+
warn I18n.t('fronde.error.org_file.dangerous_code_block', file: @file)
|
34
|
+
end
|
35
|
+
|
26
36
|
def extract_date
|
27
37
|
timerx = '([0-9:]{5})(?::([0-9]{2}))?'
|
28
38
|
daterx = /^#\+date: *<([0-9-]{10}) [\w.]+(?: #{timerx})?> *$/i
|
29
39
|
match = daterx.match(@data[:content])
|
30
40
|
return NilTime.new if match.nil?
|
31
41
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
date = Time.strptime("#{match[1]} #{time}", '%Y-%m-%d %H:%M:%S')
|
39
|
-
date.no_time = notime
|
40
|
-
date
|
42
|
+
return TimeNoTime.parse_no_time(match[1]) if match[2].nil?
|
43
|
+
|
44
|
+
Time.strptime(
|
45
|
+
"#{match[1]} #{match[2]}:#{match[3] || '00'}",
|
46
|
+
'%Y-%m-%d %H:%M:%S'
|
47
|
+
)
|
41
48
|
end
|
42
49
|
|
43
50
|
def extract_title
|
44
51
|
match = /^#\+title:(.+)$/i.match(@data[:content])
|
45
52
|
if match.nil?
|
46
53
|
# Avoid to leak absolute path
|
47
|
-
project_relative_path = @file.sub
|
54
|
+
project_relative_path = @file.sub %r{^#{Dir.pwd}/}, ''
|
48
55
|
return project_relative_path
|
49
56
|
end
|
50
57
|
match[1].strip
|
@@ -81,13 +88,17 @@ module Fronde
|
|
81
88
|
# Always return something, even when not published yet
|
82
89
|
return @data[:excerpt] unless pub_file && @project
|
83
90
|
|
84
|
-
project_type = @project
|
91
|
+
project_type = @project.type
|
85
92
|
pub_folder = Fronde::CONFIG.get("#{project_type}_public_folder")
|
86
93
|
file_name = pub_folder + pub_file
|
87
94
|
return @data[:excerpt] unless ::File.exist? file_name
|
88
95
|
|
89
96
|
return ::File.read(file_name) if project_type == 'gemini'
|
90
97
|
|
98
|
+
read_html_body file_name
|
99
|
+
end
|
100
|
+
|
101
|
+
def read_html_body(file_name)
|
91
102
|
dom = ::File.open(file_name, 'r') { |file| Nokogiri::HTML file }
|
92
103
|
body = dom.css('div#content')
|
93
104
|
body.css('header').unlink # Remove the main title
|