fronde 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/bin/fronde +15 -30
  3. data/lib/ext/nil_time.rb +25 -0
  4. data/lib/ext/r18n.rb +17 -0
  5. data/lib/ext/time.rb +49 -0
  6. data/lib/fronde/cli/commands.rb +92 -103
  7. data/lib/fronde/cli/data/Rakefile +8 -0
  8. data/lib/fronde/cli/data/config.yml +13 -0
  9. data/lib/fronde/cli/data/gitignore +7 -0
  10. data/lib/fronde/cli/data/zsh_completion +37 -0
  11. data/lib/fronde/cli/helpers.rb +55 -0
  12. data/lib/fronde/cli/opt_parse.rb +143 -0
  13. data/lib/fronde/cli/throbber.rb +99 -0
  14. data/lib/fronde/cli.rb +41 -42
  15. data/lib/fronde/config/data/org-config.el +24 -0
  16. data/lib/fronde/config/{ox-fronde.el → data/ox-fronde.el} +1 -1
  17. data/lib/fronde/config/helpers.rb +80 -0
  18. data/lib/fronde/config/lisp.rb +70 -0
  19. data/lib/fronde/config.rb +135 -99
  20. data/lib/fronde/emacs.rb +23 -20
  21. data/lib/fronde/index/atom_generator.rb +55 -66
  22. data/lib/fronde/index/data/all_tags.org +14 -0
  23. data/lib/fronde/index/data/template.org +22 -0
  24. data/lib/fronde/index/data/template.xml +37 -0
  25. data/lib/fronde/index/org_generator.rb +70 -88
  26. data/lib/fronde/index.rb +56 -82
  27. data/lib/fronde/org/file.rb +287 -0
  28. data/lib/fronde/org/file_extracter.rb +98 -0
  29. data/lib/fronde/org.rb +103 -0
  30. data/lib/fronde/preview.rb +43 -39
  31. data/lib/fronde/slug.rb +27 -0
  32. data/lib/fronde/source/gemini.rb +39 -0
  33. data/lib/fronde/source/html.rb +67 -0
  34. data/lib/fronde/source.rb +204 -0
  35. data/lib/fronde/templater.rb +94 -71
  36. data/lib/fronde/version.rb +1 -1
  37. data/lib/tasks/cli.rake +33 -0
  38. data/lib/tasks/org.rake +63 -43
  39. data/lib/tasks/site.rake +68 -30
  40. data/lib/tasks/sync.rake +41 -21
  41. data/lib/tasks/tags.rake +11 -7
  42. data/locales/en.yml +60 -14
  43. data/locales/fr.yml +68 -14
  44. metadata +57 -156
  45. data/lib/fronde/config/lisp_config.rb +0 -340
  46. data/lib/fronde/config/org-config.el +0 -19
  47. data/lib/fronde/org_file/class_methods.rb +0 -72
  48. data/lib/fronde/org_file/extracter.rb +0 -72
  49. data/lib/fronde/org_file/htmlizer.rb +0 -43
  50. data/lib/fronde/org_file.rb +0 -298
  51. data/lib/fronde/utils.rb +0 -229
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fronde
4
- # This module holds class methods for the {Fronde::OrgFile} class.
5
- module OrgFileClassMethods
6
- def source_for_target(file_name)
7
- # file_name may be frozen...
8
- src = file_name.sub(/\.html\z/, '.org')
9
- pubfolder = Fronde::Config.get('public_folder')
10
- src.sub!(/^#{pubfolder}\//, '')
11
- # Look for match in each possible sources. The first found wins.
12
- Fronde::Config.sources.each do |project|
13
- if project['target'] == '.'
14
- origin = File.join(project['path'], src)
15
- else
16
- origin = File.join(
17
- project['path'], src.sub(/^#{project['target']}\//, '')
18
- )
19
- end
20
- return origin if File.exist?(origin)
21
- end
22
- nil
23
- end
24
-
25
- def target_for_source(file_name, project, with_public_folder: true)
26
- return nil if file_name.nil?
27
- # file_name may be frozen...
28
- target = file_name.sub(/\.org\z/, '.html').sub(/^#{Dir.pwd}\//, '')
29
- if project.nil?
30
- subfolder = File.basename(File.dirname(target))
31
- target = File.basename(target)
32
- target = "#{subfolder}/#{target}" if subfolder != '.'
33
- else
34
- project_relative_path = project['path'].sub(/^#{Dir.pwd}\//, '')
35
- target.sub!(/^#{project_relative_path}\//, '')
36
- target = "#{project['target']}/#{target}" if project['target'] != '.'
37
- end
38
- return target unless with_public_folder
39
- pubfolder = Fronde::Config.get('public_folder')
40
- "#{pubfolder}/#{target}"
41
- end
42
-
43
- def project_for_source(file_name)
44
- # Look for match in each possible sources. The first found wins.
45
- Fronde::Config.sources.each do |project|
46
- project_relative_path = project['path'].sub(/^#{Dir.pwd}\//, '')
47
- return project if file_name.match?(/^#{project_relative_path}\//)
48
- end
49
- nil
50
- end
51
-
52
- def slug(title)
53
- title.downcase.tr(' ', '-')
54
- .encode('ascii', fallback: ->(k) { translit(k) })
55
- .gsub(/[^\w-]/, '').delete_suffix('-')
56
- end
57
-
58
- private
59
-
60
- def translit(char)
61
- return 'a' if ['á', 'à', 'â', 'ä', 'ǎ', 'ã', 'å'].include?(char)
62
- return 'e' if ['é', 'è', 'ê', 'ë', 'ě', 'ẽ'].include?(char)
63
- return 'i' if ['í', 'ì', 'î', 'ï', 'ǐ', 'ĩ'].include?(char)
64
- return 'o' if ['ó', 'ò', 'ô', 'ö', 'ǒ', 'õ'].include?(char)
65
- return 'u' if ['ú', 'ù', 'û', 'ü', 'ǔ', 'ũ'].include?(char)
66
- return 'y' if ['ý', 'ỳ', 'ŷ', 'ÿ', 'ỹ'].include?(char)
67
- return 'c' if char == 'ç'
68
- return 'n' if char == 'ñ'
69
- '-'
70
- end
71
- end
72
- end
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fronde
4
- # This module holds extracter methods for the {Fronde::OrgFile} class.
5
- module OrgFileExtracter
6
- private
7
-
8
- # Main method, which will call the other to initialize an
9
- # {Fronde::OrgFile} instance.
10
- def extract_data
11
- @content = File.read @file
12
- @title = extract_title
13
- @subtitle = extract_subtitle
14
- @date = extract_date
15
- @author = extract_author
16
- @keywords = extract_keywords
17
- @lang = extract_lang
18
- @excerpt = extract_excerpt
19
- end
20
-
21
- def extract_date
22
- timerx = '([0-9:]{5})(?::([0-9]{2}))?'
23
- m = /^#\+date: *<([0-9-]{10}) [\w.]+(?: #{timerx})?> *$/i.match(@content)
24
- return nil if m.nil?
25
- @notime = m[2].nil?
26
- if @notime
27
- time = '00:00:00'
28
- else
29
- time = "#{m[2]}:#{m[3] || '00'}"
30
- end
31
- DateTime.strptime("#{m[1]} #{time}", '%Y-%m-%d %H:%M:%S')
32
- end
33
-
34
- def extract_title
35
- m = /^#\+title:(.+)$/i.match(@content)
36
- if m.nil?
37
- # Avoid to leak absolute path
38
- project_relative_path = @file.sub(/^#{Dir.pwd}\//, '')
39
- return project_relative_path
40
- end
41
- m[1].strip
42
- end
43
-
44
- def extract_subtitle
45
- m = /^#\+subtitle:(.+)$/i.match(@content)
46
- return '' if m.nil?
47
- m[1].strip
48
- end
49
-
50
- def extract_author
51
- m = /^#\+author:(.+)$/i.match(@content)
52
- return Fronde::Config.get('author') if m.nil?
53
- m[1].strip
54
- end
55
-
56
- def extract_keywords
57
- m = /^#\+keywords:(.+)$/i.match(@content)
58
- return [] if m.nil?
59
- m[1].split(',').map(&:strip)
60
- end
61
-
62
- def extract_lang
63
- m = /^#\+language:(.+)$/i.match(@content)
64
- return Fronde::Config.get('lang') if m.nil?
65
- m[1].strip
66
- end
67
-
68
- def extract_excerpt
69
- @content.scan(/^#\+description:(.+)$/i).map { |l| l[0].strip }.join(' ')
70
- end
71
- end
72
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'fronde/config'
4
- require 'fronde/emacs'
5
-
6
- module Fronde
7
- # This module holds HTML formatter methods for the {Fronde::OrgFile}
8
- # class.
9
- module OrgFileHtmlizer
10
- private
11
-
12
- # Format {Fronde::OrgFile#keywords} list in an HTML listing.
13
- #
14
- # @return [String] the HTML keywords list
15
- def keywords_to_html
16
- domain = Fronde::Config.get('domain')
17
- klist = @keywords.map do |k|
18
- <<~KEYWORDLINK
19
- <li class="keyword">
20
- <a href="#{domain}/tags/#{Fronde::OrgFile.slug(k)}.html">#{k}</a>
21
- </li>
22
- KEYWORDLINK
23
- end.join
24
- "<ul class=\"keywords-list\">#{klist}</ul>"
25
- end
26
-
27
- # Format {Fronde::OrgFile#date} as a HTML `time` tag.
28
- #
29
- # @return [String] the HTML `time` tag
30
- def date_to_html(dateformat = :full)
31
- return '<time></time>' if @date.nil?
32
- "<time datetime=\"#{@date.rfc3339}\">#{datestring(dateformat)}</time>"
33
- end
34
-
35
- # Format {Fronde::OrgFile#author} in a HTML `span` tag with a
36
- # specific class.
37
- #
38
- # @return [String] the author HTML `span`
39
- def author_to_html
40
- "<span class=\"author\">#{@author}</span>"
41
- end
42
- end
43
- end
@@ -1,298 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'time'
4
- require 'fileutils'
5
- # fronde/config is required by htmlizer
6
- require 'fronde/org_file/htmlizer'
7
- require 'fronde/org_file/extracter'
8
- require 'fronde/org_file/class_methods'
9
- require 'fronde/index'
10
- require 'fronde/version'
11
-
12
- module Fronde
13
- # Handles org files.
14
- #
15
- # This class is responsible for reading or writing existing or new org
16
- # files, and formating their content to be used on the generated
17
- # website.
18
- class OrgFile # rubocop:disable Metrics/ClassLength
19
- # @return [String] the title of the current org document, taken from
20
- # the ~#+title:~ header.
21
- attr_reader :title
22
-
23
- # @return [String] the subtitle of the current org document, taken
24
- # from the ~#+subtitle:~ header.
25
- attr_reader :subtitle
26
-
27
- # @return [DateTime] the date and time of the current org document,
28
- # taken from the ~#+date:~ header.
29
- attr_reader :date
30
-
31
- # @return [Boolean] wether a time has been extracted from the
32
- # current org document ~#+date:~ header.
33
- attr_reader :notime
34
-
35
- # The author of the current org document, taken from the ~#+author:~
36
- # header.
37
- #
38
- # If the current document doesn't have any authorship information,
39
- # the one from the ~config.yml~ file will be used instead
40
- #
41
- # @return [String] the author name
42
- attr_reader :author
43
-
44
- # @return [Array] the keywords list of the current org document,
45
- # taken from the ~#+keywords:~ header.
46
- attr_reader :keywords
47
-
48
- # @return [String] the description of this org document, taken from
49
- # the ~#+description:~ header.
50
- attr_reader :excerpt
51
-
52
- # The locale of the current org document, taken from the
53
- # ~#+language:~ header.
54
- #
55
- # If the current document doesn't have any language information, the
56
- # one from the ~config.yml~ file will be used instead, or "en" by
57
- # default.
58
- #
59
- # @return [String] the document lang
60
- attr_reader :lang
61
-
62
- # @return [String] the relative path to the source of this document.
63
- attr_reader :file
64
-
65
- # @return [String] the relative path to the generated html file of
66
- # this document.
67
- attr_reader :html_file
68
-
69
- # @return [String] the url of this document, build from the ~domain~
70
- # settings and the above {#html_file @html_file} attribute.
71
- attr_reader :url
72
-
73
- # @return [String] the project owning this document.
74
- attr_reader :project
75
-
76
- extend Fronde::OrgFileClassMethods
77
-
78
- include Fronde::OrgFileExtracter
79
- include Fronde::OrgFileHtmlizer
80
-
81
- # Prepares the file named by ~file_name~ for read and write
82
- # operations.
83
- #
84
- # If the file ~file_name~ does not exist, the new instance may be
85
- # populated by data given in the ~opts~ parameter.
86
- #
87
- # @example
88
- # File.exist? './test.org'
89
- # => true
90
- # o = Fronde::OrgFile.new('./test.org')
91
- # => #<Fronde::OrgFile @file='./test.org'...>
92
- # o.title
93
- # => "This is an existing test file"
94
- # File.exist? '/tmp/does_not_exist.org'
95
- # => false
96
- # o = Fronde::OrgFile.new('/tmp/does_not_exist.org')
97
- # => #<Fronde::OrgFile @file='/tmp/does_not_exist.org'...>
98
- # o.title
99
- # => ""
100
- # File.exist? '/tmp/other.org'
101
- # => false
102
- # o = Fronde::OrgFile.new('/tmp/other.org', title: 'New file')
103
- # => #<Fronde::OrgFile @file='/tmp/other.org'...>
104
- # o.title
105
- # => "New file"
106
- #
107
- # @param file_name [String] path to the corresponding Org file
108
- # @param opts [Hash] optional data to initialize new Org file
109
- # @option opts [String] title ('') the title of the new Org file
110
- # @option opts [String] author (system user or '') the author of the
111
- # document
112
- # @option opts [String] project the project owning this file
113
- # must be stored
114
- # @return [Fronde::OrgFile] the new instance of Fronde::OrgFile
115
- def initialize(file_name, opts = {})
116
- file_name = nil if file_name == ''
117
- @file = file_name
118
- @html_file = nil
119
- @url = nil
120
- @project = opts.delete :project
121
- @options = opts
122
- build_html_file_and_url
123
- if @file && File.exist?(@file)
124
- extract_data
125
- else
126
- init_empty_file
127
- end
128
- end
129
-
130
- # Returns a String representation of the document date, which aims
131
- # to be used to sort several OrgFiles.
132
- #
133
- # The format used for the key is ~%Y%m%d%H%M%S~. If the current
134
- # OrgFile instance does not have a date, this mehod return
135
- # ~00000000000000~. If the current OrgFile instance does not have
136
- # time information, the date is padded with zeros.
137
- #
138
- # @example with the org header ~#+date: <2019-07-03 Wed 20:52:49>~
139
- # org_file.date
140
- # => #<DateTime: 2019-07-03T20:52:49+02:00...>
141
- # org_file.timekey
142
- # => "20190703205349"
143
- #
144
- # @example with the org header ~#+date: <2019-07-03 Wed>~
145
- # org_file.date
146
- # => #<DateTime: 2019-07-03T00:00:00+02:00...>
147
- # org_file.timekey
148
- # => "20190703000000"
149
- #
150
- # @example with no date header in the org file
151
- # org_file.date
152
- # => nil
153
- # org_file.timekey
154
- # => "00000000000000"
155
- #
156
- # @return [String] the document key
157
- def timekey
158
- return '00000000000000' if @date.nil?
159
- @date.strftime('%Y%m%d%H%M%S')
160
- end
161
-
162
- # Returns the current OrgFile instance DateTime as a String.
163
- #
164
- # This method accepts three values for the ~dateformat~ parameter:
165
- #
166
- # - ~:full~ (or ~:long~) :: outputs a complete date and time
167
- # representation, localized through R18n;
168
- # - ~:short~ :: outputs a short date representation (without time),
169
- # localized with R18n;
170
- # - ~:rfc3339~ :: outputs the RFC 3339 date and time representation,
171
- # used in atom feed.
172
- #
173
- # @param dateformat [Symbol] the format to use to convert DateTime
174
- # into String
175
- # @param year [Boolean] wether or not the ~:full~ format must
176
- # contain the year
177
- # @return [String] the document DateTime string representation
178
- def datestring(dateformat = :full, year: true)
179
- return '' if @date.nil?
180
- return R18n.l @date.to_date if dateformat == :short
181
- return @date.rfc3339 if dateformat == :rfc3339
182
- locale = R18n.get.locale
183
- long_fmt = R18n.t.fronde.index.full_date_format(
184
- date: locale.format_date_full(@date, year: year)
185
- )
186
- unless @notime
187
- long_fmt = R18n.t.fronde.index.full_date_with_time_format(
188
- date: long_fmt, time: locale.time_format.delete('_').strip
189
- )
190
- end
191
- locale.strftime(@date, long_fmt)
192
- end
193
-
194
- # Formats given ~string~ with values of the current OrgFile.
195
- #
196
- # This method expects to find percent-tags in the given ~string~ and
197
- # replace them by their corresponding value.
198
- #
199
- # It reuses the same tags than the ~org-html-format-spec~ method.
200
- #
201
- # *** Format:
202
- #
203
- # - %a :: the raw author name
204
- # - %A :: the HTML rendering of the author name, equivalent to
205
- # ~<span class="author">%a</span>~
206
- # - %d :: the ~:short~ date HTML representation, equivalent
207
- # to ~<time datetime="%I">%i</time>~
208
- # - %D :: the ~:full~ date and time HTML representation
209
- # - %i :: the raw ~:short~ date and time
210
- # - %I :: the raw ~:rfc3339~ date and time
211
- # - %k :: the keywords separated by a comma
212
- # - %K :: the HTML list rendering of the keywords
213
- # - %l :: the lang of the document
214
- # - %L :: the license information, taken from the
215
- # {Fronde::Config#settings}
216
- # - %n :: the Fronde name and version
217
- # - %N :: the Fronde name and version with a link to the project
218
- # home on the name
219
- # - %s :: the subtitle of the document
220
- # - %t :: the title of the document
221
- # - %u :: the web path to the related published HTML document
222
- # - %x :: the raw description (eXcerpt)
223
- # - %X :: the description, enclosed in an HTML ~p~ tag, equivalent
224
- # to ~<p>%x</p>~
225
- #
226
- # @example
227
- # org_file.format("Article written by %a the %d")
228
- # => "Article written by Alice Smith the Wednesday 3rd July"
229
- #
230
- # @return [String] the given ~string~ after replacement occurs
231
- # rubocop:disable Metrics/MethodLength
232
- # rubocop:disable Layout/LineLength
233
- def format(string)
234
- string.gsub('%a', @author)
235
- .gsub('%A', author_to_html)
236
- .gsub('%d', date_to_html(:short))
237
- .gsub('%D', date_to_html)
238
- .gsub('%i', datestring(:short))
239
- .gsub('%I', datestring(:rfc3339))
240
- .gsub('%k', @keywords.join(', '))
241
- .gsub('%K', keywords_to_html)
242
- .gsub('%l', @lang)
243
- .gsub('%L', Fronde::Config.get('license', '').gsub(/\s+/, ' ').strip)
244
- .gsub('%n', "Fronde #{Fronde::VERSION}")
245
- .gsub('%N', "<a href=\"https://git.umaneti.net/fronde/about/\">Fronde</a> #{Fronde::VERSION}")
246
- .gsub('%s', @subtitle)
247
- .gsub('%t', @title)
248
- .gsub('%u', @html_file || '')
249
- .gsub('%x', @excerpt)
250
- .gsub('%X', "<p>#{@excerpt}</p>")
251
- end
252
- # rubocop:enable Layout/LineLength
253
- # rubocop:enable Metrics/MethodLength
254
-
255
- # Writes the current OrgFile content to the underlying file.
256
- #
257
- # The intermediate parent folders are created if necessary.
258
- #
259
- # @return [Integer] the length written (as returned by the
260
- # underlying ~File.write~ method call)
261
- def write
262
- raise TypeError, 'no conversion from nil file name to path.' if @file.nil?
263
- file_dir = File.dirname @file
264
- FileUtils.mkdir_p file_dir unless Dir.exist? file_dir
265
- File.write @file, @content
266
- end
267
-
268
- private
269
-
270
- def build_html_file_and_url
271
- return if @file.nil?
272
- @html_file = Fronde::OrgFile.target_for_source(
273
- @file, @project, with_public_folder: false
274
- )
275
- @url = "#{Fronde::Config.get('domain')}/#{@html_file}"
276
- end
277
-
278
- def init_empty_file
279
- @title = @options[:title] || ''
280
- @subtitle = ''
281
- @date = DateTime.now
282
- @notime = false
283
- @author = @options[:author] || Fronde::Config.get('author')
284
- @keywords = []
285
- @lang = @options[:lang] || Fronde::Config.get('lang')
286
- @excerpt = ''
287
- body = @options[:content] || ''
288
- @content = @options[:raw_content] || <<~ORG
289
- #+title: #{@title}
290
- #+date: <#{@date.strftime('%Y-%m-%d %a. %H:%M:%S')}>
291
- #+author: #{@author}
292
- #+language: #{@lang}
293
-
294
- #{body}
295
- ORG
296
- end
297
- end
298
- end