neruda 0.0.9 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/bin/pablo +135 -238
- data/lib/neruda/config.rb +137 -0
- data/lib/neruda/config/lisp_config.rb +254 -0
- data/lib/neruda/config/org-config.el +18 -0
- data/lib/neruda/config/ox-neruda.el +114 -0
- data/lib/neruda/emacs.rb +44 -0
- data/lib/neruda/index.rb +122 -0
- data/lib/neruda/index/atom_generator.rb +86 -0
- data/lib/neruda/index/org_generator.rb +115 -0
- data/lib/neruda/org_file.rb +299 -0
- data/lib/neruda/org_file/class_methods.rb +72 -0
- data/lib/neruda/org_file/extracter.rb +72 -0
- data/lib/neruda/org_file/htmlizer.rb +53 -0
- data/lib/neruda/preview.rb +55 -0
- data/lib/neruda/templater.rb +112 -0
- data/lib/neruda/utils.rb +212 -0
- data/lib/neruda/version.rb +6 -0
- data/lib/tasks/org.rake +84 -0
- data/lib/tasks/site.rake +86 -0
- data/lib/tasks/sync.rake +34 -0
- data/lib/tasks/tags.rake +19 -0
- data/locales/en.yml +37 -0
- data/locales/fr.yml +37 -0
- data/themes/default/css/htmlize.css +346 -0
- data/themes/default/css/style.css +153 -0
- data/themes/default/img/bottom.png +0 -0
- data/themes/default/img/tic.png +0 -0
- data/themes/default/img/top.png +0 -0
- metadata +153 -43
- data/README.md +0 -98
- data/docs/Rakefile.example +0 -4
- data/docs/config.yml.example +0 -17
- data/lib/assets/chapter.slim +0 -14
- data/lib/assets/index.slim +0 -13
- data/lib/assets/layout.slim +0 -17
- data/lib/assets/style.css +0 -199
- data/lib/neruda.rb +0 -106
- data/lib/neruda/chapter.rb +0 -26
- data/lib/neruda/url.rb +0 -14
- data/lib/tasks/book.rake +0 -60
- data/lib/tasks/capistrano/chapters.rake +0 -60
- data/lib/tasks/capistrano/sinatra.rake +0 -18
- data/lib/tasks/chapters.rake +0 -132
- 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
|
data/lib/neruda/utils.rb
ADDED
@@ -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
|