neruda 0.0.9 → 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.
Files changed (45) hide show
  1. checksums.yaml +5 -5
  2. data/bin/pablo +135 -238
  3. data/lib/neruda/config.rb +137 -0
  4. data/lib/neruda/config/lisp_config.rb +254 -0
  5. data/lib/neruda/config/org-config.el +18 -0
  6. data/lib/neruda/config/ox-neruda.el +114 -0
  7. data/lib/neruda/emacs.rb +44 -0
  8. data/lib/neruda/index.rb +122 -0
  9. data/lib/neruda/index/atom_generator.rb +86 -0
  10. data/lib/neruda/index/org_generator.rb +115 -0
  11. data/lib/neruda/org_file.rb +299 -0
  12. data/lib/neruda/org_file/class_methods.rb +72 -0
  13. data/lib/neruda/org_file/extracter.rb +72 -0
  14. data/lib/neruda/org_file/htmlizer.rb +53 -0
  15. data/lib/neruda/preview.rb +55 -0
  16. data/lib/neruda/templater.rb +112 -0
  17. data/lib/neruda/utils.rb +212 -0
  18. data/lib/neruda/version.rb +6 -0
  19. data/lib/tasks/org.rake +84 -0
  20. data/lib/tasks/site.rake +86 -0
  21. data/lib/tasks/sync.rake +34 -0
  22. data/lib/tasks/tags.rake +19 -0
  23. data/locales/en.yml +37 -0
  24. data/locales/fr.yml +37 -0
  25. data/themes/default/css/htmlize.css +346 -0
  26. data/themes/default/css/style.css +153 -0
  27. data/themes/default/img/bottom.png +0 -0
  28. data/themes/default/img/tic.png +0 -0
  29. data/themes/default/img/top.png +0 -0
  30. metadata +153 -43
  31. data/README.md +0 -98
  32. data/docs/Rakefile.example +0 -4
  33. data/docs/config.yml.example +0 -17
  34. data/lib/assets/chapter.slim +0 -14
  35. data/lib/assets/index.slim +0 -13
  36. data/lib/assets/layout.slim +0 -17
  37. data/lib/assets/style.css +0 -199
  38. data/lib/neruda.rb +0 -106
  39. data/lib/neruda/chapter.rb +0 -26
  40. data/lib/neruda/url.rb +0 -14
  41. data/lib/tasks/book.rake +0 -60
  42. data/lib/tasks/capistrano/chapters.rake +0 -60
  43. data/lib/tasks/capistrano/sinatra.rake +0 -18
  44. data/lib/tasks/chapters.rake +0 -132
  45. data/lib/tasks/sinatra.rake +0 -36
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neruda
4
+ # This module holds class methods for the {Neruda::OrgFile} class.
5
+ module OrgFileClassMethods
6
+ def source_for_target(file_name)
7
+ # file_name may be frozen...
8
+ src = file_name.sub(/\.html$/, '.org')
9
+ pubfolder = Neruda::Config.settings['public_folder']
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
+ 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$/, '.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 = Neruda::Config.settings['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
+ 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
50
+ end
51
+
52
+ def slug(title)
53
+ title.downcase.gsub(' ', '-')
54
+ .encode('ascii', fallback: ->(k) { translit(k) })
55
+ .gsub(/[^\w-]/, '').gsub(/-$/, '')
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
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neruda
4
+ # This module holds extracter methods for the {Neruda::OrgFile} class.
5
+ module OrgFileExtracter
6
+ private
7
+
8
+ # Main method, which will call the other to initialize an
9
+ # {Neruda::OrgFile} instance.
10
+ def extract_data
11
+ @content = IO.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 Neruda::Config.settings['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 Neruda::Config.settings['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
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'neruda/config'
4
+ require 'neruda/emacs'
5
+
6
+ module Neruda
7
+ # This module holds HTML formatter methods for the {Neruda::OrgFile}
8
+ # class.
9
+ module OrgFileHtmlizer
10
+ # Publish the current file or the entire project if
11
+ # {Neruda::OrgFile#file @file} is ~nil~.
12
+ #
13
+ # @return [Boolean, nil] the underlying ~system~ method return value
14
+ def publish
15
+ Neruda::Emacs.new(
16
+ file_path: @file, verbose: @options[:verbose]
17
+ ).publish
18
+ end
19
+
20
+ private
21
+
22
+ # Format {Neruda::OrgFile#keywords} list in an HTML listing.
23
+ #
24
+ # @return [String] the HTML keywords list
25
+ def keywords_to_html
26
+ domain = Neruda::Config.settings['domain']
27
+ klist = @keywords.map do |k|
28
+ <<~KEYWORDLINK
29
+ <li class="keyword">
30
+ <a href="#{domain}/tags/#{Neruda::OrgFile.slug(k)}.html">#{k}</a>
31
+ </li>
32
+ KEYWORDLINK
33
+ end.join
34
+ "<ul class=\"keywords-list\">#{klist}</ul>"
35
+ end
36
+
37
+ # Format {Neruda::OrgFile#date} as a HTML `time` tag.
38
+ #
39
+ # @return [String] the HTML `time` tag
40
+ def date_to_html(dateformat = :full)
41
+ return '<time></time>' if @date.nil?
42
+ "<time datetime=\"#{@date.rfc3339}\">#{datestring(dateformat)}</time>"
43
+ end
44
+
45
+ # Format {Neruda::OrgFile#author} in a HTML `span` tag with a
46
+ # specific class.
47
+ #
48
+ # @return [String] the author HTML `span`
49
+ def author_to_html
50
+ "<span class=\"author\">#{@author}</span>"
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'webrick'
4
+ require 'neruda/config'
5
+
6
+ module Neruda # rubocop:disable Style/Documentation
7
+ # A tiny preview server, which main goal is to replace references to
8
+ # the target domain by localhost.
9
+ class PreviewServlet < WEBrick::HTTPServlet::AbstractServlet
10
+ include WEBrick::HTTPUtils
11
+
12
+ def do_GET(request, response) # rubocop:disable Naming/MethodName
13
+ file = local_path(request.path)
14
+ response.body = parse_body(file, "http://#{request.host}:#{request.port}")
15
+ response.status = 200
16
+ response.content_type = mime_type(file, DefaultMimeTypes)
17
+ end
18
+
19
+ private
20
+
21
+ def local_path(requested_path)
22
+ routes = Neruda::Config.settings.dig('preview', 'routes') || {}
23
+ return routes[requested_path] if routes.keys.include? requested_path
24
+ local_path = Neruda::Config.settings['public_folder'] + requested_path
25
+ if File.directory? local_path
26
+ local_path = format(
27
+ '%<path>s/index.html', path: local_path.delete_suffix('/')
28
+ )
29
+ end
30
+ return local_path if File.exist? local_path
31
+ raise WEBrick::HTTPStatus::NotFound, 'Not found.'
32
+ end
33
+
34
+ def parse_body(local_path, local_host)
35
+ body = IO.read local_path
36
+ return body unless local_path.match?(/\.(?:ht|x)ml$/)
37
+ domain = Neruda::Config.settings['domain']
38
+ return body if domain == ''
39
+ body.gsub(/"file:\/\//, format('"%<host>s', host: local_host))
40
+ .gsub(/"#{domain}/, format('"%<host>s', host: local_host))
41
+ end
42
+ end
43
+
44
+ class << self
45
+ def start_preview
46
+ # Inspired by ruby un.rb library, which allows normally to start a
47
+ # webrick server in one line: ruby -run -e httpd public_html -p 5000
48
+ port = Neruda::Config.settings.dig('preview', 'server_port') || 5000
49
+ s = WEBrick::HTTPServer.new(Port: port)
50
+ s.mount '/', Neruda::PreviewServlet
51
+ ['TERM', 'QUIT', 'INT'].each { |sig| trap(sig, proc { s.shutdown }) }
52
+ s.start
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ require 'digest/md5'
5
+ require 'neruda/org_file'
6
+
7
+ module Neruda
8
+ # Insert custom part inside generated HTML files.
9
+ class Templater
10
+ def initialize(source, dom, opts = {})
11
+ @dom = dom
12
+ @org_file = source
13
+ @position = opts['type'] || 'after'
14
+ @content = opts['content']
15
+ @element = @dom.css(opts['selector'])
16
+ digest = Digest::MD5.hexdigest(@content)
17
+ @check_line = " Neruda Template: #{digest} "
18
+ end
19
+
20
+ def apply
21
+ flag_head
22
+ content = @org_file.format(@content)
23
+ @element.each do |e|
24
+ insert_new_node_at e, content
25
+ end
26
+ end
27
+
28
+ def in_head?
29
+ @dom.xpath('//head').children.to_a.filter(&:comment?).each do |c|
30
+ return true if c.text == @check_line
31
+ end
32
+ false
33
+ end
34
+
35
+ class << self
36
+ def customize_output(file_name, source = nil)
37
+ templates_to_apply = filter_templates(file_name)
38
+ return if templates_to_apply.empty?
39
+ if source.nil?
40
+ sourcepath = Neruda::OrgFile.source_for_target(file_name)
41
+ source = Neruda::OrgFile.new(sourcepath)
42
+ end
43
+ dom = open_dom(file_name)
44
+ templates_to_apply.each do |t|
45
+ tpl = Neruda::Templater.new(source, dom, t)
46
+ next if tpl.in_head?
47
+ tpl.apply
48
+ end
49
+ write_dom(file_name, dom)
50
+ end
51
+
52
+ private
53
+
54
+ def filter_templates(file_name)
55
+ templates = Neruda::Config.settings['templates']
56
+ return [] if templates.nil? || templates.empty?
57
+ templates.filter do |t|
58
+ if !t.has_key?('selector') || !t.has_key?('content')
59
+ false
60
+ elsif t.has_key?('path') && !check_path(file_name, t['path'])
61
+ false
62
+ else
63
+ true
64
+ end
65
+ end
66
+ end
67
+
68
+ def open_dom(file_name)
69
+ file = File.new file_name, 'r'
70
+ dom = Nokogiri::HTML file
71
+ file.close
72
+ dom
73
+ end
74
+
75
+ def write_dom(file_name, dom)
76
+ file = File.new file_name, 'w'
77
+ dom.write_to file
78
+ file.close
79
+ end
80
+
81
+ def check_path(file_name, pathes)
82
+ pub_folder = Neruda::Config.settings['public_folder']
83
+ if pathes.is_a?(Array)
84
+ pathes.each do |tp|
85
+ return true if File.fnmatch?("#{pub_folder}#{tp}",
86
+ file_name, File::FNM_DOTMATCH)
87
+ end
88
+ return false
89
+ end
90
+ File.fnmatch?("#{pub_folder}#{pathes}",
91
+ file_name, File::FNM_DOTMATCH)
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def flag_head
98
+ @dom.xpath('//head').first.prepend_child("<!--#{@check_line}-->\n")
99
+ end
100
+
101
+ def insert_new_node_at(elem, content)
102
+ case @position
103
+ when 'before'
104
+ elem.add_previous_sibling content
105
+ when 'replace'
106
+ elem.replace content
107
+ else
108
+ elem.add_next_sibling content
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'rainbow'
5
+ require 'net/http'
6
+ require 'r18n-core'
7
+ require 'neruda/config'
8
+
9
+ module Neruda
10
+ # Embeds usefull methods, mainly used in rake tasks.
11
+ module Utils
12
+ # @return [Hash] the possible throbber themes
13
+ THROBBER_FRAMES = {
14
+ 'basic' => '-\|/',
15
+ 'basicdots' => '⋯⋱⋮⋰',
16
+ 'moon' => '🌑🌒🌓🌔🌕🌖🌗🌘',
17
+ 'clock' => '🕛🕐🕑🕒🕓🕔🕕🕖🕗🕘🕙🕚',
18
+ 'bricks' => '⣷⣯⣟⡿⢿⣻⣽⣾',
19
+ 'points' => '·⁘∷⁛∷⁘',
20
+ 'quadrant2' => '▙▛▜▟',
21
+ 'default' => ['⠁ ⠂ ⠄ ⡀ ⠄ ⠂ ⠁', '⠂ ⠁ ⠂ ⠄ ⡀ ⠄ ⠂', '⠄ ⠂ ⠁ ⠂ ⠄ ⡀ ⠄',
22
+ '⡀ ⠄ ⠂ ⠁ ⠂ ⠄ ⡀', '⠄ ⡀ ⠄ ⠂ ⠁ ⠂ ⠄', '⠂ ⠄ ⡀ ⠄ ⠂ ⠁ ⠂']
23
+ }.freeze
24
+
25
+ # @return [Hash] the possible ~pablo~ options and their
26
+ # configuration
27
+ PABLO_OPTIONS = {
28
+ '-a' => { long: 'author' },
29
+ '-l' => { long: 'lang', keyword: 'LOCALE' },
30
+ '-t' => { long: 'title' },
31
+ '-p' => { long: 'path' },
32
+ '-d' => { long: 'directory', boolean: true },
33
+ '-v' => { long: 'verbose', boolean: true, meth: :on_tail },
34
+ '-h' => { long: 'help', boolean: true, meth: :on_tail },
35
+ '-V' => { long: 'version', boolean: true, meth: :on_tail }
36
+ }.freeze
37
+
38
+ # @return [Hash] the possible ~pablo~ subcommands and their
39
+ # configuration
40
+ PABLO_COMMANDS = {
41
+ 'init' => { opts: ['-a', '-l', '-t', '-v', '-h'] },
42
+ 'config' => { alias: 'init' },
43
+ 'preview' => { opts: ['-h'] },
44
+ 'open' => { opts: ['-a', '-l', '-t', '-d', '-p', '-v', '-h'] },
45
+ 'edit' => { alias: 'open' },
46
+ 'build' => { opts: ['-h'] },
47
+ 'publish' => { opts: ['-h'] },
48
+ 'help' => { opts: ['-h'] },
49
+ 'basic' => { opts: ['-h', '-V'], label: '<command>' }
50
+ }.freeze
51
+
52
+ class << self
53
+ # Animates strings in the user console to alert him that something
54
+ # is running in the background.
55
+ #
56
+ # The animation is chosen among a bunch of themes, with the
57
+ # configuration option ~throbber~ (retrieved via
58
+ # {Neruda::Config#settings}).
59
+ #
60
+ # @example
61
+ # long_stuff = Thread.new { very_long_operation }
62
+ # Neruda::Utils.throbber(long_stuff, 'Computing hard stuff:')
63
+ #
64
+ # @param thread [Thread] the long-running operation to decorate
65
+ # @param message [String] the message to display before the throbber
66
+ # @return [void]
67
+ def throbber(thread, message)
68
+ frames = select_throbber_frames
69
+ begin
70
+ run_and_decorate_thread thread, message, frames
71
+ rescue RuntimeError => e
72
+ throbber_error message
73
+ raise e
74
+ else
75
+ done = Rainbow('done'.ljust(frames[0].length)).green
76
+ puts "#{message} #{done}"
77
+ end
78
+ end
79
+
80
+ # Returns the short and long options specification for a given
81
+ # short option.
82
+ #
83
+ # This method use the {Neruda::Utils::PABLO_OPTIONS} Hash to
84
+ # retrieve corresponding values.
85
+ #
86
+ # @example
87
+ # spec = Neruda::Utils.decorate_option('-a')
88
+ # => ['-a AUTHOR', '--author AUTHOR']
89
+ #
90
+ # @param short [String] the short option to decorate
91
+ # @return [Array] the short and long specification for an option
92
+ def decorate_option(short)
93
+ opt = Neruda::Utils::PABLO_OPTIONS[short]
94
+ long = "--#{opt[:long]}"
95
+ return [short, long] if opt[:boolean]
96
+ key = opt[:keyword] || opt[:long].upcase
97
+ [short + key, format('%<long>s %<key>s', long: long, key: key)]
98
+ end
99
+
100
+ # Returns the ~pablo~ help summary for a given command.
101
+ #
102
+ # @param command [String] the command for which a summary
103
+ # should be given
104
+ # @return [String]
105
+ def summarize_command(command)
106
+ Neruda::Utils::PABLO_COMMANDS[command][:opts].map do |k|
107
+ short, long = Neruda::Utils.decorate_option(k)
108
+ opt = Neruda::Utils::PABLO_OPTIONS[k]
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")
116
+ end
117
+
118
+ # Returns a formatted list of available commands for ~pablo~.
119
+ #
120
+ # @return [String]
121
+ def list_commands
122
+ lines = []
123
+ Neruda::Utils::PABLO_COMMANDS.each do |cmd, opt|
124
+ next if cmd == 'basic'
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
+ end
133
+ lines.join("\n")
134
+ end
135
+
136
+ # Returns the real command name for a given command, which may be
137
+ # an alias.
138
+ #
139
+ # @param command [String] the command to resolve
140
+ # @return [String]
141
+ def resolve_possible_alias(command)
142
+ return 'basic' unless Neruda::Utils::PABLO_COMMANDS.include?(command)
143
+ cmd_opt = Neruda::Utils::PABLO_COMMANDS[command]
144
+ return cmd_opt[:alias] if cmd_opt.has_key?(:alias)
145
+ command
146
+ end
147
+
148
+ # Try to discover the current host operating system.
149
+ #
150
+ # @return [String] either apple, windows or linux (default)
151
+ # :nocov:
152
+ def current_os
153
+ if ENV['OS'] == 'Windows_NT' || RUBY_PLATFORM =~ /cygwin/
154
+ return 'windows'
155
+ end
156
+ return 'apple' if RUBY_PLATFORM =~ /darwin/
157
+ 'linux'
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
181
+
182
+ private
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
+
195
+ def select_throbber_frames
196
+ model = Neruda::Config.settings['throbber'] || 'default'
197
+ model = 'default' unless Neruda::Utils::THROBBER_FRAMES.has_key?(model)
198
+ Neruda::Utils::THROBBER_FRAMES[model]
199
+ end
200
+
201
+ def run_and_decorate_thread(thread, message, frames)
202
+ thread.abort_on_exception = true
203
+ current = 0
204
+ while thread.alive?
205
+ sleep 0.1
206
+ print "#{message} #{frames[current % frames.length]}\r"
207
+ current += 1
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end