alula 0.2.3 → 0.4.0b

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 (182) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +7 -0
  3. data/Guardfile +9 -0
  4. data/Rakefile +12 -1
  5. data/VERSION +1 -1
  6. data/alula.gemspec +20 -4
  7. data/lib/alula/attachment_processor.rb +77 -0
  8. data/lib/alula/cdn.rb +30 -0
  9. data/lib/alula/cdns/edgecast.rb +16 -0
  10. data/lib/alula/cdns/hosts.rb +14 -0
  11. data/lib/alula/cli.rb +90 -39
  12. data/lib/alula/compressors.rb +22 -10
  13. data/lib/alula/config.rb +141 -0
  14. data/lib/alula/content.rb +113 -0
  15. data/lib/alula/contents/attachment.rb +8 -0
  16. data/lib/alula/contents/item.rb +409 -0
  17. data/lib/alula/contents/metadata.rb +73 -0
  18. data/lib/alula/contents/page.rb +9 -0
  19. data/lib/alula/contents/post.rb +32 -0
  20. data/lib/alula/context.rb +72 -0
  21. data/lib/alula/core_ext.rb +5 -0
  22. data/lib/alula/core_ext/environment.rb +20 -0
  23. data/lib/alula/core_ext/filter.rb +20 -0
  24. data/lib/alula/core_ext/filters/smilies.rb +36 -0
  25. data/lib/alula/core_ext/manifest.rb +30 -0
  26. data/lib/alula/core_ext/tag.rb +100 -0
  27. data/lib/alula/core_ext/tags/attachment.rb +28 -0
  28. data/lib/alula/core_ext/tags/blockquote.rb +21 -0
  29. data/lib/alula/core_ext/tags/image.rb +48 -0
  30. data/lib/alula/core_ext/tags/locale.rb +17 -0
  31. data/lib/alula/core_ext/tags/video.rb +103 -0
  32. data/lib/alula/generator.rb +31 -0
  33. data/lib/alula/generators/feedbuilder.rb +44 -0
  34. data/lib/alula/generators/paginate.rb +88 -0
  35. data/lib/alula/generators/sitemap.rb +26 -0
  36. data/lib/alula/helpers.rb +2 -0
  37. data/lib/alula/helpers/addons.rb +12 -0
  38. data/lib/alula/helpers/assets.rb +56 -0
  39. data/lib/alula/helpers/url_helpers.rb +16 -0
  40. data/lib/alula/plugin.rb +32 -0
  41. data/lib/alula/processor.rb +86 -0
  42. data/lib/alula/processors/dummy.rb +24 -0
  43. data/lib/alula/processors/image.rb +52 -0
  44. data/lib/alula/processors/magick.rb +83 -0
  45. data/lib/alula/processors/video.rb +97 -0
  46. data/lib/alula/processors/zencoder.rb +199 -0
  47. data/lib/alula/progress.rb +95 -0
  48. data/lib/alula/progressbar.rb +66 -0
  49. data/lib/alula/site.rb +331 -262
  50. data/lib/alula/storage.rb +46 -0
  51. data/lib/alula/storages/file_item.rb +43 -0
  52. data/lib/alula/storages/filestorage.rb +96 -0
  53. data/lib/alula/storages/item.rb +12 -0
  54. data/lib/alula/support/commonlogger.rb +30 -0
  55. data/lib/alula/theme.rb +70 -13
  56. data/lib/alula/theme/layout.rb +56 -0
  57. data/lib/alula/theme/view.rb +43 -0
  58. data/lib/alula/version.rb +1 -1
  59. data/locales/en.yml +9 -0
  60. data/locales/fi.yml +10 -0
  61. data/locales/l10n/ar.yml +199 -0
  62. data/locales/l10n/az.yml +199 -0
  63. data/locales/l10n/bg.yml +199 -0
  64. data/locales/l10n/bn-IN.yml +182 -0
  65. data/locales/l10n/bs.yml +242 -0
  66. data/locales/l10n/ca.yml +199 -0
  67. data/locales/l10n/cs.yml +198 -0
  68. data/locales/l10n/csb.yml +210 -0
  69. data/locales/l10n/cy.yml +199 -0
  70. data/locales/l10n/da.yml +199 -0
  71. data/locales/l10n/de-AT.yml +203 -0
  72. data/locales/l10n/de-CH.yml +203 -0
  73. data/locales/l10n/de.yml +203 -0
  74. data/locales/l10n/dsb.yml +215 -0
  75. data/locales/l10n/el.yml +199 -0
  76. data/locales/l10n/en-AU.yml +205 -0
  77. data/locales/l10n/en-CA.yml +214 -0
  78. data/locales/l10n/en-GB.yml +205 -0
  79. data/locales/l10n/en-IN.yml +205 -0
  80. data/locales/l10n/en-US.yml +205 -0
  81. data/locales/l10n/en.yml +205 -0
  82. data/locales/l10n/eo.yml +201 -0
  83. data/locales/l10n/es-AR.yml +205 -0
  84. data/locales/l10n/es-CL.yml +199 -0
  85. data/locales/l10n/es-CO.yml +205 -0
  86. data/locales/l10n/es-MX.yml +205 -0
  87. data/locales/l10n/es-PE.yml +181 -0
  88. data/locales/l10n/es-VE.yml +205 -0
  89. data/locales/l10n/es.yml +199 -0
  90. data/locales/l10n/et.yml +199 -0
  91. data/locales/l10n/eu.yml +199 -0
  92. data/locales/l10n/fa.yml +199 -0
  93. data/locales/l10n/fi.yml +199 -0
  94. data/locales/l10n/fr-CA.yml +207 -0
  95. data/locales/l10n/fr-CH.yml +207 -0
  96. data/locales/l10n/fr.yml +222 -0
  97. data/locales/l10n/fur.yml +199 -0
  98. data/locales/l10n/gl-ES.yml +178 -0
  99. data/locales/l10n/gsw-CH.yml +199 -0
  100. data/locales/l10n/he.yml +201 -0
  101. data/locales/l10n/hi-IN.yml +199 -0
  102. data/locales/l10n/hi.yml +199 -0
  103. data/locales/l10n/hr.yml +237 -0
  104. data/locales/l10n/hsb.yml +214 -0
  105. data/locales/l10n/hu.yml +199 -0
  106. data/locales/l10n/id.yml +200 -0
  107. data/locales/l10n/is.yml +213 -0
  108. data/locales/l10n/it.yml +205 -0
  109. data/locales/l10n/ja.yml +197 -0
  110. data/locales/l10n/kn.yml +199 -0
  111. data/locales/l10n/ko.yml +197 -0
  112. data/locales/l10n/lo.yml +186 -0
  113. data/locales/l10n/lt.yml +182 -0
  114. data/locales/l10n/lv.yml +215 -0
  115. data/locales/l10n/mk.yml +170 -0
  116. data/locales/l10n/mn.yml +205 -0
  117. data/locales/l10n/nb.yml +207 -0
  118. data/locales/l10n/nl.yml +199 -0
  119. data/locales/l10n/nn.yml +160 -0
  120. data/locales/l10n/pl.yml +221 -0
  121. data/locales/l10n/pt-BR.yml +207 -0
  122. data/locales/l10n/pt-PT.yml +207 -0
  123. data/locales/l10n/quotes.yml +24 -0
  124. data/locales/l10n/rm.yml +182 -0
  125. data/locales/l10n/ro.yml +199 -0
  126. data/locales/l10n/ru.yml +257 -0
  127. data/locales/l10n/sk.yml +213 -0
  128. data/locales/l10n/sl.yml +210 -0
  129. data/locales/l10n/sr-Latn.yml +170 -0
  130. data/locales/l10n/sr.yml +170 -0
  131. data/locales/l10n/sv-SE.yml +199 -0
  132. data/locales/l10n/sw.yml +197 -0
  133. data/locales/l10n/th.yml +173 -0
  134. data/locales/l10n/tl.yml +229 -0
  135. data/locales/l10n/tr.yml +199 -0
  136. data/locales/l10n/uk.yml +257 -0
  137. data/locales/l10n/vi.yml +201 -0
  138. data/locales/l10n/wo.yml +205 -0
  139. data/locales/l10n/zh-CN.yml +199 -0
  140. data/locales/l10n/zh-TW.yml +199 -0
  141. data/template/Gemfile.erb +14 -4
  142. data/template/README +16 -0
  143. data/template/config.yml.erb +42 -38
  144. data/test/fixtures/config_001_simple.yml +2 -0
  145. data/test/fixtures/config_002_l10n.yml +5 -0
  146. data/test/fixtures/pages/invalid-page.markdown +1 -0
  147. data/test/fixtures/pages/multilingual-page.markdown +20 -0
  148. data/test/fixtures/pages/section/subpage.markdown +5 -0
  149. data/test/fixtures/pages/simple-page.markdown +7 -0
  150. data/test/fixtures/posts/2012-07-02-invalid-post.markdown +1 -0
  151. data/test/fixtures/posts/2012-07-02-simple.markdown +7 -0
  152. data/test/fixtures/posts/2012-07-03-full-metadata.markdown +8 -0
  153. data/test/fixtures/posts/2012-07-03-multilingual-full-metadata.markdown +20 -0
  154. data/test/fixtures/theme/test/layouts/default.html.erb +1 -0
  155. data/test/fixtures/theme/test/views/page.html.erb +1 -0
  156. data/test/fixtures/theme/test/views/post.html.erb +1 -0
  157. data/test/minitest_helper.rb +14 -0
  158. data/test/test_config.rb +33 -0
  159. data/test/test_content.rb +30 -0
  160. data/test/test_metadata.rb +83 -0
  161. data/test/test_page.rb +81 -0
  162. data/test/test_post.rb +123 -0
  163. data/test/test_storage.rb +23 -0
  164. data/test/test_storage_file.rb +32 -0
  165. data/test/test_theme.rb +45 -0
  166. data/vendor/assets/images/favicon.png +0 -0
  167. data/vendor/assets/images/grey.gif +0 -0
  168. data/vendor/assets/javascripts/jquery.alula.js.coffee +16 -0
  169. data/vendor/{javascripts → assets/javascripts}/jquery.js +0 -0
  170. data/vendor/assets/javascripts/jquery.lazyload.js +210 -0
  171. data/vendor/assets/javascripts/lazyload.js.coffee +15 -0
  172. data/vendor/layouts/feed.xml.builder +19 -0
  173. data/vendor/layouts/sitemap.xml.builder +10 -0
  174. data/vendor/views/feed_post.html.haml +1 -0
  175. metadata +529 -50
  176. data/lib/alula.rb +0 -5
  177. data/lib/alula/assethelper.rb +0 -75
  178. data/lib/alula/plugins.rb +0 -23
  179. data/lib/alula/plugins/assets.rb +0 -82
  180. data/lib/alula/plugins/pagination.rb +0 -121
  181. data/lib/alula/rake_tasks.rb +0 -42
  182. data/lib/alula/tasks.rb +0 -2
@@ -0,0 +1,95 @@
1
+ require 'alula/progressbar'
2
+
3
+ module Alula
4
+ class Progress
5
+ def initialize(options)
6
+ @pbars = {}
7
+ @interval = 1.0
8
+ @options = options
9
+ @display = false
10
+
11
+ @@lock = Mutex.new
12
+ end
13
+
14
+ def create(identifier, opts)
15
+ if @pbars[identifier]
16
+ @pbars[identifier].finish
17
+ end
18
+
19
+ @@lock.synchronize do
20
+ @pbars[identifier] = ProgressBar.new(opts[:title], opts[:total] == 0 ? 0.1 : opts[:total])
21
+ if @options[:debug]
22
+ @pbars[identifier].settings.force_mode = :notty
23
+ end
24
+ end
25
+ end
26
+
27
+ def step(identifier)
28
+ if @pbars[identifier]
29
+ @pbars[identifier].step
30
+ _display
31
+ end
32
+ end
33
+
34
+ def set(identifier, value)
35
+ if @pbars[identifier]
36
+ @pbars[identifier].set(value)
37
+ end
38
+ end
39
+
40
+ def title(identifier, title)
41
+ if @pbars[identifier]
42
+ @pbars[identifier].message = title
43
+ end
44
+ end
45
+
46
+ def set_file_transfer(identifier)
47
+ if @pbars[identifier]
48
+ @pbars[identifier].file_transfer_mode
49
+ end
50
+ end
51
+
52
+ def finish(identifier)
53
+ if @pbars[identifier]
54
+ @pbars[identifier].finish
55
+ _display
56
+ @@lock.synchronize do
57
+ @pbars.delete(identifier)
58
+ end
59
+ end
60
+ end
61
+
62
+ def display
63
+ @display = true
64
+ _display(true)
65
+ unless @options[:debug]
66
+ @update_thread = Thread.new {
67
+ loop {
68
+ sleep(@interval)
69
+ _display
70
+ }
71
+ }
72
+ end
73
+ end
74
+
75
+ def hide
76
+ @display = false
77
+ if @update_thread
78
+ Thread.kill(@update_thread)
79
+ end
80
+ end
81
+
82
+ private
83
+ def _display(first = false)
84
+ return unless @display
85
+
86
+ @@lock.synchronize do
87
+ output = @pbars.collect {|identifier, pbar| pbar.render }
88
+ unless @options[:debug] or first
89
+ print "\e[#{output.count}F"
90
+ end
91
+ puts output.join("\n")
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,66 @@
1
+ require 'powerbar'
2
+
3
+ module Alula
4
+ class ProgressBar < PowerBar
5
+ attr_accessor :showing
6
+ attr_accessor :message
7
+
8
+ def initialize(message, total, opts = {})
9
+ super(opts)
10
+
11
+ @message = "%20s" % message
12
+ @done = 0
13
+ @total = total
14
+
15
+ @showing = true
16
+
17
+ self.settings.tty.finite.template.main = '${<msg>}: ${<percent>% } ${[<bar>] }${ ETA: <eta>}'
18
+ self.settings.tty.finite.template.barchar = 'o'
19
+ self.settings.tty.finite.template.padchar = ' '
20
+ end
21
+
22
+ def file_transfer_mode
23
+ self.settings.tty.finite.template.main = '${<msg>}: ${<percent>% } ${[<bar>] }${<rate>/s }${<done>}${ ETA: <eta>}'
24
+ end
25
+
26
+ def render(opts = {})
27
+ super({msg: @message, done: @done, total: @total}.merge(opts))
28
+ end
29
+
30
+ def step
31
+ @done += 1
32
+ end
33
+
34
+ def set(value)
35
+ @done = value
36
+ end
37
+
38
+ def vanish
39
+ self.wipe
40
+ end
41
+
42
+ def finish(fill = true)
43
+ # self.close(true)
44
+ render(
45
+ {
46
+ :done => fill && !state.total.is_a?(Symbol) ? state.total : state.done,
47
+ :tty => {
48
+ :finite => { :show_eta => false },
49
+ :infinite => { :show_eta => false },
50
+ },
51
+ :notty => {
52
+ :finite => { :show_eta => false },
53
+ :infinite => { :show_eta => false },
54
+ },
55
+ })
56
+ # scope.output.call(scope.template.close) unless scope.template.close.nil?
57
+ state.closed = true
58
+ end
59
+
60
+ private
61
+ # Monkey-patch to force percentage always being three characters long
62
+ def h_percent
63
+ sprintf "%3d", percent
64
+ end
65
+ end
66
+ end
@@ -1,336 +1,405 @@
1
- require 'yaml'
2
- require 'jekyll'
3
- require 'sprockets'
4
- require 'active_support/inflector/methods'
5
- require 'progressbar'
6
- require 'stringex'
7
-
8
- require 'alula/theme'
9
- require 'alula/plugins'
10
- require 'alula/assethelper'
1
+ require 'alula/core_ext'
11
2
 
12
- # Compressors
3
+ require 'alula/config'
4
+ require 'alula/plugin'
5
+ require 'alula/storage'
6
+ require 'alula/content'
7
+ require 'alula/context'
8
+ require 'alula/generator'
9
+ require 'alula/attachment_processor'
13
10
  require 'alula/compressors'
11
+ require 'alula/cdn'
12
+ require 'alula/helpers'
13
+ require 'alula/progress'
14
+
15
+ require 'thor'
16
+ require 'sprockets'
17
+ require 'i18n'
18
+ require 'parallel'
19
+ require 'hashie/mash'
14
20
 
15
- # Jekyll extensions, plugins, tags
16
- # These are used always in every blog, i.e. mandatory plugins
17
- require 'alula/plugins/assets'
18
- require 'alula/plugins/pagination'
21
+ # Silence Tilt
22
+ require 'sass'
23
+ require 'coffee-script'
19
24
 
20
25
  module Alula
21
26
  class Site
27
+ def self.instance; @@instance; end
28
+
29
+ # Global configuration
30
+ attr_reader :config
31
+
32
+ # Storage
33
+ attr_reader :storage
34
+
35
+ # Context for rendering
36
+ attr_reader :context
37
+
38
+ # Progress displayer
39
+ attr_reader :progress
40
+
41
+ # CDN Resolver for Site
42
+ attr_reader :cdn
43
+
44
+ # Site Plugins
45
+ attr_reader :plugins
46
+
47
+ # Site filters
48
+ attr_reader :filters
49
+
50
+ # Compressors
51
+ attr_reader :compressors
52
+
53
+ # Site metadata information
54
+ attr_reader :metadata
22
55
 
23
- attr_reader :config, :jekyll
56
+ # Site attachment mapping
57
+ attr_reader :attachments
24
58
 
25
- def initialize(override = {})
26
- # Load configuration
27
- @config = YAML.load_file('config.yml').deep_merge(override)
28
-
29
- # Register local theme path
30
- Alula::Theme.register("themes")
31
-
32
- # Initialize Jekyll
33
- options = Alula::DEFAULTS.deep_merge({
34
- # Site options
35
- 'title' => @config["title"],
36
- 'tagline' => @config["tagline"],
37
- 'author' => @config["author"],
38
- 'root' => @config["root"],
39
- 'asset_path' => File.join(@config["root"], "assets"),
40
- 'permalink' => @config["permalink"],
41
- 'paginate' => @config["paginate"],
42
- 'pagination_dir' => @config["pagination_dir"],
43
- 'excerpt_link' => @config["excerpt_link"],
59
+ # Theme
60
+ attr_reader :theme
61
+
62
+ # User generated content
63
+ attr_reader :content
64
+
65
+ # System generated content, pages, pagination, etc.
66
+ attr_reader :generated
67
+
68
+ def initialize(options)
69
+ @@instance = self
70
+
71
+ # Read local config
72
+ @config = Config.new(options)
73
+
74
+ @storage = Storage.load(site: self)
75
+
76
+ @metadata = Content::Metadata.new({
77
+ base_locale: @config.locale,
78
+ environment: @config.environment,
79
+
80
+ title: @config.title,
81
+ author: @config.author,
82
+ tagline: @config.tagline,
83
+ url: @config.url,
84
+
85
+ theme: @config.theme,
86
+
87
+ # Use this to store information of GIT site or note
88
+ git: ::File.directory?(".git"),
44
89
  })
45
90
 
46
- @jekyll = Jekyll::Site.new(options)
91
+ # Progress displayer
92
+ @progress = Progress.new(debug: options["debug"])
47
93
 
48
- @themepath = Alula::Theme.find_theme(@config['theme'])
49
- unless @themepath
50
- raise "Cannot find theme #{@config['theme']}"
94
+ # Compressors
95
+ compressors = if @config.assets.compress
96
+ {
97
+ html: Alula::Compressors::HTMLCompressor.new,
98
+ css: Alula::Compressors::CSSCompressor.new,
99
+ js: Alula::Compressors::JSCompressor.new,
100
+ }
101
+ else
102
+ {
103
+ html: Alula::Compressors::DummyCompressor.new,
104
+ css: Alula::Compressors::DummyCompressor.new,
105
+ js: Alula::Compressors::DummyCompressor.new,
106
+ }
51
107
  end
108
+ @compressors = Hashie::Mash.new(compressors)
52
109
 
53
- # Initialize Sprockets
54
- @sprockets = Sprockets::Environment.new
55
- # Set our compressor
56
- if @config['asset_compress']
57
- @sprockets.css_compressor = Alula::Compressors::CSSCompressor.new
58
- @sprockets.js_compressor = Alula::Compressors::JSCompressor.new
59
-
60
- [Jekyll::Post, Jekyll::Page].each do |klass|
61
- klass.send(:include, Alula::Compressors::HTMLCompressor)
62
- klass.send(:alias_method, :output_without_compression, :output)
63
- klass.send(:alias_method, :output, :output_with_compression)
64
- end
65
- end
66
-
67
- # Add theme to asset paths
68
- @sprockets.append_path File.join(@themepath, @config['theme'], "stylesheets")
69
- @sprockets.append_path File.join(@themepath, @config['theme'], "javascripts")
70
-
71
- # Generated assets
72
- @sprockets.append_path File.join("_tmp", "assets")
110
+ @attachments = AttachmentProcessor.new(site: self)
73
111
 
74
- # Attachments
75
- @sprockets.append_path File.join("attachments", "_generated")
112
+ # Set up CDN resolver
113
+ @cdn = CDN.load(site: self)
76
114
 
77
- # Vendor assets
78
- vendor_path = File.expand_path(File.join(File.dirname(__FILE__), *%w{.. .. vendor}))
79
- @sprockets.append_path File.join(vendor_path, "stylesheets")
80
- @sprockets.append_path File.join(vendor_path, "javascripts")
115
+ @plugins = {}
116
+
117
+ @filters = {}
118
+
119
+ # Set up I18n
120
+ l10n_path = File.join(File.dirname(__FILE__), "..", "..", "locales", "l10n", "*.yml")
121
+ locale_path = File.join(File.dirname(__FILE__), "..", "..", "locales", "*.yml")
122
+ custom_locale_path = File.join(@storage.path(:custom, "locales"), "*.yml")
123
+ I18n.load_path += Dir[l10n_path]
124
+ I18n.load_path += Dir[locale_path]
125
+ I18n.load_path += Dir[custom_locale_path]
126
+ I18n.default_locale = @config.locale
127
+
128
+ # Set up default head addons
129
+ Alula::Plugin.addon(:head, "<meta name=\"generator\" content=\"Alula #{Alula::VERSION}\">")
130
+ Alula::Plugin.addon(:head, ->(context){"<link rel=\"icon\" type=\"image/png\" href=\"#{context.asset_url('favicon.png')}\">"})
81
131
 
82
- # Initialize blog plugins
83
- if @config["plugins"] != nil
84
- @config["plugins"].each do |plugin, opts|
85
- require "alula/plugins/#{plugin}"
86
-
87
- plugin_class = Alula::Plugins.const_get(ActiveSupport::Inflector.camelize(plugin, true))
88
- path = plugin_class.install(opts)
89
- @sprockets.append_path File.join(path, "stylesheets")
90
- @sprockets.append_path File.join(path, "javascripts")
91
- end
92
- end
93
132
  end
94
133
 
134
+ # Compiles a site to static website
95
135
  def generate
96
- puts "==> Generating blog"
136
+ # Load our plugins and filters
137
+ load_plugins
138
+ load_filters
139
+
140
+ # Prepare public folder
141
+ prepare(true)
97
142
 
98
- # Prepare Jekyll environment
99
- prepare
143
+ load_content
100
144
 
101
- # Generate missing assets
102
- assetgen
145
+ process_attachments
103
146
 
104
- # Generate asset manifest
105
- compile
147
+ compile_assets
106
148
 
107
- # Execute jekyll
108
- process
149
+ render
109
150
 
110
- # Cleanup
111
151
  cleanup
152
+ # Store cached version of configuration
153
+ cached_config = File.join(storage.path(:cache), "config.yml")
154
+ @config.write_cache(cached_config)
112
155
  end
113
156
 
114
- def preview
115
- generate
116
-
117
- require 'webrick'
118
- # include WEBrick
119
-
120
- FileUtils.mkdir_p("public")
121
-
122
- mime_types = WEBrick::HTTPUtils::DefaultMimeTypes
123
- mime_types.store 'js', 'application/javascript'
124
-
125
- s = WEBrick::HTTPServer.new(
126
- :Port => 3000,
127
- :MimeTypes => mime_types
128
- )
129
- s.mount('/', WEBrick::HTTPServlet::FileHandler, "public")
130
- t = Thread.new {
131
- s.start
132
- }
133
-
134
- trap("INT") { s.shutdown }
135
- t.join()
157
+ # Proxy to metadata
158
+ def method_missing(meth, *args, &blk)
159
+ # Proxy to metadata
160
+ if !meth[/=$/] and self.metadata.respond_to?(meth)
161
+ args.unshift(self.context.locale || self.config.locale) if args.empty?
162
+ self.metadata.send(meth, *args)
163
+ else
164
+ super
165
+ end
136
166
  end
137
167
 
138
- def asset_attach(a_post, assets)
139
- # Find the post
140
- post = find_post(a_post) or raise "Cannot find post #{a_post}"
141
-
142
- /(?<date>(\d{4}-\d{2}-\d{2}))/ =~ post
143
- date = Time.parse(date)
144
- asset_path = File.join(%w{%Y %m %d}.collect{|f| date.strftime(f) })
145
-
146
- helper = Alula::AssetHelper.new(asset_path, @config)
147
-
148
- post_io = File.open(post, "a")
149
- assets.each do |asset|
150
- type, generated = helper.process(asset, :type => :attachment)
151
- tn_type, tn_generated = helper.process(asset, :type => :thumbnail)
152
- if generated and tn_generated
153
- # Asset processed
154
- puts "(#{asset}) done."
155
- if handler = Alula::Plugins.attachment_handler(type)
156
- post_io.puts handler.call(generated[0])
157
- else
158
- post_io.puts "{% image _images/#{generated[0]} %}"
159
- end
160
- else
161
- puts "(#{asset}) cannot process."
168
+ private
169
+ def load_plugins
170
+ config.plugins.each do |name, options|
171
+ if plugin = Alula::Plugin.load(name, options)
172
+ @plugins[name] = plugin
162
173
  end
163
174
  end
164
175
  end
165
176
 
166
- def clean
167
- cleanup
168
- FileUtils.rm_rf(Dir[File.join("attachments", "_images", "*")])
169
- FileUtils.rm_rf(Dir[File.join("attachments", "_thumbnails", "*")])
177
+ def load_filters
178
+ config.content.filters.each do |name, options|
179
+ if filter = Alula::Filter.load(name, options)
180
+ @filters[name] = filter
181
+ end
182
+ end
170
183
  end
171
184
 
172
- private
173
- def find_post(post)
174
- if File.exists?(post)
175
- return post
176
- elsif File.exists?(File.join("posts", post))
177
- return File.join("posts", post)
178
- else
179
- # Try to find by title
180
- title = post.to_url
181
- posts = Dir[File.join("posts", "*")].select { |p| p =~ /#{title}/ }
182
- if posts.count == 1
183
- return posts.first
185
+ def prepare(preserve = false)
186
+ say "==> Preparing environment" + (preserve ? " (preserving existing files)" : "")
187
+
188
+ # Delegate preparations to storage module
189
+ self.storage.prepare(preserve)
190
+
191
+ # Load theme
192
+ @context = Alula::Context.new(site: self, storage: self.storage)
193
+ @context.send(:extend, Helpers)
194
+
195
+ @theme = Alula::Theme.load(site: self)
196
+
197
+ # Create our asset environment
198
+ @environment = Environment.new
199
+ # Add compressor support
200
+ # if config.environment == "production"
201
+ @environment.css_compressor = @compressors.css
202
+ @environment.js_compressor = @compressors.js
203
+ # end
204
+ @environment.context_class.class_eval do
205
+ # include Helpers
206
+ def context; Alula::Site.instance.context; end
207
+ def method_missing(meth, *args, &blk)
208
+ return context.send(meth, *args, &blk) if context.respond_to?(meth)
209
+ super
184
210
  end
185
211
  end
212
+ @context.environment = @environment
213
+ @context.attachments = self.attachments
214
+
215
+ # Add generated attachements
216
+ @environment.append_path @storage.path(:cache, "attachments")
217
+
218
+ # Add generated assets
219
+ @environment.append_path @storage.path(:cache, "assets")
220
+
221
+ # Theme, plugins, vendor and customisation
222
+ [
223
+ self.theme.path,
224
+ *plugins.collect{|name, plugin| plugin.asset_path},
225
+ ::File.join(File.dirname(__FILE__), "..", "..", "vendor"),
226
+ ].each do |path|
227
+ %w{javascripts stylesheets images}.each {|p|
228
+ @environment.append_path ::File.join(path, "assets", p)
229
+ }
230
+ end
231
+
232
+ # Customisation
233
+ %w{javascripts stylesheets images}.each do |path|
234
+ @environment.prepend_path @storage.path(:custom, path)
235
+ end
186
236
  end
187
237
 
188
- def prepare
189
- puts "==> Prepare"
190
- # Clean our temporary folder
191
- FileUtils.rm_rf "_tmp"
192
- FileUtils.mkdir "_tmp"
193
- FileUtils.rm_rf "public"
194
- FileUtils.mkdir "public"
195
-
196
- # Copy Jekyll files
197
- # Install our theme
198
- FileUtils.mkdir_p File.join("_tmp", "_layouts")
199
- FileUtils.mkdir_p File.join("_tmp", "_includes")
200
-
201
- FileUtils.cp_r Dir[File.join(@themepath, @config['theme'], "layouts", "*")], File.join("_tmp", "_layouts")
202
- FileUtils.cp_r Dir[File.join(@themepath, @config['theme'], "includes", "*")], File.join("_tmp", "_includes")
203
-
204
- FileUtils.cp_r Dir[File.join(@themepath, @config['theme'], "site", "*")], "_tmp"
205
-
206
- # Copy posts
207
- FileUtils.mkdir_p File.join("_tmp", "_posts")
208
- FileUtils.cp_r Dir[File.join("posts", "*")], File.join("_tmp", "_posts")
238
+ def load_content
239
+ say "==> Loading site content"
209
240
 
210
- # Copy pages
211
- Dir[File.join("pages", "**", "*")].each do |page|
212
- next unless File.file?(page)
213
- page = File.join(page.split("/")[1..-1])
214
-
215
- FileUtils.mkdir_p File.join("_tmp", File.dirname(page))
216
- FileUtils.cp File.join("pages", page), File.join("_tmp", page)
241
+ # Read site content
242
+ @content = Content.new(site: self)
243
+ @content.load
244
+
245
+ # Do we have index page defined
246
+ if self.config.index
247
+ index_page = @content.by_slug(self.config.index)
248
+ if index_page
249
+ index_page.metadata.slug = "index"
250
+ index_page.metadata.template = "/:locale/:slug"
251
+ index_page.metadata.title = Hash[index_page.metadata.languages.collect{|lang| [lang, metadata.title(lang)]}]
252
+ end
217
253
  end
218
-
219
- FileUtils.mkdir_p File.join("_tmp", "assets")
220
254
  end
221
255
 
222
- def assetgen
223
- puts "==> Generating assets"
224
-
225
- # width, height = @config["images"]["thumbnails"].split("x").collect {|i| i.to_i }
256
+ def process_attachments
257
+ puts "==> Processing attachments"
226
258
 
227
- # Get all attachements
228
- attachments_path = File.join("attachments", "originals")
229
- images_path = File.join("attachments", "_generated", "images")
230
- thumbnails_path = File.join("attachments", "_generated", "thumbnails")
259
+ progress.create :attachments, title: "Attachments", total: self.content.attachments.count
260
+ progress.display
231
261
 
232
- attachments = Dir[File.join(attachments_path, "**", "*")]
233
- .select {|f| File.file?(f) }
234
- .collect {|f| File.join(f.split("/")[2..-1])}
235
- pb = ProgressBar.new "Assets", attachments.count
262
+ @@lock = Mutex.new
236
263
 
237
- attachments.each do |attachment|
238
- unless File.exists?(File.join(images_path, attachment))
239
- helper = Alula::AssetHelper.new(File.dirname(attachment), @config)
240
- type, generated = helper.process(File.join("attachments", "originals", attachment), :type => :attachment)
264
+ Parallel.map(self.content.attachments, :in_threads => Parallel.processor_count) do |attachment|
265
+ if processor = attachments.get(attachment)
266
+ processor.process
241
267
  end
242
-
243
- unless File.exists?(File.join(thumbnails_path, attachment))
244
- helper = Alula::AssetHelper.new(File.dirname(attachment), @config)
245
- tn_type, tn_generated = helper.process(File.join("attachments", "originals", attachment), :type => :thumbnail)
268
+ @@lock.synchronize do
269
+ progress.step(:attachments)
246
270
  end
247
-
248
- pb.inc
249
271
  end
250
-
251
- pb.finish
272
+
273
+ progress.finish(:attachments)
274
+ progress.hide
275
+
276
+ # DEBUG
277
+ require 'json'
278
+ File.open(self.storage.path(:cache) + "/mapping.json", 'w') {|io| io.puts self.attachments.mapping.to_json}
252
279
  end
253
280
 
254
- def compile
281
+ def compile_assets
255
282
  puts "==> Compiling assets"
256
283
 
257
- # Package stylesheet
258
- File.open(File.join("_tmp", "assets", "styles.css"), "w") do |tf|
259
- tf.puts "/*"
260
- tf.puts " *=require #{@config["theme"]}"
284
+ # Generate stylesheet
285
+ @storage.output(:cache, "assets/style.css") do |io|
286
+ io.puts "/*"
287
+
288
+ # Theme style
289
+ io.puts " *= require #{self.config.theme}"
290
+
261
291
  # Plugins
262
- if @config["plugins"]
263
- @config["plugins"].each { |plugin, opts| tf.puts " *=require #{plugin}" }
292
+ @plugins.each do |name, plugin|
293
+ io.puts " *= require #{name}"
294
+ end
295
+
296
+ # Vendored
297
+
298
+ # Blog customization
299
+ @storage.custom(/stylesheets\/.*.css.*$/).each do |name, item|
300
+ name = File.basename(name).gsub(/(\.\S+)$/, '')
301
+ io.puts " *= require #{name}"
264
302
  end
265
- tf.puts " */"
303
+
304
+ io.puts "*/"
266
305
  end
306
+ # Add stlesheet to template
307
+ Alula::Plugin.addon(:head, ->(context){ context.stylesheet_link("style") })
267
308
 
268
- # Package javascript
269
- File.open(File.join("_tmp", "assets", "scripts.js"), "w") do |tf|
270
- tf.puts "//=require #{@config["theme"]}"
309
+ # Generate javascript
310
+ @storage.output(:cache, "assets/script.js") do |io|
311
+ io.puts "/*"
312
+
313
+ # Theme scripts
314
+ io.puts " *= require #{self.config.theme}"
315
+
271
316
  # Plugins
272
- if @config["plugins"]
273
- @config["plugins"].each { |plugin, opts| tf.puts "//=require #{plugin}" }
317
+ @plugins.each do |name, plugin|
318
+ io.puts " *= require #{name}"
274
319
  end
275
- end
276
320
 
277
- File.open(File.join("_tmp", "assets", "scripts_body.js"), "w") do |tf|
278
- tf.puts "//=require #{@config["theme"]}_body"
279
- # Plugins
280
- if @config["plugins"]
281
- @config["plugins"].each { |plugin, opts| tf.puts "//=require #{plugin}_body" }
321
+ # Vendored
322
+ io.puts " *= require lazyload" if self.config.attachments.image.lazyload
323
+
324
+ # Customisation
325
+ @storage.custom(/javascripts\/.*.js.*$/).each do |name, item|
326
+ name = File.basename(name).gsub(/(\.\S+)$/, '')
327
+ io.puts " *= require #{name}"
282
328
  end
329
+ io.puts " */"
283
330
  end
331
+ # Add javascript to end of body
332
+ Alula::Plugin.addon(:body, ->(context){ context.javascript_link("script") })
284
333
 
334
+ # Compile all assets
335
+ progress.create :assets, title: "Compiling assets", total: @environment.each_logical_path.count
336
+ progress.display
337
+
338
+ @manifest = Manifest.new(@environment, @storage.path(:assets))
339
+ @manifest.progress = -> { progress.step(:assets) }
285
340
 
286
- @manifest = Sprockets::Manifest.new(@sprockets, File.join("public", "assets"))
287
-
288
- # Compile assets
289
341
  @manifest.compile
290
- # @manifest.compile([
291
- # # Stylesheet
292
- # "styles.css",
293
- #
294
- # # Javascript
295
- # "javascripts.js",
296
- #
297
- # # Attachments
298
- # Dir[File.join("attachments", "**", "*")]
299
- # .select {|f| File.file?(f) }
300
- # .collect {|f| File.join(f.split("/")[1..-1])},
301
- # ])
302
-
303
- # Inject our manifest to jekyll
304
- @jekyll.config["manifest"] = @manifest
305
-
306
- # Cleanup
307
- %w{styles.css scripts.js scripts_body.js}.each do |f|
308
- FileUtils.rm(File.join("_tmp", "assets", f))
309
- end
342
+
343
+ progress.finish(:assets)
344
+ progress.hide
310
345
  end
311
346
 
312
- def process
313
- puts "==> Processing Jekyll site"
314
-
315
- @jekyll.reset
316
- @jekyll.read
317
- @jekyll.generate
318
- @jekyll.render
319
- @jekyll.write
347
+ def render
348
+ say "==> Render site"
349
+
350
+ progress.create :render, title: "Rendering content", total: (self.content.posts.count + self.content.pages.count)
351
+ progress.display
352
+
353
+ # Render all user content, parallel...
354
+ (self.content.posts + self.content.pages).each do |content|
355
+ # Write content to file
356
+ content.write
357
+
358
+ progress.title(:render, "%20s" % content.name[0..19]) if self.config.debug
359
+ progress.step(:render)
360
+ end
361
+
362
+ progress.finish(:render)
363
+
364
+ # Copy static content
365
+ progress.create :static, title: "Copy statics", total: self.content.statics.count
366
+ self.content.statics.each do |static|
367
+ @storage.output_public(static.name) { |io| io.write(static.read) }
368
+
369
+ progress.step :static
370
+ end
371
+ progress.finish :static
372
+
373
+ progress.hide
320
374
  end
321
375
 
322
376
  def cleanup
323
- puts "==> Cleaning up"
377
+ say "==> Cleaning up"
378
+
379
+ asset_path = @storage.path(:assets)
380
+ assets = @environment.used
381
+ .collect{|u| @environment[u]}
382
+ .reject{|u| u.nil?}
383
+ .collect{|u| File.join(asset_path, u.digest_path)}
384
+ outputted = @storage.outputted.reject{|o|o[/^#{asset_path}/]}
385
+
386
+ keep = assets + outputted
387
+ Dir[File.join(@storage.path(:public), "**", "*")].each do |entry|
388
+ next unless File.file?(entry)
389
+ FileUtils.rm entry if File.file?(entry) and !keep.include?(entry)
390
+ end
324
391
 
325
- FileUtils.rm_rf "_tmp"
392
+ # Clean up empty directories
393
+ Dir[File.join(@storage.path(:public), "**", "*")].each do |entry|
394
+ next unless File.directory?(entry)
395
+ FileUtils.rmdir entry if Dir[File.join(entry, "**", "*")].count == 0
396
+ end
326
397
  end
327
- end
328
-
329
- DEFAULTS = Jekyll::DEFAULTS.deep_merge({
330
- 'source' => '_tmp',
331
- 'destination' => 'public',
332
- 'markdown' => 'kramdown',
333
398
 
334
- 'pagination_dir' => '/page/',
335
- })
399
+ # Output helpers
400
+ def say(msg)
401
+ @shell ||= Thor::Shell::Basic.new
402
+ @shell.say msg
403
+ end
404
+ end
336
405
  end