fronde 0.3.4 → 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 +62 -42
  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 +53 -110
  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,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
data/lib/fronde/utils.rb DELETED
@@ -1,229 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'uri'
4
- require 'rainbow'
5
- require 'net/http'
6
- require 'r18n-core'
7
- require 'fronde/config'
8
-
9
- module Fronde
10
- # Default Error, which may be raised by fronde code
11
- class Error < ::StandardError; end
12
-
13
- # Embeds usefull methods, mainly used in rake tasks.
14
- module Utils
15
- # @return [Hash] the possible throbber themes
16
- THROBBER_FRAMES = {
17
- 'basic' => '-\|/',
18
- 'basicdots' => '⋯⋱⋮⋰',
19
- 'moon' => '🌑🌒🌓🌔🌕🌖🌗🌘',
20
- 'clock' => '🕛🕐🕑🕒🕓🕔🕕🕖🕗🕘🕙🕚',
21
- 'bricks' => '⣷⣯⣟⡿⢿⣻⣽⣾',
22
- 'points' => '·⁘∷⁛∷⁘',
23
- 'quadrant2' => '▙▛▜▟',
24
- 'default' => ['⠁ ⠂ ⠄ ⡀ ⠄ ⠂ ⠁', '⠂ ⠁ ⠂ ⠄ ⡀ ⠄ ⠂', '⠄ ⠂ ⠁ ⠂ ⠄ ⡀ ⠄',
25
- '⡀ ⠄ ⠂ ⠁ ⠂ ⠄ ⡀', '⠄ ⡀ ⠄ ⠂ ⠁ ⠂ ⠄', '⠂ ⠄ ⡀ ⠄ ⠂ ⠁ ⠂']
26
- }.freeze
27
-
28
- # @return [Hash] the possible ~fronde~ options and their
29
- # configuration
30
- FRONDE_OPTIONS = {
31
- '-a' => { long: 'author' },
32
- '-f' => { long: 'force', boolean: true },
33
- '-h' => { long: 'help', boolean: true, meth: :on_tail },
34
- '-l' => { long: 'lang', keyword: 'LOCALE' },
35
- '-t' => { long: 'title' },
36
- '-v' => { long: 'verbose', boolean: true, meth: :on_tail },
37
- '-V' => { long: 'version', boolean: true, meth: :on_tail }
38
- }.freeze
39
-
40
- # @return [Hash] the possible ~fronde~ subcommands and their
41
- # configuration
42
- FRONDE_COMMANDS = {
43
- 'init' => { opts: ['-a', '-h', '-l', '-t', '-v'] },
44
- 'update' => { opts: ['-a', '-h', '-l', '-t', '-v'] },
45
- 'config' => { alias: 'update' },
46
- 'preview' => { opts: ['-h'] },
47
- 'open' => { opts: ['-a', '-h', '-l', '-t', '-v'] },
48
- 'edit' => { alias: 'open' },
49
- 'build' => { opts: ['-f', '-h'] },
50
- 'publish' => { opts: ['-h'] },
51
- 'help' => { opts: ['-h'] },
52
- 'basic' => { opts: ['-h', '-V'], label: '<command>' }
53
- }.freeze
54
-
55
- class << self
56
- # Animates strings in the user console to alert him that something
57
- # is running in the background.
58
- #
59
- # The animation is chosen among a bunch of themes, with the
60
- # configuration option ~throbber~ (retrieved via
61
- # {Fronde::Config#settings}).
62
- #
63
- # @example
64
- # long_stuff = Thread.new { very_long_operation }
65
- # Fronde::Utils.throbber(long_stuff, 'Computing hard stuff:')
66
- #
67
- # @param thread [Thread] the long-running operation to decorate
68
- # @param message [String] the message to display before the throbber
69
- # @return [void]
70
- def throbber(thread, message)
71
- frames = select_throbber_frames
72
- begin
73
- run_and_decorate_thread thread, message, frames
74
- rescue RuntimeError => e
75
- throbber_error message
76
- raise e
77
- else
78
- done = Rainbow('done'.ljust(frames[0].length)).green
79
- puts "#{message} #{done}"
80
- end
81
- end
82
-
83
- # Returns the short and long options specification for a given
84
- # short option.
85
- #
86
- # This method use the {Fronde::Utils::FRONDE_OPTIONS} Hash to
87
- # retrieve corresponding values.
88
- #
89
- # @example
90
- # spec = Fronde::Utils.decorate_option('-a')
91
- # => ['-a AUTHOR', '--author AUTHOR']
92
- #
93
- # @param short [String] the short option to decorate
94
- # @return [Array] the short and long specification for an option
95
- def decorate_option(short)
96
- opt = Fronde::Utils::FRONDE_OPTIONS[short]
97
- long = "--#{opt[:long]}"
98
- return [short, long] if opt[:boolean]
99
- key = opt[:keyword] || opt[:long].upcase
100
- [short + key, format('%<long>s %<key>s', long: long, key: key)]
101
- end
102
-
103
- # Returns the ~fronde~ help summary for a given command.
104
- #
105
- # @param command [String] the command for which a summary
106
- # should be given
107
- # @return [String]
108
- def summarize_command(command)
109
- Fronde::Utils::FRONDE_COMMANDS[command][:opts].map do |k|
110
- short, long = Fronde::Utils.decorate_option(k)
111
- opt = Fronde::Utils::FRONDE_OPTIONS[k]
112
- label = [short, long].join(', ')
113
- line = [format(' %<opt>s', opt: label).ljust(30)]
114
- if R18n.t.fronde.bin.options[opt[:long]].translated?
115
- line << R18n.t.fronde.bin.options[opt[:long]]
116
- end
117
- line.join(' ')
118
- end.join("\n")
119
- end
120
-
121
- # Returns a formatted list of available commands for ~fronde~.
122
- #
123
- # @return [String]
124
- def list_commands
125
- lines = []
126
- Fronde::Utils::FRONDE_COMMANDS.each do |cmd, opt|
127
- next if cmd == 'basic'
128
- line = [' ', cmd.ljust(10)]
129
- if opt.has_key? :alias
130
- line << R18n.t.fronde.bin.commands.alias(opt[:alias])
131
- else
132
- line << R18n.t.fronde.bin.commands[cmd]
133
- end
134
- lines << line.join(' ')
135
- end
136
- lines.join("\n")
137
- end
138
-
139
- # Returns the real command name for a given command, which may be
140
- # an alias.
141
- #
142
- # @param command [String] the command to resolve
143
- # @return [String]
144
- def resolve_possible_alias(command)
145
- return 'basic' unless Fronde::Utils::FRONDE_COMMANDS.include?(command)
146
- cmd_opt = Fronde::Utils::FRONDE_COMMANDS[command]
147
- return cmd_opt[:alias] if cmd_opt.has_key?(:alias)
148
- command
149
- end
150
-
151
- # Returns the given command options.
152
- #
153
- # This method will first try to resolve command alias, if any.
154
- #
155
- # @param command [String] the command, which options should be returned
156
- # @return [Hash] the command options
157
- def command_options(command)
158
- cmd = resolve_possible_alias command
159
- FRONDE_COMMANDS[cmd].merge(name: cmd)
160
- end
161
-
162
- # Try to discover the current host operating system.
163
- #
164
- # @return [String] either apple, windows or linux (default)
165
- # :nocov:
166
- def current_os
167
- if ENV['OS'] == 'Windows_NT' || RUBY_PLATFORM.include?('cygwin')
168
- return 'windows'
169
- end
170
- return 'apple' if RUBY_PLATFORM.include?('darwin')
171
- 'linux'
172
- end
173
- # :nocov:
174
-
175
- # Download latest org-mode tarball.
176
- #
177
- # @param destination [String] where to save the org-mode tarball
178
- # @return [String] the downloaded org-mode version
179
- def download_org(destination = 'var/tmp')
180
- # :nocov:
181
- return if Fronde::Config.org_last_version.nil?
182
- # :nocov:
183
- # Remove version number in dest file to allow easy rake file
184
- # task naming
185
- dest_file = File.expand_path('org.tar.gz', destination)
186
- return if File.exist?(dest_file)
187
- tarball = "org-mode-release_#{Fronde::Config.org_last_version}.tar.gz"
188
- uri = URI("https://git.savannah.gnu.org/cgit/emacs/org-mode.git/snapshot/#{tarball}")
189
- # Will crash on purpose if anything goes wrong
190
- Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http|
191
- http.request(Net::HTTP::Get.new(uri)) do |response|
192
- File.open(dest_file, 'w') do |io|
193
- response.read_body { |chunk| io.write chunk }
194
- end
195
- end
196
- end
197
- end
198
-
199
- private
200
-
201
- def throbber_error(message)
202
- warn(
203
- format(
204
- "%<message>s %<label>s\n%<explanation>s",
205
- message: message,
206
- label: Rainbow(R18n.t.fronde.error.label).bold.red,
207
- explanation: Rainbow(R18n.t.fronde.error.explanation).bold
208
- )
209
- )
210
- end
211
-
212
- def select_throbber_frames
213
- model = Fronde::Config.settings['throbber'] || 'default'
214
- model = 'default' unless Fronde::Utils::THROBBER_FRAMES.has_key?(model)
215
- Fronde::Utils::THROBBER_FRAMES[model]
216
- end
217
-
218
- def run_and_decorate_thread(thread, message, frames)
219
- thread.abort_on_exception = true
220
- current = 0
221
- while thread.alive?
222
- sleep 0.1
223
- print "#{message} #{frames[current % frames.length]}\r"
224
- current += 1
225
- end
226
- end
227
- end
228
- end
229
- end