neruda 0.1.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- @lang = Neruda::Config.settings['lang']
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,39 +3,56 @@
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)
6
+ def source_for_target(file_name)
7
+ # file_name may be frozen...
8
+ src = file_name.sub(/\.html\z/, '.org')
9
9
  pubfolder = Neruda::Config.settings['public_folder']
10
- path.sub(/^#{pubfolder}\//, '/')
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
11
23
  end
12
24
 
13
- def html_file_with_domain(file_name)
25
+ def target_for_source(file_name, project, with_public_folder: true)
14
26
  return nil if file_name.nil?
15
- Neruda::Config.settings['domain'] + html_file(file_name)
16
- end
17
-
18
- def source_for_target(file_name)
19
27
  # file_name may be frozen...
20
- src = file_name.sub(/\.html$/, '.org')
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
21
39
  pubfolder = Neruda::Config.settings['public_folder']
22
- src.sub(/^#{pubfolder}\//, 'src/')
40
+ "#{pubfolder}/#{target}"
23
41
  end
24
42
 
25
- def target_for_source(file_name)
26
- # file_name may be frozen...
27
- target = file_name.sub(/\.org$/, '.html')
28
- 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}"
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.match?(/^#{project_relative_path}\//)
48
+ end
49
+ nil
33
50
  end
34
51
 
35
52
  def slug(title)
36
- title.downcase.gsub(' ', '-')
53
+ title.downcase.tr(' ', '-')
37
54
  .encode('ascii', fallback: ->(k) { translit(k) })
38
- .gsub(/[^\w-]/, '').gsub(/-$/, '')
55
+ .gsub(/[^\w-]/, '').delete_suffix('-')
39
56
  end
40
57
 
41
58
  private
@@ -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,22 +1,19 @@
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}
7
8
  # class.
8
9
  module OrgFileHtmlizer
9
- # Publish the current file or the entire project if
10
- # {Neruda::OrgFile#file @file} is ~nil~.
10
+ # Publish the current file
11
11
  #
12
12
  # @return [Boolean, nil] the underlying ~system~ method return value
13
13
  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
14
+ Neruda::Emacs.new(
15
+ file_path: @file, verbose: @options[:verbose]
16
+ ).publish
20
17
  end
21
18
 
22
19
  private
@@ -40,7 +37,7 @@ module Neruda
40
37
  #
41
38
  # @return [String] the HTML `time` tag
42
39
  def date_to_html(dateformat = :full)
43
- return '' if @date.nil?
40
+ return '<time></time>' if @date.nil?
44
41
  "<time datetime=\"#{@date.rfc3339}\">#{datestring(dateformat)}</time>"
45
42
  end
46
43
 
@@ -49,30 +46,7 @@ module Neruda
49
46
  #
50
47
  # @return [String] the author HTML `span`
51
48
  def author_to_html
52
- return '' if @author == ''
53
49
  "<span class=\"author\">#{@author}</span>"
54
50
  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)
74
- end
75
- system command, out: '/dev/null', err: '/dev/null'
76
- end
77
51
  end
78
52
  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'] || {}
23
- return routes[requested_path] if routes.keys.include? requested_path
22
+ routes = Neruda::Config.settings.dig('preview', 'routes') || {}
23
+ return routes[requested_path] if routes.has_key? 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.'
@@ -31,11 +33,11 @@ module Neruda # rubocop:disable Style/Documentation
31
33
 
32
34
  def parse_body(local_path, local_host)
33
35
  body = IO.read local_path
34
- return body unless local_path.match?(/\.(?:ht|x)ml$/)
36
+ return body unless local_path.match?(/\.(?:ht|x)ml\z/)
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
@@ -23,30 +26,27 @@ module Neruda
23
26
  # configuration
24
27
  PABLO_OPTIONS = {
25
28
  '-a' => { long: 'author' },
29
+ '-d' => { long: 'directory', boolean: true },
30
+ '-f' => { long: 'force', boolean: true },
31
+ '-h' => { long: 'help', boolean: true, meth: :on_tail },
26
32
  '-l' => { long: 'lang', keyword: 'LOCALE' },
33
+ '-p' => { long: 'path' },
27
34
  '-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
35
  '-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' }
36
+ '-V' => { long: 'version', boolean: true, meth: :on_tail }
36
37
  }.freeze
37
38
 
38
39
  # @return [Hash] the possible ~pablo~ subcommands and their
39
40
  # configuration
40
41
  PABLO_COMMANDS = {
41
- 'init' => { opts: ['-a', '-l', '-t', '-v', '-h'],
42
- desc: 'Initialize your Neruda instance ' \
43
- '(you just need to do it once).' },
44
- 'preview' => { opts: ['-h'],
45
- desc: 'Start a test webserver to preview ' \
46
- 'your website on http://127.0.0.1:5000' },
47
- 'open' => { opts: ['-a', '-l', '-t', '-d', '-p', '-v', '-h'],
48
- desc: 'Open or create an org file for edition.' },
49
- 'help' => { opts: ['-h'], desc: 'Alias for the -h switch.' },
42
+ 'init' => { opts: ['-a', '-h', '-l', '-t', '-v'] },
43
+ 'config' => { alias: 'init' },
44
+ 'preview' => { opts: ['-h'] },
45
+ 'open' => { opts: ['-a', '-d', '-h', '-l', '-p', '-t', '-v'] },
46
+ 'edit' => { alias: 'open' },
47
+ 'build' => { opts: ['-f', '-h'] },
48
+ 'publish' => { opts: ['-h'] },
49
+ 'help' => { opts: ['-h'] },
50
50
  'basic' => { opts: ['-h', '-V'], label: '<command>' }
51
51
  }.freeze
52
52
 
@@ -66,17 +66,16 @@ module Neruda
66
66
  # @param message [String] the message to display before the throbber
67
67
  # @return [void]
68
68
  def throbber(thread, message)
69
- model = Neruda::Config.settings['throbber'] || 'default'
70
- model = 'default' unless Neruda::Utils::THROBBER_FRAMES.has_key?(model)
71
- frames = Neruda::Utils::THROBBER_FRAMES[model]
72
- current = 0
73
- while thread.alive?
74
- sleep 0.1
75
- print "#{message} #{frames[current % frames.length]}\r"
76
- current += 1
69
+ frames = select_throbber_frames
70
+ begin
71
+ run_and_decorate_thread thread, message, frames
72
+ rescue RuntimeError => e
73
+ throbber_error message
74
+ raise e
75
+ else
76
+ done = Rainbow('done'.ljust(frames[0].length)).green
77
+ puts "#{message} #{done}"
77
78
  end
78
- done = Rainbow('done'.ljust(frames[0].length)).green
79
- puts "#{message} #{done}"
80
79
  end
81
80
 
82
81
  # Returns the short and long options specification for a given
@@ -95,8 +94,8 @@ module Neruda
95
94
  opt = Neruda::Utils::PABLO_OPTIONS[short]
96
95
  long = "--#{opt[:long]}"
97
96
  return [short, long] if opt[:boolean]
98
- key = ' ' + (opt[:keyword] || opt[:long].upcase)
99
- [short + key, long + key]
97
+ key = opt[:keyword] || opt[:long].upcase
98
+ [short + key, format('%<long>s %<key>s', long: long, key: key)]
100
99
  end
101
100
 
102
101
  # Returns the ~pablo~ help summary for a given command.
@@ -108,22 +107,108 @@ module Neruda
108
107
  Neruda::Utils::PABLO_COMMANDS[command][:opts].map do |k|
109
108
  short, long = Neruda::Utils.decorate_option(k)
110
109
  opt = Neruda::Utils::PABLO_OPTIONS[k]
111
- line = ' ' + [short, long].join(', ')
112
- line = line.ljust(34) + " #{opt[:desc]}" if opt.has_key?(:desc)
113
- line + "\n"
114
- end.join
110
+ label = [short, long].join(', ')
111
+ line = [format(' %<opt>s', opt: label).ljust(30)]
112
+ if R18n.t.pablo.options[opt[:long]].translated?
113
+ line << R18n.t.pablo.options[opt[:long]]
114
+ end
115
+ line.join(' ')
116
+ end.join("\n")
115
117
  end
116
118
 
117
119
  # Returns a formatted list of available commands for ~pablo~.
118
120
  #
119
121
  # @return [String]
120
122
  def list_commands
121
- lines = ''
123
+ lines = []
122
124
  Neruda::Utils::PABLO_COMMANDS.each do |cmd, opt|
123
125
  next if cmd == 'basic'
124
- lines += " #{cmd.ljust(10)} #{opt[:desc]}\n"
126
+ line = [' ', cmd.ljust(10)]
127
+ if opt.has_key? :alias
128
+ line << R18n.t.pablo.commands.alias(opt[:alias])
129
+ else
130
+ line << R18n.t.pablo.commands[cmd]
131
+ end
132
+ lines << line.join(' ')
133
+ end
134
+ lines.join("\n")
135
+ end
136
+
137
+ # Returns the real command name for a given command, which may be
138
+ # an alias.
139
+ #
140
+ # @param command [String] the command to resolve
141
+ # @return [String]
142
+ def resolve_possible_alias(command)
143
+ return 'basic' unless Neruda::Utils::PABLO_COMMANDS.include?(command)
144
+ cmd_opt = Neruda::Utils::PABLO_COMMANDS[command]
145
+ return cmd_opt[:alias] if cmd_opt.has_key?(:alias)
146
+ command
147
+ end
148
+
149
+ # Try to discover the current host operating system.
150
+ #
151
+ # @return [String] either apple, windows or linux (default)
152
+ # :nocov:
153
+ def current_os
154
+ if ENV['OS'] == 'Windows_NT' || RUBY_PLATFORM.include?('cygwin')
155
+ return 'windows'
156
+ end
157
+ return 'apple' if RUBY_PLATFORM.include?('darwin')
158
+ 'linux'
159
+ end
160
+ # :nocov:
161
+
162
+ # Download latest org-mode tarball.
163
+ #
164
+ # @return [String] the downloaded org-mode version
165
+ def download_org
166
+ # :nocov:
167
+ return if Neruda::Config.org_last_version.nil?
168
+ # :nocov:
169
+ tarball = "org-#{Neruda::Config.org_last_version}.tar.gz"
170
+ # Remove version number in dest file to allow easy rake file
171
+ # task naming
172
+ dest_file = 'tmp/org.tar.gz'
173
+ return if File.exist?(dest_file)
174
+ uri = URI("https://orgmode.org/#{tarball}")
175
+ # Will crash on purpose if anything goes wrong
176
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http|
177
+ http.request(Net::HTTP::Get.new(uri)) do |response|
178
+ File.open(dest_file, 'w') do |io|
179
+ response.read_body { |chunk| io.write chunk }
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ private
186
+
187
+ def throbber_error(message)
188
+ warn(
189
+ format(
190
+ "%<message>s %<label>s\n%<explanation>s",
191
+ message: message,
192
+ label: Rainbow(R18n.t.neruda.error.label).bold.red,
193
+ explanation: Rainbow(R18n.t.neruda.error.explanation).bold
194
+ )
195
+ )
196
+ end
197
+
198
+ def select_throbber_frames
199
+ model = Neruda::Config.settings['throbber'] || 'default'
200
+ model = 'default' unless Neruda::Utils::THROBBER_FRAMES.has_key?(model)
201
+ Neruda::Utils::THROBBER_FRAMES[model]
202
+ end
203
+
204
+ def run_and_decorate_thread(thread, message, frames)
205
+ thread.abort_on_exception = true
206
+ current = 0
207
+ while thread.alive?
208
+ sleep 0.1
209
+ print "#{message} #{frames[current % frames.length]}\r"
210
+ current += 1
125
211
  end
126
- lines
127
212
  end
128
213
  end
129
214
  end