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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de0736f77b1fd461b05781a1afee1775cc0e0bf8fea098f14223475a28fb5282
4
- data.tar.gz: c3443f328c833e1591631243f4df6e7ff29dbc2fcceb3889bc0d0f763b41662f
3
+ metadata.gz: bf44dce40376682ca2835d1472ccd7eb2ea47d5e7b892623da09350077bf4a10
4
+ data.tar.gz: fcabfb117f2ac44a76fc325a84982be6dd6e389872f4fb6bfbd10bf05924d882
5
5
  SHA512:
6
- metadata.gz: 6239d642c54843825f9009514772452200acfd70ca3b65ed564b761fc24ae885ec7b0bf852b8bd947c8b24d325013342c3047019b83f33f98067d0162c0b3bef
7
- data.tar.gz: bd3528ca312d8435b360fb34436817e4ffbea7e3bd758a90ea74b5713d7ec457ee1844dc443459705f6c0c94c22aaaad313b8d6b8565cfacc6a4a309c33e63a2
6
+ metadata.gz: 7113b65a71b026c419a6de56d7ba2b84cdf9fbf6b02a977736dbad0484a225c2d97acb4df906c957e51a9c3b968b3260fb748856630fc534c4f30a7d5aa280d7
7
+ data.tar.gz: 1e478aa7c7c9ac80cb9293fb873baeadbfa25864cffb81460ac5e727b46360228f5eeb0f525116e88b35a537a2ae07c94f5b373e037095168c1b929ccc07bb9b
data/bin/pablo CHANGED
@@ -7,8 +7,8 @@ require 'r18n-core'
7
7
  require 'neruda/utils'
8
8
  require 'neruda/version'
9
9
 
10
- R18n.set(Neruda::Config.settings['lang'],
11
- File.expand_path('../locales', __dir__))
10
+ R18n.default_places = File.expand_path('../locales', __dir__)
11
+ R18n.set Neruda::Config.settings['lang']
12
12
 
13
13
  # Pablo commands
14
14
  module PabloCommands
@@ -20,8 +20,8 @@ module PabloCommands
20
20
  Neruda::Config.save(Neruda::Config.settings.merge(cnf))
21
21
  @rake.options.build_all = true
22
22
  @rake.invoke_task('org:install')
23
- o = Neruda::OrgFile.new('src/index.org', @options)
24
- o.write unless File.exist? 'src/index.org'
23
+ return if File.exist? 'src/index.org'
24
+ Neruda::OrgFile.new('src/index.org', @options).write
25
25
  end
26
26
  alias_method :pablo_config, :pablo_init
27
27
 
@@ -37,12 +37,13 @@ module PabloCommands
37
37
  def pablo_preview
38
38
  Thread.new do
39
39
  sleep 1
40
- port = Neruda::Config.settings['server_port'] || 5000
40
+ port = Neruda::Config.settings.dig('preview', 'server_port') || 5000
41
41
  uri = "http://127.0.0.1:#{port}/"
42
42
  current_os = Neruda::Utils.current_os
43
- if current_os == 'windows'
43
+ case current_os
44
+ when 'windows'
44
45
  system 'start', uri
45
- elsif current_os == 'apple'
46
+ when 'apple'
46
47
  system 'open', uri
47
48
  else
48
49
  system 'gio', 'open', uri
@@ -57,8 +58,7 @@ module PabloCommands
57
58
  o = Neruda::OrgFile.new(filename, @options)
58
59
  o.write unless File.exist? filename
59
60
  editor = ENV['EDITOR'] || ENV['VISUAL'] || 'emacs'
60
- cmd = [editor, filename]
61
- cmd.insert(1, '+6') if editor.match?(/^emacs/)
61
+ cmd = [editor, '+6', filename]
62
62
  system(*cmd)
63
63
  end
64
64
  alias_method :pablo_edit, :pablo_open
@@ -67,15 +67,16 @@ module PabloCommands
67
67
  @rake.invoke_task('sync:push')
68
68
  end
69
69
 
70
- def pablo_help(command = 'basic', error = false)
70
+ def pablo_help(command = 'basic', error: false)
71
71
  warn R18n.t.pablo.error.no_command if error
72
72
  cmd = Neruda::Utils.resolve_possible_alias(command)
73
73
  cmd_opt = Neruda::Utils::PABLO_COMMANDS[cmd]
74
74
  label = cmd_opt[:label] || command
75
- warn "Usage: pablo #{label} [options]\n\n"
76
- warn cmd_opt[:desc] + "\n\n" if cmd_opt.has_key?(:desc)
77
- warn "Options\n#{Neruda::Utils.summarize_command(cmd)}\n"
78
- warn "Commands\n#{Neruda::Utils.list_commands}\n" if cmd == 'basic'
75
+ warn format("%<label>s\n\n", label: R18n.t.pablo.usage(label))
76
+ if R18n.t.pablo.commands[cmd].translated?
77
+ warn format("%<label>s\n\n", label: R18n.t.pablo.commands[cmd])
78
+ end
79
+ warn help_command_body(cmd).join("\n")
79
80
  exit 1 if error
80
81
  exit
81
82
  end
@@ -89,6 +90,19 @@ module PabloCommands
89
90
  return "#{filename}.org" unless @options[:directory]
90
91
  "#{filename}/index.org"
91
92
  end
93
+
94
+ def help_command_body(command)
95
+ body = [
96
+ R18n.t.pablo.options.cmd_title,
97
+ Neruda::Utils.summarize_command(command)
98
+ ]
99
+ return body unless command == 'basic'
100
+ body + [
101
+ '',
102
+ R18n.t.pablo.commands.cmd_title,
103
+ Neruda::Utils.list_commands
104
+ ]
105
+ end
92
106
  end
93
107
 
94
108
  # Main pablo class
@@ -117,8 +131,8 @@ class Pablo
117
131
  require 'r18n-core'
118
132
 
119
133
  neruda_spec = Gem::Specification.find_by_name 'neruda'
120
- R18n.set(Neruda::Config.settings['lang'] || 'en',
121
- "\#{neruda_spec.gem_dir}/locales")
134
+ R18n.default_places = "\#{neruda_spec.gem_dir}/locales"
135
+ R18n.set(Neruda::Config.settings['lang'] || 'en')
122
136
  R18n::Filters.on(:named_variables)
123
137
 
124
138
  Dir.glob("\#{neruda_spec.gem_dir}/lib/tasks/*.rake").each { |r| import r }
@@ -153,10 +167,10 @@ command = "pablo_#{ARGV[0]}".to_sym
153
167
  cmd_err = !pablo.respond_to?(command)
154
168
  if params[:help] || cmd_err
155
169
  cmd_err = false if params[:help] && !ARGV[0]
156
- pablo.pablo_help(ARGV[0], cmd_err)
170
+ pablo.pablo_help(ARGV[0], error: cmd_err)
157
171
  end
158
172
  ARGV.shift
159
173
 
160
- init_cmds = ['pablo_init', 'pablo_config']
174
+ init_cmds = [:pablo_init, :pablo_config]
161
175
  pablo.pablo_init unless File.exist?('config.yml') || init_cmds.include?(command)
162
176
  pablo.send command
@@ -53,8 +53,17 @@ module Neruda
53
53
  # @param new_config [Hash] the settings to save
54
54
  # @return [Hash] the new settings after save
55
55
  def save(new_config)
56
+ # Do not save obvious default config values. We'll always try to
57
+ # save author and lang as they default on system variables,
58
+ # which may be different from a system to another. Thus it may
59
+ # be confusing if one use neruda on two different computer and
60
+ # these params always change.
61
+ new_config.delete_if do |k, v|
62
+ ['domain', 'public_folder', 'templates', 'theme'].include?(k) \
63
+ && v == default_settings[k]
64
+ end
56
65
  IO.write 'config.yml', new_config.to_yaml
57
- @config = new_config.freeze
66
+ load_settings # Reload config, taking default settings into account
58
67
  end
59
68
 
60
69
  # Load the given settings as if they comes from the ~config.yml~ file.
@@ -66,28 +75,62 @@ module Neruda
66
75
  # @param config [Hash] the settings to artificially load
67
76
  # @return [Hash] the new settings
68
77
  def load_test(config)
69
- @config = config
70
- add_default_settings
78
+ @sources = nil # Reset sources
79
+ @config = default_settings.merge config
80
+ end
81
+
82
+ # Return the qualified projects sources list.
83
+ #
84
+ # @return [Array] the fully qualified projects sources list
85
+ def sources
86
+ return @sources if @sources
87
+ default_sources = [{ 'path' => 'src', 'target' => '.' }]
88
+ @sources = (settings['sources'] || default_sources).map do |s|
89
+ build_source(s)
90
+ end.compact
71
91
  end
72
92
 
73
93
  private
74
94
 
75
95
  def load_settings
76
- @config = {}
77
- conf = 'config.yml'
78
- @config = YAML.load_file(conf) if File.exist? conf
79
- add_default_settings
80
- @config.freeze
96
+ @sources = nil
97
+ conf_file = 'config.yml'
98
+ if File.exist? conf_file
99
+ @config = default_settings.merge(YAML.load_file(conf_file)).freeze
100
+ else
101
+ @config = default_settings
102
+ end
103
+ end
104
+
105
+ def extract_lang_from_env(default)
106
+ (ENV['LANG'] || default).split('_', 2).first
107
+ end
108
+
109
+ def default_settings
110
+ return @default_settings if @default_settings
111
+ @default_settings = {
112
+ 'author' => (ENV['USER'] || ''),
113
+ 'domain' => '',
114
+ 'lang' => extract_lang_from_env('en'),
115
+ 'public_folder' => 'public_html',
116
+ 'templates' => [],
117
+ 'theme' => 'default'
118
+ }.freeze
81
119
  end
82
120
 
83
- def add_default_settings
84
- @config['lang'] ||= 'en'
85
- @config['author'] ||= (ENV['USER'] || '')
86
- @config['domain'] ||= ''
87
- @config['public_folder'] ||= 'public_html'
88
- @config['templates'] ||= []
89
- return if @config['blog_path'].nil?
90
- @config['blog_pattern'] ||= '**/*.org'
121
+ def build_source(seed)
122
+ opts = { 'recursive' => true, 'is_blog' => false }
123
+ case seed
124
+ when String
125
+ opts['path'] = seed
126
+ when Hash
127
+ opts.merge! seed
128
+ end
129
+ return nil unless opts.has_key?('path')
130
+ opts['path'] = File.expand_path(opts['path'])
131
+ opts['name'] ||= File.basename(opts['path']).sub(/^\./, '')
132
+ opts['target'] ||= opts['name']
133
+ opts
91
134
  end
92
135
  end
93
136
  end
@@ -1,28 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'open-uri'
4
+ require 'neruda/version'
4
5
 
5
6
  module Neruda
6
7
  # This module contains utilitary methods to ease ~org-config.el~
7
8
  # file generation
8
9
  module LispConfig
9
- # Fetch and return the last published version of org mode.
10
+ # Fetch and return the last published version of Org.
10
11
  #
11
- # @return [String] the new x.x.x version string of org mode
12
+ # @return [String] the new x.x.x version string of Org
12
13
  def org_last_version
13
14
  return @org_version if @org_version
14
- index = open('https://orgmode.org/index.html', 'r').read
15
+ if File.exist?('tmp/__last_org_version__')
16
+ @org_version = IO.read('tmp/__last_org_version__')
17
+ return @org_version
18
+ end
19
+ index = URI('https://orgmode.org/index.html').open.read
15
20
  last_ver = index.match(/https:\/\/orgmode\.org\/org-([0-9.]+)\.tar\.gz/)
16
21
  # :nocov:
17
22
  if last_ver.nil?
18
23
  warn 'Org last version not found'
19
24
  return nil
20
25
  end
26
+ FileUtils.mkdir_p 'tmp'
27
+ IO.write('tmp/__last_org_version__', last_ver[1])
21
28
  # :nocov:
22
29
  @org_version = last_ver[1]
23
30
  end
24
31
 
25
- # Generate emacs lisp configuration file for org mode and write it.
32
+ # Generate emacs lisp configuration file for Org and write it.
26
33
  #
27
34
  # This method saves the generated configuration in the file
28
35
  # ~org-config.el~ at the root of your project, overwriting it if it
@@ -30,15 +37,16 @@ module Neruda
30
37
  #
31
38
  # @return [Integer] the length written (as returned by the
32
39
  # underlying ~IO.write~ method call)
33
- def write_org_lisp_config
34
- projects = org_generate_projects
40
+ def write_org_lisp_config(with_tags: false)
41
+ projects = org_generate_projects(with_tags: with_tags)
35
42
  workdir = Dir.pwd
36
43
  content = IO.read(File.expand_path('./org-config.el', __dir__))
44
+ .gsub('__VERSION__', Neruda::VERSION)
37
45
  .gsub('__WORK_DIR__', workdir)
38
46
  .gsub('__NERUDA_DIR__', __dir__)
39
47
  .gsub('__ORG_VER__', org_last_version)
40
- .gsub('__ALL_PROJECTS__', all_projects(projects).strip)
41
- .gsub('__THEME_CONFIG__', org_theme_config.strip)
48
+ .gsub('__ALL_PROJECTS__', all_projects(projects))
49
+ .gsub('__THEME_CONFIG__', org_default_theme_config)
42
50
  .gsub('__ALL_PROJECTS_NAMES__', project_names(projects))
43
51
  .gsub('__LONG_DATE_FMT__', r18n_full_datetime_format)
44
52
  .gsub('__AUTHOR_EMAIL__', settings['author_email'] || '')
@@ -49,8 +57,8 @@ module Neruda
49
57
  # Generate emacs directory variables file.
50
58
  #
51
59
  # This method generate the file ~.dir-locals.el~, which is
52
- # responsible to load neruda org mode settings when visiting an
53
- # org file of this neruda instance.
60
+ # responsible to load neruda Org settings when visiting an Org file
61
+ # of this neruda instance.
54
62
  #
55
63
  # @return [Integer] the length written (as returned by the
56
64
  # underlying ~IO.write~ method call)
@@ -76,67 +84,120 @@ module Neruda
76
84
  )
77
85
  end
78
86
 
87
+ def ruby_to_lisp_boolean(value)
88
+ return 't' if value == true
89
+ 'nil'
90
+ end
91
+
79
92
  def project_names(projects)
80
- projects.keys.map { |p| ["\"#{p}\"", "\"#{p}-assets\""] }
81
- .flatten.join(' ')
93
+ names = projects.keys.map do |p|
94
+ ["\"#{p}\"", "\"#{p}-assets\""]
95
+ end.flatten
96
+ names << "\"theme-#{settings['theme']}\""
97
+ sources.each do |s|
98
+ next unless s['theme'] && s['theme'] != settings['theme']
99
+ theme = "\"theme-#{s['theme']}\""
100
+ next if names.include? theme
101
+ names << theme
102
+ end
103
+ names.join(' ')
82
104
  end
83
105
 
84
106
  def all_projects(projects)
85
107
  projects.values.join("\n").strip
86
- .gsub(/\n\n/, "\n")
108
+ .gsub(/\n\s*\n/, "\n")
87
109
  .gsub(/\n/, "\n ")
88
110
  end
89
111
 
90
- def org_project(project_name, opts)
91
- orgtpl = opts['org_headers']
92
- base_directory = File.expand_path(opts['path'])
112
+ # Return the full path to the publication path of a given project
113
+ # configuration.
114
+ #
115
+ # @param project [Hash] a project configuration (as extracted from
116
+ # the ~sources~ key)
117
+ # @return [String] the full path to the target dir of this project
118
+ def publication_path(project)
93
119
  publish_in = [Dir.pwd, settings['public_folder']]
94
- publish_in << project_name unless project_name == 'neruda'
95
- publish_in = publish_in.join('/')
96
- recline = [opts['recursive'] || 't']
97
- default_ex_ptrn = settings['exclude_pattern']
120
+ publish_in << project['target'] unless project['target'] == '.'
121
+ publish_in.join('/')
122
+ end
123
+
124
+ def org_project(project_name, opts)
125
+ publish_in = publication_path(opts)
126
+ other_lines = [
127
+ format(':recursive %<value>s',
128
+ value: ruby_to_lisp_boolean(opts['recursive']))
129
+ ]
98
130
  if opts['exclude']
99
- recline << ":exclude \"#{opts['exclude']}\""
100
- elsif project_name == 'neruda' && default_ex_ptrn
101
- recline << ":exclude \"#{default_ex_ptrn}\""
131
+ other_lines << format(':exclude "%<value>s"',
132
+ value: opts['exclude'])
102
133
  end
134
+ themeconf = org_theme_config(opts['theme']) || ''
103
135
  <<~ORGPROJECT
104
136
  ("#{project_name}"
105
- :base-directory "#{base_directory}"
137
+ :base-directory "#{opts['path']}"
106
138
  :base-extension "org"
107
- :recursive #{recline.join("\n ")}
139
+ #{other_lines.join("\n ")}
108
140
  :publishing-directory "#{publish_in}"
109
141
  :publishing-function org-html-publish-to-html
110
142
  :section-numbers nil
111
143
  :with-toc nil
112
- #{orgtpl})
144
+ #{opts['org_headers']})
113
145
  ("#{project_name}-assets"
114
- :base-directory "#{base_directory}"
146
+ :base-directory "#{opts['path']}"
115
147
  :base-extension "jpg\\\\\\|gif\\\\\\|png\\\\\\|svg\\\\\\|pdf"
116
- :recursive #{recline[0]}
148
+ #{other_lines[0]}
117
149
  :publishing-directory "#{publish_in}"
118
150
  :publishing-function org-publish-attachment)
151
+ #{themeconf}
119
152
  ORGPROJECT
120
153
  end
121
154
 
122
- def org_default_theme_options
123
- postamble = <<~POSTAMBLE
155
+ def org_default_postamble
156
+ <<~POSTAMBLE
124
157
  <p><span class="author">#{R18n.t.neruda.org.postamble.written_by}</span>
125
158
  #{R18n.t.neruda.org.postamble.with_emacs}</p>
126
159
  <p class="date">#{R18n.t.neruda.org.postamble.last_modification}</p>
127
160
  <p class="validation">%v</p>
128
161
  POSTAMBLE
129
- { 'html-head' => build_html_head.strip,
130
- 'html-postamble' => postamble.strip,
162
+ end
163
+
164
+ def org_default_html_head
165
+ <<~HTMLHEAD
166
+ <link rel="stylesheet" type="text/css" media="screen"
167
+ href="__DOMAIN__/assets/__THEME__/css/style.css">
168
+ <link rel="stylesheet" type="text/css" media="screen"
169
+ href="__DOMAIN__/assets/__THEME__/css/htmlize.css">
170
+ __ATOM_FEED__
171
+ HTMLHEAD
172
+ end
173
+
174
+ def org_default_html_options
175
+ { 'html-head' => org_default_html_head,
176
+ 'html-postamble' => org_default_postamble,
131
177
  'html-head-include-default-style' => 't',
132
178
  'html-head-include-scripts' => 'nil' }
133
179
  end
134
180
 
135
- def org_templates
136
- orgtplopts = org_default_theme_options.merge
137
- orgtplopts.merge!(settings['org-html'] || {})
181
+ def expand_vars_in_html_head(head, project)
182
+ curtheme = project['theme'] || settings['theme']
183
+ # Head may be frozen when coming from settings
184
+ head = head.gsub('__THEME__', curtheme)
185
+ .gsub('__DOMAIN__', settings['domain'])
186
+ return head.gsub('__ATOM_FEED__', '') unless project['is_blog']
187
+ atomfeed = <<~ATOMFEED
188
+ <link rel="alternate" type="application/atom+xml" title="Atom 1.0"
189
+ href="#{settings['domain']}/feeds/index.xml" />
190
+ ATOMFEED
191
+ head.gsub('__ATOM_FEED__', atomfeed)
192
+ end
193
+
194
+ def build_project_org_headers(project)
195
+ orgtplopts = org_default_html_options.merge(
196
+ settings['org-html'] || {}, project['org-html'] || {}
197
+ )
138
198
  orgtpl = []
139
199
  orgtplopts.each do |k, v|
200
+ v = expand_vars_in_html_head(v, project) if k == 'html-head'
140
201
  val = v.strip.gsub(/"/, '\"')
141
202
  if ['t', 'nil', '1'].include? val
142
203
  orgtpl << ":#{k} #{val}"
@@ -147,62 +208,47 @@ module Neruda
147
208
  orgtpl.join("\n ")
148
209
  end
149
210
 
150
- def org_external_projects_opts(seed, orgtpl)
151
- opts = { 'org_headers' => orgtpl }
152
- if seed.is_a? String
153
- opts['path'] = seed
154
- elsif seed.is_a? Hash
155
- opts.merge! seed
211
+ def org_generate_projects(with_tags: false)
212
+ projects = {}
213
+ projects_sources = sources
214
+ if with_tags
215
+ tags_conf = build_source('tags')
216
+ tags_conf['recursive'] = false
217
+ projects_sources << tags_conf
156
218
  end
157
- opts
158
- end
159
-
160
- def org_generate_projects
161
- orgtpl = org_templates
162
- default_project = org_project(
163
- 'neruda', 'org_headers' => orgtpl, 'path' => './src'
164
- )
165
- projects = { 'neruda' => default_project }
166
- settings['external_sources']&.each do |s|
167
- opts = org_external_projects_opts(s, orgtpl)
168
- next unless opts.has_key?('path')
169
- pname = File.basename(opts['path']).sub(/^\./, '')
170
- projects[pname] = org_project(pname, opts)
219
+ projects_sources.each do |opts|
220
+ opts['org_headers'] = build_project_org_headers(opts)
221
+ projects[opts['name']] = org_project(opts['name'], opts)
171
222
  end
172
223
  projects
173
224
  end
174
225
 
175
- def org_theme_config
176
- curtheme = settings['theme'] || 'default'
226
+ def org_default_theme_config
227
+ org_theme_config(settings['theme']).split("\n").map do |line|
228
+ if line[0] == '('
229
+ line
230
+ else
231
+ " #{line}"
232
+ end
233
+ end.join("\n")
234
+ end
235
+
236
+ def org_theme_config(theme)
237
+ return nil if theme.nil?
177
238
  workdir = Dir.pwd
178
- if curtheme == 'default'
239
+ if theme == 'default'
179
240
  sourcedir = File.expand_path('../../../', __dir__)
180
241
  else
181
242
  sourcedir = workdir
182
243
  end
183
244
  <<~THEMECONFIG
184
- ("theme"
185
- :base-directory "#{sourcedir}/themes/#{curtheme}"
186
- :base-extension "jpg\\\\\\|gif\\\\\\|png\\\\\\|js\\\\\\|css\\\\\\|otf\\\\\\|ttf\\\\\\|woff2?"
187
- :recursive t
188
- :publishing-directory "#{workdir}/#{settings['public_folder']}/assets"
189
- :publishing-function org-publish-attachment)
245
+ ("theme-#{theme}"
246
+ :base-directory "#{sourcedir}/themes/#{theme}"
247
+ :base-extension "jpg\\\\\\|gif\\\\\\|png\\\\\\|js\\\\\\|css\\\\\\|otf\\\\\\|ttf\\\\\\|woff2?"
248
+ :recursive t
249
+ :publishing-directory "#{workdir}/#{settings['public_folder']}/assets/#{theme}"
250
+ :publishing-function org-publish-attachment)
190
251
  THEMECONFIG
191
252
  end
192
-
193
- def build_html_head
194
- stylesheet = <<~CSS
195
- <link rel="stylesheet" type="text/css" media="screen"
196
- href="#{settings['domain']}/assets/css/style.css">
197
- <link rel="stylesheet" type="text/css" media="screen"
198
- href="#{settings['domain']}/assets/css/htmlize.css">
199
- CSS
200
- return stylesheet if settings['blog_path'].nil?
201
- <<~ATOM
202
- #{stylesheet.strip}
203
- <link rel="alternate" type="application/atom+xml" title="Atom 1.0"
204
- href="#{settings['domain']}/feeds/index.xml" />
205
- ATOM
206
- end
207
253
  end
208
254
  end