neruda 0.1.3 → 0.2.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.
@@ -7,6 +7,7 @@ require 'neruda/org_file/htmlizer'
7
7
  require 'neruda/org_file/extracter'
8
8
  require 'neruda/org_file/class_methods'
9
9
  require 'neruda/index'
10
+ require 'neruda/version'
10
11
 
11
12
  module Neruda
12
13
  # Handles org files.
@@ -19,6 +20,10 @@ module Neruda
19
20
  # the ~#+title:~ header.
20
21
  attr_reader :title
21
22
 
23
+ # @return [String] the subtitle of the current org document, taken
24
+ # from the ~#+subtitle:~ header.
25
+ attr_reader :subtitle
26
+
22
27
  # @return [DateTime] the date and time of the current org document,
23
28
  # taken from the ~#+date:~ header.
24
29
  attr_reader :date
@@ -40,6 +45,10 @@ module Neruda
40
45
  # taken from the ~#+keywords:~ header.
41
46
  attr_reader :keywords
42
47
 
48
+ # @return [String] the description of this org document, taken from
49
+ # the ~#+description:~ header.
50
+ attr_reader :excerpt
51
+
43
52
  # The locale of the current org document, taken from the
44
53
  # ~#+language:~ header.
45
54
  #
@@ -61,9 +70,8 @@ module Neruda
61
70
  # settings and the above {#html_file @html_file} attribute.
62
71
  attr_reader :url
63
72
 
64
- # @return [String] the description of this org document, taken from
65
- # the ~#+description:~ header.
66
- attr_reader :excerpt
73
+ # @return [String] the project owning this document.
74
+ attr_reader :project
67
75
 
68
76
  extend Neruda::OrgFileClassMethods
69
77
 
@@ -96,21 +104,25 @@ module Neruda
96
104
  # o.title
97
105
  # => "New file"
98
106
  #
99
- # @param file_name [String] path to the corresponding org mode file
100
- # @param opts [Hash] optional data to initialize new org file
101
- # @option opts [String] title ('') the title of the new org file
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
102
110
  # @option opts [String] author (system user or '') the author of the
103
111
  # document
104
112
  # @option opts [Boolean] verbose (false) if the
105
113
  # {Neruda::OrgFileHtmlizer#publish publish} method should output
106
114
  # emacs process messages
115
+ # @option opts [String] project the project owning this file
116
+ # must be stored
107
117
  # @return [Neruda::OrgFile] the new instance of Neruda::OrgFile
108
118
  def initialize(file_name, opts = {})
109
119
  file_name = nil if file_name == ''
110
120
  @file = file_name
111
- @html_file = Neruda::OrgFile.html_file @file
112
- @url = Neruda::OrgFile.html_file_with_domain @file
121
+ @html_file = nil
122
+ @url = nil
123
+ @project = opts.delete :project
113
124
  @options = opts
125
+ build_html_file_and_url
114
126
  if @file && File.exist?(@file)
115
127
  extract_data
116
128
  else
@@ -166,7 +178,7 @@ module Neruda
166
178
  # @param year [Boolean] wether or not the ~:full~ format must
167
179
  # contain the year
168
180
  # @return [String] the document DateTime string representation
169
- def datestring(dateformat = :full, year = true)
181
+ def datestring(dateformat = :full, year: true)
170
182
  return '' if @date.nil?
171
183
  return R18n.l @date.to_date if dateformat == :short
172
184
  return @date.rfc3339 if dateformat == :rfc3339
@@ -202,6 +214,10 @@ module Neruda
202
214
  # - %l :: the lang of the document
203
215
  # - %L :: the license information, taken from the
204
216
  # {Neruda::Config#settings}
217
+ # - %n :: the Neruda name and version
218
+ # - %N :: the Neruda name and version with a link to the project
219
+ # home on the name
220
+ # - %s :: the subtitle of the document
205
221
  # - %t :: the title of the document
206
222
  # - %u :: the web path to the related published HTML document
207
223
  # - %x :: the raw description (eXcerpt)
@@ -213,8 +229,9 @@ module Neruda
213
229
  # => "Article written by Alice Smith the Wednesday 3rd July"
214
230
  #
215
231
  # @return [String] the given ~string~ after replacement occurs
232
+ # rubocop:disable Metrics/MethodLength
233
+ # rubocop:disable Layout/LineLength
216
234
  def format(string)
217
- license = Neruda::Config.settings['license'] || ''
218
235
  string.gsub('%a', @author)
219
236
  .gsub('%A', author_to_html)
220
237
  .gsub('%d', date_to_html(:short))
@@ -224,12 +241,17 @@ module Neruda
224
241
  .gsub('%k', @keywords.join(', '))
225
242
  .gsub('%K', keywords_to_html)
226
243
  .gsub('%l', @lang)
227
- .gsub('%L', license.gsub(/\s+/, ' ').strip)
244
+ .gsub('%L', (Neruda::Config.settings['license'] || '').gsub(/\s+/, ' ').strip)
245
+ .gsub('%n', "Neruda #{Neruda::VERSION}")
246
+ .gsub('%N', "<a href=\"https://git.umaneti.net/neruda/about/\">Neruda</a> #{Neruda::VERSION}")
247
+ .gsub('%s', @subtitle)
228
248
  .gsub('%t', @title)
229
- .gsub('%u', @html_file)
249
+ .gsub('%u', @html_file || '')
230
250
  .gsub('%x', @excerpt)
231
251
  .gsub('%X', "<p>#{@excerpt}</p>")
232
252
  end
253
+ # rubocop:enable Layout/LineLength
254
+ # rubocop:enable Metrics/MethodLength
233
255
 
234
256
  # Writes the current OrgFile content to the underlying file.
235
257
  #
@@ -246,20 +268,31 @@ module Neruda
246
268
 
247
269
  private
248
270
 
271
+ def build_html_file_and_url
272
+ return if @file.nil?
273
+ @html_file = Neruda::OrgFile.target_for_source(
274
+ @file, @project, with_public_folder: false
275
+ )
276
+ @url = "#{Neruda::Config.settings['domain']}/#{@html_file}"
277
+ end
278
+
249
279
  def init_empty_file
250
280
  @title = @options[:title] || ''
281
+ @subtitle = ''
251
282
  @date = DateTime.now
252
283
  @notime = false
253
284
  @author = @options[:author] || Neruda::Config.settings['author']
254
285
  @keywords = []
255
286
  @lang = @options[:lang] || Neruda::Config.settings['lang']
256
287
  @excerpt = ''
257
- @content = @options[:content] || <<~ORG
288
+ body = @options[:content] || ''
289
+ @content = @options[:raw_content] || <<~ORG
258
290
  #+title: #{@title}
259
291
  #+date: <#{@date.strftime('%Y-%m-%d %a. %H:%M:%S')}>
260
292
  #+author: #{@author}
261
293
  #+language: #{@lang}
262
294
 
295
+ #{body}
263
296
  ORG
264
297
  end
265
298
  end
@@ -3,33 +3,50 @@
3
3
  module Neruda
4
4
  # This module holds class methods for the {Neruda::OrgFile} class.
5
5
  module OrgFileClassMethods
6
- def html_file(file_name)
7
- return nil if file_name.nil?
8
- path = Neruda::OrgFile.target_for_source(file_name)
9
- pubfolder = Neruda::Config.settings['public_folder']
10
- path.sub(/^#{pubfolder}\//, '/')
11
- end
12
-
13
- def html_file_with_domain(file_name)
14
- return nil if file_name.nil?
15
- Neruda::Config.settings['domain'] + html_file(file_name)
16
- end
17
-
18
6
  def source_for_target(file_name)
19
7
  # file_name may be frozen...
20
8
  src = file_name.sub(/\.html$/, '.org')
21
9
  pubfolder = Neruda::Config.settings['public_folder']
22
- src.sub(/^#{pubfolder}\//, 'src/')
10
+ src.sub!(/^#{pubfolder}\//, '')
11
+ # Look for match in each possible sources. The first found wins.
12
+ Neruda::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
23
  end
24
24
 
25
- def target_for_source(file_name)
25
+ def target_for_source(file_name, project, with_public_folder: true)
26
+ return nil if file_name.nil?
26
27
  # file_name may be frozen...
27
- target = file_name.sub(/\.org$/, '.html')
28
+ target = file_name.sub(/\.org$/, '.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
28
39
  pubfolder = Neruda::Config.settings['public_folder']
29
- return target.sub(/^src\//, "#{pubfolder}/") if /^src\//.match?(target)
30
- subfolder = File.basename(File.dirname(target))
31
- leaf = File.basename(target)
32
- "#{pubfolder}/#{subfolder}/#{leaf}"
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
+ Neruda::Config.sources.each do |project|
46
+ project_relative_path = project['path'].sub(/^#{Dir.pwd}\//, '')
47
+ return project if file_name =~ /^#{project_relative_path}\//
48
+ end
49
+ nil
33
50
  end
34
51
 
35
52
  def slug(title)
@@ -10,6 +10,7 @@ module Neruda
10
10
  def extract_data
11
11
  @content = IO.read @file
12
12
  @title = extract_title
13
+ @subtitle = extract_subtitle
13
14
  @date = extract_date
14
15
  @author = extract_author
15
16
  @keywords = extract_keywords
@@ -32,7 +33,17 @@ module Neruda
32
33
 
33
34
  def extract_title
34
35
  m = /^#\+title:(.+)$/i.match(@content)
35
- return @file if m.nil?
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?
36
47
  m[1].strip
37
48
  end
38
49
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'neruda/config'
4
+ require 'neruda/emacs'
4
5
 
5
6
  module Neruda
6
7
  # This module holds HTML formatter methods for the {Neruda::OrgFile}
@@ -11,12 +12,9 @@ module Neruda
11
12
  #
12
13
  # @return [Boolean, nil] the underlying ~system~ method return value
13
14
  def publish
14
- if @file.nil?
15
- emacs_args = ['--eval \'(org-publish "website")\'']
16
- else
17
- emacs_args = ['-f org-publish-current-file']
18
- end
19
- call_emacs emacs_args
15
+ Neruda::Emacs.new(
16
+ file_path: @file, verbose: @options[:verbose]
17
+ ).publish
20
18
  end
21
19
 
22
20
  private
@@ -40,7 +38,7 @@ module Neruda
40
38
  #
41
39
  # @return [String] the HTML `time` tag
42
40
  def date_to_html(dateformat = :full)
43
- return '' if @date.nil?
41
+ return '<time></time>' if @date.nil?
44
42
  "<time datetime=\"#{@date.rfc3339}\">#{datestring(dateformat)}</time>"
45
43
  end
46
44
 
@@ -49,30 +47,7 @@ module Neruda
49
47
  #
50
48
  # @return [String] the author HTML `span`
51
49
  def author_to_html
52
- return '' if @author == ''
53
50
  "<span class=\"author\">#{@author}</span>"
54
51
  end
55
-
56
- def emacs_command(arguments = [])
57
- default_emacs = Neruda::Config.settings['emacs']
58
- emacs_cmd = [default_emacs || 'emacs -Q --batch -nw']
59
- emacs_cmd << '--eval \'(setq enable-dir-local-variables nil)\''
60
- unless @options[:verbose]
61
- emacs_cmd << '--eval \'(setq inhibit-message t)\''
62
- end
63
- emacs_cmd << '-l ./org-config.el'
64
- emacs_cmd << "--eval '(find-file \"#{@file}\")'" unless @file.nil?
65
- emacs_cmd.concat(arguments)
66
- emacs_cmd.join(' ')
67
- end
68
-
69
- def call_emacs(arguments = [])
70
- command = emacs_command arguments
71
- if @options[:verbose]
72
- warn command
73
- return system(command, exception: true)
74
- end
75
- system command, out: '/dev/null', err: '/dev/null', exception: true
76
- end
77
52
  end
78
53
  end
@@ -19,11 +19,13 @@ module Neruda # rubocop:disable Style/Documentation
19
19
  private
20
20
 
21
21
  def local_path(requested_path)
22
- routes = Neruda::Config.settings['routes'] || {}
22
+ routes = Neruda::Config.settings.dig('preview', 'routes') || {}
23
23
  return routes[requested_path] if routes.keys.include? requested_path
24
24
  local_path = Neruda::Config.settings['public_folder'] + requested_path
25
25
  if File.directory? local_path
26
- local_path = local_path.delete_suffix('/') + '/index.html'
26
+ local_path = format(
27
+ '%<path>s/index.html', path: local_path.delete_suffix('/')
28
+ )
27
29
  end
28
30
  return local_path if File.exist? local_path
29
31
  raise WEBrick::HTTPStatus::NotFound, 'Not found.'
@@ -34,8 +36,8 @@ module Neruda # rubocop:disable Style/Documentation
34
36
  return body unless local_path.match?(/\.(?:ht|x)ml$/)
35
37
  domain = Neruda::Config.settings['domain']
36
38
  return body if domain == ''
37
- body.gsub(/"file:\/\//, '"' + local_host)
38
- .gsub(/"#{domain}/, '"' + local_host)
39
+ body.gsub(/"file:\/\//, format('"%<host>s', host: local_host))
40
+ .gsub(/"#{domain}/, format('"%<host>s', host: local_host))
39
41
  end
40
42
  end
41
43
 
@@ -43,7 +45,7 @@ module Neruda # rubocop:disable Style/Documentation
43
45
  def start_preview
44
46
  # Inspired by ruby un.rb library, which allows normally to start a
45
47
  # webrick server in one line: ruby -run -e httpd public_html -p 5000
46
- port = Neruda::Config.settings['server_port'] || 5000
48
+ port = Neruda::Config.settings.dig('preview', 'server_port') || 5000
47
49
  s = WEBrick::HTTPServer.new(Port: port)
48
50
  s.mount '/', Neruda::PreviewServlet
49
51
  ['TERM', 'QUIT', 'INT'].each { |sig| trap(sig, proc { s.shutdown }) }
@@ -99,9 +99,10 @@ module Neruda
99
99
  end
100
100
 
101
101
  def insert_new_node_at(elem, content)
102
- if @position == 'before'
102
+ case @position
103
+ when 'before'
103
104
  elem.add_previous_sibling content
104
- elsif @position == 'replace'
105
+ when 'replace'
105
106
  elem.replace content
106
107
  else
107
108
  elem.add_next_sibling content
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'uri'
3
4
  require 'rainbow'
5
+ require 'net/http'
6
+ require 'r18n-core'
4
7
  require 'neruda/config'
5
8
 
6
9
  module Neruda
@@ -25,33 +28,24 @@ module Neruda
25
28
  '-a' => { long: 'author' },
26
29
  '-l' => { long: 'lang', keyword: 'LOCALE' },
27
30
  '-t' => { long: 'title' },
28
- '-p' => { long: 'path', desc: 'Path to the new file' },
29
- '-d' => { long: 'directory', boolean: true,
30
- desc: 'Wrap the new org file in a named folder' },
31
+ '-p' => { long: 'path' },
32
+ '-d' => { long: 'directory', boolean: true },
31
33
  '-v' => { long: 'verbose', boolean: true, meth: :on_tail },
32
- '-h' => { long: 'help', boolean: true, meth: :on_tail,
33
- desc: 'Display help for a command and exit' },
34
- '-V' => { long: 'version', boolean: true, meth: :on_tail,
35
- desc: 'Display Neruda version and exit' }
34
+ '-h' => { long: 'help', boolean: true, meth: :on_tail },
35
+ '-V' => { long: 'version', boolean: true, meth: :on_tail }
36
36
  }.freeze
37
37
 
38
38
  # @return [Hash] the possible ~pablo~ subcommands and their
39
39
  # configuration
40
40
  PABLO_COMMANDS = {
41
- 'init' => { opts: ['-a', '-l', '-t', '-v', '-h'],
42
- desc: 'Initialize your Neruda instance ' \
43
- '(you just need to do it once).' },
41
+ 'init' => { opts: ['-a', '-l', '-t', '-v', '-h'] },
44
42
  'config' => { alias: 'init' },
45
- 'preview' => { opts: ['-h'],
46
- desc: 'Start a test webserver to preview ' \
47
- 'your website on http://127.0.0.1:5000' },
48
- 'open' => { opts: ['-a', '-l', '-t', '-d', '-p', '-v', '-h'],
49
- desc: 'Open or create an org file for edition.' },
43
+ 'preview' => { opts: ['-h'] },
44
+ 'open' => { opts: ['-a', '-l', '-t', '-d', '-p', '-v', '-h'] },
50
45
  'edit' => { alias: 'open' },
51
- 'publish' => { opts: ['-h'],
52
- desc: 'Push local changes to your public ' \
53
- 'server online.' },
54
- 'help' => { opts: ['-h'], desc: 'Alias for the -h switch.' },
46
+ 'build' => { opts: ['-h'] },
47
+ 'publish' => { opts: ['-h'] },
48
+ 'help' => { opts: ['-h'] },
55
49
  'basic' => { opts: ['-h', '-V'], label: '<command>' }
56
50
  }.freeze
57
51
 
@@ -75,10 +69,7 @@ module Neruda
75
69
  begin
76
70
  run_and_decorate_thread thread, message, frames
77
71
  rescue RuntimeError => e
78
- done = Rainbow('An error occured.').bold.red + "\n"
79
- done += Rainbow('To see it, run again your command with more ' \
80
- 'verbosity, i.e. pablo build -v').bold
81
- warn "#{message} #{done}"
72
+ throbber_error message
82
73
  raise e
83
74
  else
84
75
  done = Rainbow('done'.ljust(frames[0].length)).green
@@ -103,7 +94,7 @@ module Neruda
103
94
  long = "--#{opt[:long]}"
104
95
  return [short, long] if opt[:boolean]
105
96
  key = opt[:keyword] || opt[:long].upcase
106
- [short + key, long + ' ' + key]
97
+ [short + key, format('%<long>s %<key>s', long: long, key: key)]
107
98
  end
108
99
 
109
100
  # Returns the ~pablo~ help summary for a given command.
@@ -115,22 +106,31 @@ module Neruda
115
106
  Neruda::Utils::PABLO_COMMANDS[command][:opts].map do |k|
116
107
  short, long = Neruda::Utils.decorate_option(k)
117
108
  opt = Neruda::Utils::PABLO_OPTIONS[k]
118
- line = ' ' + [short, long].join(', ')
119
- line = line.ljust(34) + " #{opt[:desc]}" if opt.has_key?(:desc)
120
- line + "\n"
121
- end.join
109
+ label = [short, long].join(', ')
110
+ line = [format(' %<opt>s', opt: label).ljust(30)]
111
+ if R18n.t.pablo.options[opt[:long]].translated?
112
+ line << R18n.t.pablo.options[opt[:long]]
113
+ end
114
+ line.join(' ')
115
+ end.join("\n")
122
116
  end
123
117
 
124
118
  # Returns a formatted list of available commands for ~pablo~.
125
119
  #
126
120
  # @return [String]
127
121
  def list_commands
128
- lines = ''
122
+ lines = []
129
123
  Neruda::Utils::PABLO_COMMANDS.each do |cmd, opt|
130
124
  next if cmd == 'basic'
131
- lines += " #{cmd.ljust(10)} #{opt[:desc]}\n"
125
+ line = [' ', cmd.ljust(10)]
126
+ if opt.has_key? :alias
127
+ line << R18n.t.pablo.commands.alias(opt[:alias])
128
+ else
129
+ line << R18n.t.pablo.commands[cmd]
130
+ end
131
+ lines << line.join(' ')
132
132
  end
133
- lines
133
+ lines.join("\n")
134
134
  end
135
135
 
136
136
  # Returns the real command name for a given command, which may be
@@ -148,6 +148,7 @@ module Neruda
148
148
  # Try to discover the current host operating system.
149
149
  #
150
150
  # @return [String] either apple, windows or linux (default)
151
+ # :nocov:
151
152
  def current_os
152
153
  if ENV['OS'] == 'Windows_NT' || RUBY_PLATFORM =~ /cygwin/
153
154
  return 'windows'
@@ -155,9 +156,42 @@ module Neruda
155
156
  return 'apple' if RUBY_PLATFORM =~ /darwin/
156
157
  'linux'
157
158
  end
159
+ # :nocov:
160
+
161
+ # Download latest org-mode tarball.
162
+ #
163
+ # @return [String] the downloaded org-mode version
164
+ def download_org
165
+ # :nocov:
166
+ return if Neruda::Config.org_last_version.nil?
167
+ # :nocov:
168
+ tarball = "org-#{Neruda::Config.org_last_version}.tar.gz"
169
+ dest_file = "tmp/#{tarball}"
170
+ return if File.exist?(dest_file)
171
+ uri = URI("https://orgmode.org/#{tarball}")
172
+ # Will crash on purpose if anything goes wrong
173
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http|
174
+ http.request(Net::HTTP::Get.new(uri)) do |response|
175
+ File.open(dest_file, 'w') do |io|
176
+ response.read_body { |chunk| io.write chunk }
177
+ end
178
+ end
179
+ end
180
+ end
158
181
 
159
182
  private
160
183
 
184
+ def throbber_error(message)
185
+ warn(
186
+ format(
187
+ "%<message>s %<label>s\n%<explanation>s",
188
+ message: message,
189
+ label: Rainbow(R18n.t.neruda.error.label).bold.red,
190
+ explanation: Rainbow(R18n.t.neruda.error.explanation).bold
191
+ )
192
+ )
193
+ end
194
+
161
195
  def select_throbber_frames
162
196
  model = Neruda::Config.settings['throbber'] || 'default'
163
197
  model = 'default' unless Neruda::Utils::THROBBER_FRAMES.has_key?(model)