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,136 @@
1
+ require 'generators/base'
2
+ require 'core/paginated_file'
3
+
4
+
5
+ module Yuzu::Generators
6
+ class PaginateGenerator < Generator
7
+
8
+ @@generated = []
9
+ def self.get_generated
10
+ @@generated
11
+ end
12
+
13
+ def self.add_generator(generator)
14
+ @@generated.push(generator)
15
+ end
16
+
17
+ def self.is_generated?(generator)
18
+ @@generated.include?(generator)
19
+ end
20
+
21
+ def initialize
22
+ @name = "paginate"
23
+ @directive = "INSERTCATALOG"
24
+ end
25
+
26
+ def should_generate?(website_file)
27
+ not PaginateGenerator.is_generated?(website_file) and \
28
+ not website_file.raw_contents.scan(regex).flatten.empty?
29
+ end
30
+
31
+ def visitor_filter
32
+ proc {|c| c.file?}
33
+ end
34
+
35
+ def generate!(website_file)
36
+ # Attempt to ensure this website_file isn't generated twice.
37
+ return if website_file.is_a?(Yuzu::Core::PaginatedWebsiteFile) or \
38
+ PaginateGenerator.is_generated?(website_file)
39
+
40
+ CatalogPaginator.new(website_file, self).generate!
41
+ PaginateGenerator.add_generator(website_file)
42
+ end
43
+ end
44
+ Generator.register(:paginate => PaginateGenerator)
45
+
46
+
47
+ class CatalogPaginator
48
+ # Determine whether each catalog requires pagination.
49
+ # Produce the new WebsiteFile objects by replacing each INSERTCATALOG(...) with a new one.
50
+ # Add the new WebsiteFile objects to the parent.
51
+ def initialize(website_file, catalog_generator)
52
+ @website_file = website_file
53
+ @catalog_generator = catalog_generator
54
+ end
55
+
56
+ def generate!
57
+ catalog_args = @website_file.raw_contents.scan(@catalog_generator.regex).flatten
58
+ catalogs = catalogs_from_args(catalog_args)
59
+ generate_files_from_catalogs!(catalogs)
60
+ end
61
+
62
+ def generate_files_from_catalogs!(catalogs)
63
+ paginating_catalogs = catalogs.select {|cat| cat.should_paginate?}
64
+ first_paginating_catalog = paginating_catalogs.empty? ? nil : paginating_catalogs[0]
65
+
66
+ generate_files_for_paginating_catalog!(first_paginating_catalog)
67
+ end
68
+
69
+ # Return an Array of Catalog instances from the given user-specified catalog arguments.
70
+ #
71
+ # @param [Array] catalog_args An Array of Hashes containing the arguments gathered from the
72
+ # INSERTCATALOG directives of the page.
73
+ # @return [Array] An Array of corresponding Catalog objects.
74
+ def catalogs_from_args(catalog_args)
75
+ catalog_args.collect {|args| Yuzu::Filters.catalog_for(@website_file, args)}
76
+ end
77
+
78
+ # Given a Catalog instance, check to see if it requires pagination and generate the appropriate
79
+ # files for the given content.
80
+ #
81
+ # @param [Catalog] catalog The Yuzu::Generator.Catalog object that represents the contents to
82
+ # insert into the page.
83
+ # @return nothing
84
+ def generate_files_for_paginating_catalog!(catalog)
85
+ return if catalog.nil?
86
+
87
+ num_pages = catalog.num_pages
88
+ return if num_pages == 1
89
+
90
+ # Don't generate the first page, it's all ready to go.
91
+ num_pages_to_generate = num_pages - 1
92
+ num_pages_to_generate.times do |page|
93
+ generate_page!(page + 2, catalog)
94
+ end
95
+ end
96
+
97
+ # Add a single PaginatedWebsiteFile instance and add it to the parent of the given WebsiteFile
98
+ # as a sibling for the specified page of the given Catalog.
99
+ #
100
+ # @param [Fixnum] page The page to generate, indexed by 1.
101
+ # @param [Catalog] catalog The Catalog being paginated.
102
+ # @return nothing
103
+ def generate_page!(page, catalog)
104
+ original_directive = catalog.original_directive
105
+ directive_for_page = catalog.directive_for_page(page)
106
+
107
+ new_raw_contents = @website_file.raw_contents.sub(original_directive, directive_for_page)
108
+
109
+ paginated_file = Yuzu::Core::PaginatedWebsiteFile.new(@website_file, new_raw_contents, page)
110
+ add_file(paginated_file)
111
+ end
112
+
113
+ # Takes the PaginatedWebsiteFile instance and adds it as a sibling to the root WebsiteFile being
114
+ # paginated.
115
+ #
116
+ # @param [PaginatedWebsiteFile] paginated_file The new page to add as a sibling.
117
+ # @return nothing
118
+ def add_file(paginated_file)
119
+ container = @website_file.parent
120
+ container.append_child(paginated_file)
121
+ stash_paginated_file(paginated_file)
122
+ end
123
+
124
+ # Registers the generated file in the seed file's stash, enabling us to generate pagination
125
+ # links and other niceties later.
126
+ #
127
+ # @param [PaginatedWebsiteFile] paginated_file A file to add.
128
+ # @return nothing
129
+ def stash_paginated_file(paginated_file)
130
+ paginated_siblings = @website_file.stash[:paginated_siblings] || []
131
+ @website_file.stash(:paginated_siblings => paginated_siblings + [paginated_file])
132
+ end
133
+
134
+ end
135
+ end
136
+
@@ -0,0 +1,48 @@
1
+ require 'postprocessors/base'
2
+ require 'core/visitor'
3
+
4
+ module Yuzu::PostProcessors
5
+
6
+ class AllCategoriesPostProcessor < PostProcessor
7
+ def initialize
8
+ @name = :all_categories
9
+ end
10
+
11
+ def regex
12
+ end
13
+
14
+ def default_value(website_file)
15
+ []
16
+ end
17
+
18
+ def value(website_file)
19
+ # There should only be one set of categories no matter which WebsiteFile is asked to gather
20
+ # them. So we can cache the value in this singleton.
21
+ @value ||= get_value(website_file)
22
+ end
23
+
24
+ def get_value(website_file)
25
+ cats = []
26
+ found_names = []
27
+
28
+ v = Yuzu::Core::Visitor.new(proc {|c| c.file?})
29
+ v.traverse(website_file.root) do |f|
30
+ # NOTE Because of Ruby's hashing algorithm, we'll get different results calling uniq on an
31
+ # array with objects hashed against a simple string. The comparison doesn't produce truly
32
+ # unique results, and the categories.uniq call isn't accurate or consistent.
33
+
34
+ f.categories.each do |cat|
35
+ if not found_names.include?(cat.name) and not cat.name == "uncategorized"
36
+ found_names.push(cat.name)
37
+ cats.push(cat)
38
+ end
39
+ end
40
+ end
41
+
42
+ cats.sort
43
+ end
44
+ end
45
+ PostProcessor.register(:all_categories => AllCategoriesPostProcessor)
46
+
47
+ end
48
+
@@ -0,0 +1,34 @@
1
+
2
+ module Yuzu::PostProcessors
3
+ include Yuzu::Registrar
4
+
5
+ class PostProcessor < Register
6
+ @@postprocessors = {}
7
+ def self.registry
8
+ :postprocessors
9
+ end
10
+ def self.postprocessors
11
+ @@postprocessors
12
+ end
13
+
14
+ attr_reader :name
15
+
16
+ def initialize
17
+ @name = :postprocessor
18
+ end
19
+
20
+ def value(website_file)
21
+ match(website_file.rendered_contents)
22
+ end
23
+
24
+ def match(contents)
25
+ contents.to_s.match(regex)
26
+ end
27
+
28
+ def regex
29
+ /.*/
30
+ end
31
+ end
32
+
33
+ end
34
+
@@ -0,0 +1,20 @@
1
+ require 'postprocessors/base'
2
+
3
+ module Yuzu::PostProcessors
4
+ class ContentsWithoutFirstParagraphPostProcessor < PostProcessor
5
+ def initialize
6
+ @name = :contents_without_first_paragraph
7
+ @directive = "CONTENTSWITHOUTFIRSTPARAGRAPH"
8
+ end
9
+
10
+ def regex
11
+ /<p\b[^>]*?>(.*?)<\/p>/n
12
+ end
13
+
14
+ def match(contents)
15
+ contents.sub(regex, "")
16
+ end
17
+ end
18
+ PostProcessor.register(:contents_without_first_paragraph => ContentsWithoutFirstParagraphPostProcessor)
19
+ end
20
+
@@ -0,0 +1,23 @@
1
+ require 'postprocessors/base'
2
+
3
+ module Yuzu::PostProcessors
4
+ class ExcerptPostProcessor < PostProcessor
5
+ def initialize
6
+ @name = :excerpt
7
+ @directive = "EXCERPT"
8
+ end
9
+
10
+ def regex
11
+ # Grab the entire paragraph, with the tag.
12
+ /<p\b[^>]*?>.*?<\/p>/n
13
+ end
14
+
15
+ def match(contents)
16
+ num_paragraphs = 5
17
+ m = contents.scan(regex)
18
+ m.nil? ? "" : m[0...5].join
19
+ end
20
+ end
21
+ PostProcessor.register(:excerpt => ExcerptPostProcessor)
22
+ end
23
+
@@ -0,0 +1,16 @@
1
+ require 'postprocessors/base'
2
+
3
+ module Yuzu::PostProcessors
4
+ class FirstParagraphPostProcessor < PostProcessor
5
+ def initialize
6
+ @name = :first_paragraph
7
+ @directive = "FIRSTPARAGRAPH"
8
+ end
9
+
10
+ def regex
11
+ /<p\b[^>]*?>(.*?)<\/p>/n
12
+ end
13
+ end
14
+ PostProcessor.register(:first_paragraph => FirstParagraphPostProcessor)
15
+ end
16
+
@@ -0,0 +1,35 @@
1
+ require 'postprocessors/base'
2
+ require 'html/base'
3
+
4
+ module Yuzu::PostProcessors
5
+ class PaginationPostProcessor < PostProcessor
6
+ def initialize
7
+ @name = :pagination
8
+ @directive = "PAGINATION"
9
+ end
10
+
11
+ def value(website_file)
12
+ root_file = website_file.paginated? ? website_file.original_file : website_file
13
+ pagination_catalog = root_file.stash[:catalog]
14
+ return "" if pagination_catalog.nil?
15
+ num_pages = pagination_catalog.num_pages
16
+
17
+ pages = [root_file] + root_file.stash[:paginated_siblings]
18
+
19
+ links = pages.collect do |page|
20
+ page_num = pages.find_index(page) + 1
21
+ is_current_page = page == website_file
22
+ if is_current_page
23
+ Html::Span.new << page_num.to_s
24
+ else
25
+ Html::Link.new(:href => page.link_url) << page_num.to_s
26
+ end
27
+ end
28
+
29
+ return Html::Div.new(:class => "pagination-links") << links.join(" ")
30
+ end
31
+ end
32
+ PostProcessor.register(:pagination => PaginationPostProcessor)
33
+
34
+ end
35
+
@@ -0,0 +1,27 @@
1
+ require 'postprocessors/base'
2
+
3
+ module Yuzu::PostProcessors
4
+
5
+ class RecentPostsPostProcessor < PostProcessor
6
+ def initialize
7
+ @name = :recent_posts
8
+ end
9
+
10
+ def regex
11
+ end
12
+
13
+ def value(website_file)
14
+ # Only one set of recent posts per site, so we can cache this.
15
+ @value ||= get_value(website_file)
16
+ end
17
+
18
+ def get_value(website_file)
19
+ processable = website_file.blog_folder.all_processable_children
20
+ user_authored = processable.reject {|file| file.generated? or file.index?}
21
+ user_authored.sort {|a, b| b.modified_at <=> a.modified_at}[0...10]
22
+ end
23
+ end
24
+ PostProcessor.register(:recent_posts => RecentPostsPostProcessor)
25
+
26
+ end
27
+
@@ -0,0 +1,48 @@
1
+ require 'postprocessors/base'
2
+ require 'html/base'
3
+
4
+ module Yuzu::PostProcessors
5
+ class ThumbnailsPostProcessor < PostProcessor
6
+ def initialize
7
+ @name = "thumbnails"
8
+ end
9
+
10
+ def value(website_file)
11
+ thumbnails = website_file.config.thumbnails
12
+ images = website_file.images
13
+ Thumbnails.new(images, thumbnails.keys)
14
+ end
15
+ end
16
+ PostProcessor.register(:thumbnails => ThumbnailsPostProcessor)
17
+
18
+
19
+ class Thumbnails
20
+ def initialize(images, thumbnail_sizes)
21
+ image_path = images.empty? ? nil : images[0]
22
+
23
+ (class << self; self; end).class_eval do
24
+ thumbnail_sizes.each do |size|
25
+ define_method size do
26
+ begin
27
+
28
+ if image_path.nil?
29
+ ""
30
+ else
31
+ ext = File.extname(image_path)
32
+ new_name = image_path.sub(ext, "-#{size}#{ext}")
33
+ Html::Image.new(:src => new_name).to_s
34
+ end
35
+ rescue => e
36
+ puts "Exception in thumbnails"
37
+ puts e.message
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+
@@ -0,0 +1,71 @@
1
+
2
+ module Yuzu::PreProcessors
3
+ include Yuzu::Registrar
4
+
5
+ # Preprocessors differ from PostProcesors and Filters in that they are allowed to modify the
6
+ # raw_contents of a website_file. e.g. When contents are inserted from other files, they are done
7
+ # so in a manner to create the illusion that those contents were part of the original file.
8
+ class PreProcessor < Register
9
+ @@preprocessors = {}
10
+ def self.registry
11
+ :preprocessors
12
+ end
13
+ def self.preprocessors
14
+ @@preprocessors
15
+ end
16
+
17
+ attr_reader :name
18
+
19
+ def initialize
20
+ @name = :preprocessor
21
+ @directive = "PREPROCESSOR"
22
+ end
23
+
24
+ def value(website_file)
25
+ match(website_file.raw_contents)
26
+ end
27
+
28
+ def match(contents)
29
+ m = contents.to_s.match(regex)
30
+ m[1]
31
+ end
32
+
33
+ def regex
34
+ Regexp.new('^\s*' + @directive.to_s + '\(([\w\s\.\,\'\"\/\-:]*?)\)')
35
+ end
36
+
37
+ # Returns the contents to replace the directive with as written. This will modify the raw
38
+ # contents of the website_file.
39
+ #
40
+ # @param [WebsiteFile] website_file The page in which the directive appears
41
+ # @param [String, nil] new_contents The contents of the given WebsiteFile as they are
42
+ # being transformed by processing.
43
+ # @return [String] What to replace the directive with.
44
+ def replacement(website_file, new_contents="")
45
+ website_file.raw_contents
46
+ end
47
+
48
+ def process(website_file, new_contents)
49
+ replaced = false
50
+
51
+ m = new_contents.match(regex)
52
+
53
+ while not m.nil?
54
+ repl = replacement(website_file, new_contents)
55
+
56
+ # Remove the next match.
57
+ new_contents = new_contents.sub(regex, repl.to_s)
58
+ replaced = true
59
+
60
+ # Find any others...
61
+ m = new_contents.match(regex)
62
+ end
63
+
64
+ if replaced
65
+ website_file.instance_variable_set(:@raw_contents, new_contents)
66
+ end
67
+ end
68
+ end
69
+
70
+ end
71
+