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