yuzu 0.2.1.pre

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 (116) hide show
  1. data/.document +5 -0
  2. data/.yardopts +7 -0
  3. data/ChangeLog.md +8 -0
  4. data/Gemfile +26 -0
  5. data/Gemfile.lock +30 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.md +52 -0
  8. data/Rakefile +62 -0
  9. data/bin/yuzu +17 -0
  10. data/docs/About.md +19 -0
  11. data/docs/GettingStarted.md +48 -0
  12. data/docs/Reference.md +97 -0
  13. data/lib/helpers/object.rb +19 -0
  14. data/lib/helpers/path.rb +296 -0
  15. data/lib/helpers/string.rb +35 -0
  16. data/lib/helpers/system_checks.rb +10 -0
  17. data/lib/helpers/url.rb +64 -0
  18. data/lib/html/base.rb +187 -0
  19. data/lib/uploader/base.rb +59 -0
  20. data/lib/uploader/config.rb +19 -0
  21. data/lib/uploader/filesystem_service.rb +72 -0
  22. data/lib/uploader/ftp_service.rb +90 -0
  23. data/lib/uploader/s3_service.rb +135 -0
  24. data/lib/uploader/service.rb +57 -0
  25. data/lib/uploader/suppressor.rb +25 -0
  26. data/lib/yuzu.rb +6 -0
  27. data/lib/yuzu/argparse.rb +60 -0
  28. data/lib/yuzu/command.rb +104 -0
  29. data/lib/yuzu/commands/base.rb +150 -0
  30. data/lib/yuzu/commands/create.rb +68 -0
  31. data/lib/yuzu/commands/generate.rb +95 -0
  32. data/lib/yuzu/commands/help.rb +20 -0
  33. data/lib/yuzu/commands/preview.rb +58 -0
  34. data/lib/yuzu/commands/publish.rb +43 -0
  35. data/lib/yuzu/commands/stage.rb +62 -0
  36. data/lib/yuzu/commands/watch.rb +70 -0
  37. data/lib/yuzu/content/blog_post.rb +45 -0
  38. data/lib/yuzu/content/sample_project.rb +130 -0
  39. data/lib/yuzu/core/config.rb +154 -0
  40. data/lib/yuzu/core/layout.rb +85 -0
  41. data/lib/yuzu/core/paginated_file.rb +69 -0
  42. data/lib/yuzu/core/registrar.rb +32 -0
  43. data/lib/yuzu/core/siteroot.rb +57 -0
  44. data/lib/yuzu/core/template.rb +158 -0
  45. data/lib/yuzu/core/updater.rb +123 -0
  46. data/lib/yuzu/core/visitor.rb +44 -0
  47. data/lib/yuzu/core/website_base.rb +150 -0
  48. data/lib/yuzu/core/website_file.rb +270 -0
  49. data/lib/yuzu/core/website_folder.rb +176 -0
  50. data/lib/yuzu/filters/base.rb +86 -0
  51. data/lib/yuzu/filters/catalog.rb +248 -0
  52. data/lib/yuzu/filters/categories.rb +58 -0
  53. data/lib/yuzu/filters/currentpath.rb +35 -0
  54. data/lib/yuzu/filters/description.rb +16 -0
  55. data/lib/yuzu/filters/extension.rb +16 -0
  56. data/lib/yuzu/filters/images.rb +32 -0
  57. data/lib/yuzu/filters/linkroot.rb +35 -0
  58. data/lib/yuzu/filters/post_date.rb +45 -0
  59. data/lib/yuzu/filters/post_title.rb +66 -0
  60. data/lib/yuzu/filters/post_title_removed.rb +28 -0
  61. data/lib/yuzu/filters/sidebar.rb +26 -0
  62. data/lib/yuzu/filters/template.rb +16 -0
  63. data/lib/yuzu/generators/base.rb +44 -0
  64. data/lib/yuzu/generators/category_folders.rb +91 -0
  65. data/lib/yuzu/generators/index.rb +108 -0
  66. data/lib/yuzu/generators/paginate.rb +136 -0
  67. data/lib/yuzu/postprocessors/all_categories.rb +48 -0
  68. data/lib/yuzu/postprocessors/base.rb +34 -0
  69. data/lib/yuzu/postprocessors/contents_without_first_paragraph.rb +20 -0
  70. data/lib/yuzu/postprocessors/excerpt.rb +23 -0
  71. data/lib/yuzu/postprocessors/first_paragraph.rb +16 -0
  72. data/lib/yuzu/postprocessors/pagination.rb +35 -0
  73. data/lib/yuzu/postprocessors/recent_posts.rb +27 -0
  74. data/lib/yuzu/postprocessors/thumbnails.rb +48 -0
  75. data/lib/yuzu/preprocessors/base.rb +71 -0
  76. data/lib/yuzu/preprocessors/insert_contents.rb +57 -0
  77. data/lib/yuzu/renderers/base.rb +23 -0
  78. data/lib/yuzu/renderers/breadcrumb.rb +163 -0
  79. data/lib/yuzu/renderers/gallery.rb +24 -0
  80. data/lib/yuzu/renderers/title.rb +21 -0
  81. data/lib/yuzu/translators/base.rb +57 -0
  82. data/lib/yuzu/translators/markdown.rb +21 -0
  83. data/lib/yuzu/translators/plaintext.rb +17 -0
  84. data/lib/yuzu/version.rb +12 -0
  85. data/resources/config/compass.rb +6 -0
  86. data/resources/config/yuzu.yml +166 -0
  87. data/resources/git/post-commit +42 -0
  88. data/resources/sample_content/introduction/_snippets/about_insert_contents.md +3 -0
  89. data/resources/sample_content/introduction/about.md +6 -0
  90. data/resources/sample_content/introduction/advanced-posts.md +18 -0
  91. data/resources/sample_content/introduction/blog/blog-folder-is-special.md +8 -0
  92. data/resources/sample_content/introduction/getting-started.md +14 -0
  93. data/resources/sample_content/introduction/index.md +6 -0
  94. data/resources/sample_content/introduction/sample-post.md +13 -0
  95. data/resources/sample_projects.yml +7 -0
  96. data/resources/themes/minimal/_sass/print.sass +0 -0
  97. data/resources/themes/minimal/_sass/screen.sass +81 -0
  98. data/resources/themes/minimal/_templates/_block.haml +4 -0
  99. data/resources/themes/minimal/_templates/_blog.haml +3 -0
  100. data/resources/themes/minimal/_templates/_footer.haml +2 -0
  101. data/resources/themes/minimal/_templates/_gallery.haml +25 -0
  102. data/resources/themes/minimal/_templates/_head.haml +12 -0
  103. data/resources/themes/minimal/_templates/_header.haml +1 -0
  104. data/resources/themes/minimal/_templates/_menu.haml +6 -0
  105. data/resources/themes/minimal/_templates/blog.haml +21 -0
  106. data/resources/themes/minimal/_templates/generic.haml +26 -0
  107. data/resources/themes/minimal/_templates/home.haml +15 -0
  108. data/resources/themes/minimal/_templates/index.haml +21 -0
  109. data/resources/themes/minimal/css/print.css +0 -0
  110. data/resources/themes/minimal/css/screen.css +133 -0
  111. data/resources/themes/minimal/img/favicon.png +0 -0
  112. data/resources/yard/default/fulldoc/html/css/common.css +16 -0
  113. data/test/helper.rb +18 -0
  114. data/test/test_yuzu.rb +8 -0
  115. data/yuzu.gemspec +182 -0
  116. metadata +302 -0
@@ -0,0 +1,69 @@
1
+ require 'core/website_file'
2
+
3
+ module Yuzu::Core
4
+ class PaginatedWebsiteFile < WebsiteFile
5
+ attr_reader :original_file
6
+
7
+ # Initialize a new generated file from an existing WebsiteFile
8
+ #
9
+ # @param [WebsiteFile] original_file The root file representing page 1.
10
+ # @param [String] new_raw_contents The new contents with the INSERTCATALOG directives updated to
11
+ # include the page:N argument.
12
+ # @param [Fixnum] page The page that this generated file represents.
13
+ def initialize(original_file, new_raw_contents, page)
14
+ @raw_contents = new_raw_contents
15
+ @page = page
16
+
17
+ @path = Yuzu::Core.get_paginated_path(original_file.path, page)
18
+ raise "@path is nil in #{self}" if @path.nil?
19
+
20
+ @original_file = original_file
21
+ @parent = original_file.parent
22
+ @kind = :file
23
+ end
24
+
25
+ def to_s
26
+ "PaginatedWebsiteFile(#{@path.relative})"
27
+ end
28
+
29
+ def get_raw_contents
30
+ @raw_contents
31
+ end
32
+
33
+ def modified_at
34
+ @original_file.modified_at
35
+ end
36
+
37
+ def created_at
38
+ @original_file.created_at
39
+ end
40
+
41
+ def load_file_info!
42
+ # No need to do anything here. All dependent methods are overridden.
43
+ end
44
+
45
+ def generated?
46
+ true
47
+ end
48
+
49
+ def paginated?
50
+ true
51
+ end
52
+ end
53
+
54
+ # Calculate the page of this file, assuming it's a file produced from pagination.
55
+ #
56
+ # @param [Path] original_path The path of the original seed file being paginated.
57
+ # @param [Fixnum] page The page of the new file.
58
+ # @return [Path] The new path in the format path/to/file_N.ext where N is the page number.
59
+ def get_paginated_path(original_path, page)
60
+ new_path = original_path.dup
61
+ new_path.make_file!
62
+ tr = new_path.add_suffix(page)
63
+ raise "New path is nil!" if tr.nil?
64
+ tr.make_file!
65
+ tr
66
+ end
67
+ module_function :get_paginated_path
68
+
69
+ end
@@ -0,0 +1,32 @@
1
+
2
+ module Yuzu::Registrar
3
+
4
+ # Class Register includes routines for storing singletons of objects with a common base class that
5
+ # can be executed as a set.
6
+ class Register
7
+ # Subclasses should redefine `registry` with a unique identifier equal to the name of the class
8
+ # variable that will contain the singletons.
9
+ @@registered = {}
10
+ def self.registry
11
+ :registered
12
+ end
13
+ def self.registered
14
+ @@registered
15
+ end
16
+
17
+ # Subclasses of the Register should call this method after their definitions to register them
18
+ # into the registry.
19
+ def self.register(name_to_class={})
20
+ class_var = "@@#{self.registry}".to_sym
21
+ register = class_variable_get(class_var)
22
+ if register.nil?
23
+ class_variable_set(class_var, {})
24
+ register = {}
25
+ end
26
+ name_to_instance = Hash[name_to_class.collect {|name, klass| [name, klass.new]}]
27
+ class_variable_set(class_var, register.merge(name_to_instance))
28
+ end
29
+ end
30
+
31
+ end
32
+
@@ -0,0 +1,57 @@
1
+ require 'core/website_folder'
2
+
3
+ Dir["#{File.dirname(__FILE__)}/../generators/*"].each { |c| require c }
4
+
5
+ module Yuzu::Core
6
+ class SiteRoot < WebsiteFolder
7
+ include Yuzu::Generators
8
+
9
+ attr_reader :config
10
+ def initialize(yuzu_config)
11
+ @config = yuzu_config
12
+ @path = @config.pwd
13
+ @parent = nil
14
+ @kind = :folder
15
+
16
+ generate!
17
+ end
18
+
19
+ def name
20
+ @config.site_name
21
+ end
22
+
23
+ # Given a Path, return the WebsiteFile or WebsiteFolder corresponding to the Path.
24
+ def find_file_by_path(path)
25
+ path_elements = path.relative.split(File::SEPARATOR)
26
+
27
+ parent = self
28
+ path_elements.each do |element|
29
+ child = parent.get_child_by_rootname(element)
30
+ return nil if child.nil?
31
+ parent = child
32
+ end
33
+
34
+ return parent
35
+ end
36
+
37
+ def generators
38
+ Generator.generators
39
+ end
40
+
41
+ # Process all the generators to make all the new files required from things like pagination of
42
+ # catalogs and new index files.
43
+ def generate!
44
+ generators.each_pair do |name, generator|
45
+ v = Visitor.new(generator.visitor_filter)
46
+
47
+ v.traverse(self) do |node|
48
+ if generator.should_generate?(node)
49
+ generator.generate!(node)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ end
57
+
@@ -0,0 +1,158 @@
1
+ require 'haml'
2
+ require 'helpers/path'
3
+
4
+ module Yuzu::Core
5
+ class Template
6
+ include Helpers
7
+
8
+ # TODO Make this part of a module-level config.
9
+ @@template_dir = Path.new("_templates")
10
+
11
+ # template_name -- String. The filename of the template, e.g. _gallery.haml
12
+ def initialize(template_name)
13
+ @template_name = template_name
14
+ end
15
+
16
+ def path
17
+ @@template_dir + @template_name
18
+ end
19
+
20
+ def exists?
21
+ path.exists?
22
+ end
23
+
24
+ def fallback_path
25
+ # TODO Configure fallbacks in a more general way.
26
+ if @template_name[0].chr == "_"
27
+ @@template_dir + "_block.haml"
28
+ else
29
+ @@template_dir + "generic.haml"
30
+ end
31
+ end
32
+
33
+ def fallback_exists?
34
+ fallback_path.exists?
35
+ end
36
+
37
+ def contents
38
+ @contents ||= get_contents
39
+ end
40
+
41
+ def get_contents
42
+ tr = get_template_contents(path)
43
+ tr.nil? ? get_template_contents(fallback_path) : tr
44
+ end
45
+
46
+ def get_template_contents(file_path)
47
+ if file_path.exists?
48
+ f = File.open(file_path.absolute, 'r')
49
+ contents = f.read
50
+ f.close
51
+ contents
52
+ else
53
+ $stderr.puts "WARNING: Couldn't find template #{file_path}"
54
+ nil
55
+ end
56
+ end
57
+
58
+ def render(website_file, data={})
59
+ ""
60
+ end
61
+ end
62
+
63
+
64
+ class HamlTemplate < Template
65
+ def options
66
+ {:format => :html5}
67
+ end
68
+
69
+ def engine
70
+ @engine ||= Haml::Engine.new(contents, options)
71
+ end
72
+
73
+ def locals(website_file)
74
+ {
75
+ :post => website_file.properties,
76
+ :config => website_file.config
77
+ }
78
+ end
79
+
80
+ def render(website_file, data={})
81
+ engine.render(
82
+ TemplateMethods.new(website_file.root),
83
+ locals(website_file).merge(data)
84
+ )
85
+ end
86
+ end
87
+
88
+
89
+ # The root object that contains functions that can be called from inside the Haml template.
90
+ class TemplateMethods
91
+ def initialize(siteroot)
92
+ @siteroot = siteroot
93
+ end
94
+
95
+ def insert_raw_file(filename)
96
+ insert_contents(filename, :raw_contents)
97
+ end
98
+
99
+ def insert_rendered_contents(filename)
100
+ insert_contents(filename, :rendered_contents)
101
+ end
102
+
103
+ def insert_contents(filename, method=:raw_contents)
104
+ path = Helpers::Path.new(filename)
105
+ return (Html::Comment.new << "File #{filename} not found.") if not path.exists?
106
+
107
+ website_file = @siteroot.find_file_by_path(path)
108
+
109
+ if not website_file.nil?
110
+ website_file.send(method)
111
+
112
+ else
113
+
114
+ f = File.open(filename, "r")
115
+ contents = f.read
116
+ f.close
117
+
118
+ return contents if method == :raw_contents
119
+ Yuzu::Translators::Translator.translate(contents, path.extension)
120
+
121
+ end
122
+ end
123
+
124
+ def format_date(raw_date)
125
+ if not raw_date.is_a?(String) and not raw_date.is_a?(Time)
126
+ return raw_date
127
+ end
128
+
129
+ if raw_date.is_a?(String)
130
+ raw_date = Time.parse(raw_date)
131
+ end
132
+
133
+ date = raw_date.strftime("%Y-%m-%d")
134
+
135
+ months = {
136
+ "01" => "January",
137
+ "02" => "February",
138
+ "03" => "March",
139
+ "04" => "April",
140
+ "05" => "May",
141
+ "06" => "June",
142
+ "07" => "July",
143
+ "08" => "August",
144
+ "09" => "September",
145
+ "10" => "October",
146
+ "11" => "November",
147
+ "12" => "December"
148
+ }
149
+
150
+ year, month, day = date.split("-")[0..2]
151
+ month = months[month]
152
+
153
+ "#{year} #{month} #{day}"
154
+ end
155
+
156
+ end
157
+ end
158
+
@@ -0,0 +1,123 @@
1
+ require 'core/visitor'
2
+ require 'helpers/path'
3
+ require 'core/siteroot'
4
+
5
+
6
+
7
+ module Yuzu::Core
8
+ BOLD = "\033[1m"
9
+ WHITE = "\033[37m"
10
+ ENDC = "\033[0m"
11
+
12
+ # Updater is the primary mechanism to update a website and filter for the files that need
13
+ # updating.
14
+ class Updater
15
+
16
+ def initialize(uploader, config)
17
+ @uploader = uploader
18
+ @config = config
19
+
20
+ @siteroot = SiteRoot.new(@config)
21
+
22
+ $stderr.puts "Updater initialized..." if @config.verbose?
23
+ end
24
+
25
+ def update_all
26
+ $stderr.puts "Updating all..." if @config.verbose?
27
+
28
+ filter = proc {|c| (c.processable? or c.resource? or c.image? or c.asset?) and not c.hidden?}
29
+ update_by_filter(filter)
30
+ end
31
+
32
+ def update_text
33
+ $stderr.puts "Updating text files..." if @config.verbose?
34
+
35
+ filter = proc {|c| c.processable? and not c.hidden?}
36
+ update_by_filter(filter)
37
+ end
38
+
39
+ def upload_new_images(known_images=[])
40
+ filter = proc {|c| c.image? and not c.hidden? and not known_images.include?(c)}
41
+ update_by_filter(filter)
42
+ end
43
+
44
+ def upload_all_images
45
+ filter = proc {|c| c.image? and not c.hidden?}
46
+ update_by_filter(filter)
47
+ end
48
+
49
+ def upload_all_assets
50
+ filter = proc {|c| c.asset? and not c.hidden?}
51
+ update_by_filter(filter)
52
+ end
53
+
54
+ def upload_all_resources
55
+ filter = proc {|c| c.resource? and not c.hidden?}
56
+ update_by_filter(filter)
57
+ end
58
+
59
+ def upload_all_css
60
+ filter = proc {|c| c.path.extension == ".css" and not c.hidden?}
61
+ update_by_filter(filter)
62
+ end
63
+
64
+ def update_by_filter(proc_filter)
65
+ updated = []
66
+ visit = Visitor.new(proc_filter)
67
+
68
+ visit.traverse(@siteroot) do |file|
69
+ update_file(file)
70
+ updated.push(file)
71
+ end
72
+
73
+ updated
74
+ end
75
+
76
+ # Update a single WebsiteFile. This effectively initiates the uploader to publish the file to
77
+ # the destination specified by the currently selected service.
78
+ def update_file(website_file)
79
+ $stderr.puts "#{BOLD}#{WHITE}Updating #{website_file}#{ENDC}#{ENDC}" if @config.verbose?
80
+
81
+ if website_file.processable?
82
+ @uploader.upload(website_file.remote_path, website_file.html_contents)
83
+
84
+ elsif website_file.resource? or website_file.image? or website_file.asset?
85
+ f = File.open(website_file.path.absolute, "r")
86
+ @uploader.upload(website_file.remote_path, f)
87
+ f.close
88
+
89
+ else
90
+ $stderr.puts "Can't update #{website_file}." if @config.verbose?
91
+
92
+ end
93
+ end
94
+
95
+ # Update a particular list of files. This is relatively expensive as it requires searching
96
+ # through the file tree, but it should work well for a few files.
97
+ #
98
+ # @param [Array] files_to_update An Array of Strings holding absolute file paths or paths
99
+ # relative to the project folder.
100
+ def update_these(files_to_update=[])
101
+ if files_to_update.empty?
102
+ update_all
103
+ return
104
+ end
105
+
106
+ $stderr.puts "Updating files..." if @config.verbose?
107
+
108
+ files_to_update.each do |relative_path|
109
+ p = Helpers::Path.new(relative_path)
110
+ website_file = @siteroot.find_file_by_path(p)
111
+ if not website_file.nil?
112
+ update_file(website_file)
113
+ else
114
+ $stderr.puts "Couldn't find a WebsiteFile for #{p}" if @config.verbose?
115
+ end
116
+ end
117
+ end
118
+
119
+ def done
120
+ @uploader.close! unless @uploader.nil?
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,44 @@
1
+
2
+ module Yuzu::Core
3
+
4
+ # Visitor is a convenience mechanism for traversing a tree structure. To be traversable, objects
5
+ # in the data structure must have the following interface defined:
6
+ #
7
+ # def children
8
+ # return an array of nodes or nil
9
+ # end
10
+ #
11
+ # WebsiteBase and Path have this interface, so are traversable. Given a Proc that returns a
12
+ # boolean, we can limit the operation performed by the visitor to a subset of available nodes.
13
+ #
14
+ # Usage:
15
+ #
16
+ # filter_for_files_only = proc {|c| c.file?}
17
+ # v = Visitor.new(filter_for_files_only)
18
+ # v.traverse(root) do |child|
19
+ # # Do something with child.
20
+ # end
21
+ #
22
+ # Each node passing the `filter_for_files_only` filter will be executed in the given block.
23
+ class Visitor
24
+
25
+ # Create a visitor
26
+ #
27
+ # @param [Proc] filter A proc that returns true/false given the child object in question
28
+ def initialize(filter=nil)
29
+ #raise "Visitor must be given a Proc." if not filter.is_a?(Proc)
30
+ @filter = filter.nil? ? proc {|c| true} : filter
31
+ end
32
+
33
+ def traverse(container, &block)
34
+ return if container.children.nil?
35
+
36
+ container.children.each do |child|
37
+ block.call(child) if @filter.call(child)
38
+ self.traverse(child, &block) if not child.children.nil?
39
+ end
40
+ end
41
+ end
42
+
43
+ end
44
+