yuzu 0.2.1.pre

Sign up to get free protection for your applications and to get access to all the features.
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,90 @@
1
+ require 'uploader/base'
2
+ require 'uploader/service'
3
+ require 'net/ftp'
4
+ require 'stringio'
5
+
6
+ module Uploader
7
+ class FtpService < Service
8
+
9
+ def connect!
10
+ $stderr.puts "Opening a persistent FTP connection to #{@config.host}..."
11
+
12
+ @ftp = Net::FTP.new
13
+ @ftp.passive = true
14
+ @ftp.connect(@config.host)
15
+ @ftp.login(@config.username, @config.password)
16
+ rescue
17
+ $strerr.puts "Something went wrong with the FTP login."
18
+ end
19
+
20
+ def close!
21
+ @ftp.close
22
+ end
23
+
24
+ def initialized?
25
+ not @ftp.nil?
26
+ end
27
+
28
+ def upload(remote_path, contents)
29
+ raise UploadArgumentError if not remote_path.is_a?(Path)
30
+
31
+ if contents.is_a?(File)
32
+ upload_via_ftp(remote_path, contents)
33
+
34
+ elsif contents.is_a?(String)
35
+ upload_contents_via_ftp(remote_path, contents)
36
+
37
+ else
38
+ raise UnrecognizedContentType
39
+
40
+ end
41
+ end
42
+
43
+ def upload_contents_via_ftp(remote_path, contents)
44
+ file = StringIO.new(contents)
45
+ upload_via_ftp(remote_path, file, false)
46
+ end
47
+
48
+ def upload_via_ftp(remote_path, file, binary=true)
49
+ server_path = Path.new(@config.remote_root_path) + remote_path.relative
50
+
51
+ $stderr.puts "Opening an FTP connection for #{remote_path}"
52
+
53
+ begin
54
+ attempt_upload!(server_path, file, binary)
55
+ rescue => e
56
+ $stderr.puts "FTP Error"
57
+ $stderr.puts e.message
58
+
59
+ create_remote_folder!(server_path)
60
+ attempt_upload!(server_path, file, binary)
61
+ end
62
+ end
63
+
64
+ def attempt_upload!(server_path, file, binary)
65
+ $stderr.puts "Path on server: #{server_path}"
66
+ if binary
67
+ file.binmode
68
+ @ftp.storbinary("STOR #{server_path}", file, Net::FTP::DEFAULT_BLOCKSIZE)
69
+ else
70
+ @ftp.storlines("STOR #{server_path}", file)
71
+ end
72
+ end
73
+
74
+ def create_remote_folder!(server_path)
75
+ server_path.descend do |path|
76
+ begin
77
+ @ftp.mkdir(path)
78
+ rescue Net::FTPPermError
79
+ $stderr.puts "Remote folder already exists."
80
+ rescue
81
+ # Ok.
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ Service.register(:ftp => FtpService)
88
+ end
89
+
90
+
@@ -0,0 +1,135 @@
1
+ require 'helpers/system_checks'
2
+ require 'uploader/base'
3
+ require 'uploader/service'
4
+ require 'uploader/suppressor'
5
+
6
+ module Uploader
7
+ class S3Service < Service
8
+
9
+ def connect!
10
+ # TODO S3 may not work yet.
11
+
12
+ if not SystemChecks.gem_available?('aws/s3')
13
+ $stderr.puts %Q{\nThe Amazon S3 service requires the aws-s3 gem (vers >= 0.6.2), which is
14
+ not required by default. Run 'gem install aws-s3' to install it.\n\n}
15
+ raise LoadError
16
+ end
17
+
18
+ @bucket_name = @config.bucket
19
+ access_key = ENV[@config.access_key]
20
+ secret_key = ENV[@config.secret_key]
21
+
22
+ $stderr.puts "Connecting to AWS..."
23
+
24
+ @suppressor.shutup!
25
+
26
+ unless AWS::S3::Base.connected?
27
+ options = {
28
+ :access_key_id => access_key,
29
+ :secret_access_key => secret_key,
30
+ :use_ssl => true,
31
+ :port => 443
32
+ }
33
+
34
+ if @config.use_proxy
35
+ options.update({
36
+ :proxy => {
37
+ :host => @config.proxy['host'],
38
+ :port => @config.proxy['port']
39
+ }
40
+ })
41
+ end
42
+
43
+ AWS::S3::Base.establish_connection!(options)
44
+ @suppressor.ok
45
+
46
+ else
47
+ @suppressor.ok
48
+ $stderr.puts "AWS already connected."
49
+ end
50
+
51
+ @s3_bucket = AWS::S3::Bucket.find(@bucket_name)
52
+ end
53
+
54
+ def close!
55
+ AWS::S3::Base.disconnect!
56
+ end
57
+
58
+ def upload(remote_path, contents)
59
+ raise UploadArgumentError if not remote_path.is_a?(Path)
60
+
61
+ if contents.is_a?(File)
62
+ contents_to_upload = contents.read
63
+
64
+ elsif contents.is_a?(String)
65
+ contents = contents
66
+
67
+ else
68
+ raise UnrecognizedContentType
69
+
70
+ end
71
+
72
+ upload_contents_to_s3(local_path, contents_to_upload)
73
+ end
74
+
75
+ def upload_contents_to_s3(remote_path, contents, mime_type=nil)
76
+ $stderr.puts "Attempting to upload #{remote_path} to S3"
77
+
78
+ destination = Path.new(@config.remote_root_path) + remote_path
79
+
80
+ @suppressor.shutup!
81
+
82
+ # Prepare the object.
83
+ object = @s3_bucket.new_object
84
+ object.key = destination
85
+ object.value = contents
86
+
87
+ # Store the object first...
88
+ object.store
89
+
90
+ #if not mime_type.blank?
91
+ # #object.content_type = mime_type
92
+ # #object.store
93
+ #else
94
+ # ext = File.extname(local_path)
95
+ # if content_types.keys.include? ext
96
+ # #object.content_type = content_types[ext]
97
+ # #object.store
98
+ # end
99
+ #end
100
+
101
+ # ... then set the ACLs.
102
+ public_read_grant = AWS::S3::ACL::Grant.grant(:public_read)
103
+ object.acl.grants << public_read_grant
104
+ object.acl(object.acl)
105
+
106
+ @suppressor.ok
107
+
108
+ if @s3_bucket.objects.include?(object)
109
+ $stderr.puts "Successfully uploaded #{local_path} to S3!"
110
+ else
111
+ $stderr.puts "Something went wrong uploading #{local_path} to S3."
112
+ end
113
+ rescue => detail
114
+ $stderr.puts "Uploader#upload_contents_to_s3 exception."
115
+ $stderr.puts detail.message
116
+ end
117
+
118
+ def self.content_types
119
+ {
120
+ ".png" => "image/png",
121
+ ".jpg" => "image/jpeg",
122
+ ".jpeg" => "image/jpeg",
123
+ ".pdf" => "application/pdf",
124
+ ".jar" => "application/java-archive",
125
+ ".txt" => "text/plain",
126
+ ".html" => "text/html",
127
+ ".css" => "text/css",
128
+ ".js" => "text/javascript"
129
+ }
130
+ end
131
+
132
+ end
133
+ Service.register(:s3 => S3Service)
134
+ end
135
+
@@ -0,0 +1,57 @@
1
+ require 'helpers/path'
2
+ require 'helpers/system_checks'
3
+
4
+
5
+ module Uploader
6
+ class UnrecognizedContentType < Exception; end
7
+ class ServiceNotConnected < Exception; end
8
+ class UploadArgumentError < Exception; end
9
+
10
+ class Service
11
+ include Helpers
12
+
13
+ def initialize(config)
14
+ @config = config
15
+ end
16
+
17
+ # Called to connect to remote services like FTP and S3.
18
+ def connect!
19
+ end
20
+
21
+ # Perform the upload operation to this service.
22
+ #
23
+ # @param [Path] remote_path The relative path to upload. This is prefixed by "remote_root_path"
24
+ # set in the uploader Config. You can set remote_root_path to "" if you want complete control
25
+ # over the path.
26
+ # @param [File, String] contents The contents to upload into the remote file
27
+ # destination.
28
+ # @return nothing
29
+ def upload(remote_path, contents)
30
+ end
31
+
32
+ # Closes the service connection, if required.
33
+ def close!
34
+ end
35
+
36
+ def initialized?
37
+ false
38
+ end
39
+
40
+ @@services = {}
41
+
42
+ def self.services
43
+ @@services
44
+ end
45
+
46
+ # Returns whether this service appears in the Service class's registry.
47
+ def self.is_registered?(service)
48
+ @@services.keys.include?(service)
49
+ end
50
+
51
+ # Registers the Service in the registry, so clients of the service can access it by name.
52
+ def self.register(kwds={})
53
+ @@services.update(kwds)
54
+ end
55
+ end
56
+
57
+ end
@@ -0,0 +1,25 @@
1
+ # Haml and AWS put out a lot of junk. Redirect stdout and stderr to /dev/null to suppress these
2
+ # useless warnings, then restore stdout and stderr at the end.
3
+ class Suppressor
4
+ def initialize
5
+ @hole = File.new('/dev/null', 'w')
6
+ @orig_stdout = $stdout
7
+ @orig_stderr = $stderr
8
+ end
9
+
10
+ # Redirect stdout to /dev/null
11
+ def shutup!
12
+ $stdout = @hole
13
+ $stderr = @hole
14
+ end
15
+
16
+ # Restore stdout and stderr
17
+ def ok
18
+ $stdout = @orig_stdout
19
+ $stderr = @orig_stderr
20
+ end
21
+
22
+ def close!
23
+ @hole.close
24
+ end
25
+ end
data/lib/yuzu.rb ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ module Yuzu; end
4
+
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/yuzu')
6
+
@@ -0,0 +1,60 @@
1
+ require 'ostruct'
2
+ require 'optparse'
3
+ require 'version'
4
+
5
+ module Yuzu::Command
6
+ HELP_MESSAGE = %Q{
7
+ This is yuzu, a blog-aware, static-website generator and publisher that converts
8
+ a folder of text files and images into an HTML5 website.
9
+
10
+ Typical Usage
11
+
12
+ yuzu [command] [options] [files]
13
+
14
+ Available Commands
15
+
16
+ help # Show this help page.
17
+ help [command] # Help about the particular command.
18
+ create # Creates a new yuzu project in the current folder.
19
+ preview # Updates files in the preview folder.
20
+ publish # Publishes files to the currently selected remote server.
21
+ stage # Updates files in the staging folder.
22
+ generate # Generate new content like posts and thumbnails.
23
+ }
24
+
25
+ class ArgParser
26
+ def self.parse(args)
27
+ options = OpenStruct.new
28
+ options.output = :quiet # :verbose, :debug
29
+
30
+ option_parser = \
31
+ OptionParser.new do |opts|
32
+ opts.banner = ""
33
+
34
+ opts.separator "Options"
35
+ opts.separator ""
36
+
37
+ opts.on("--verbose", "Run showing verbose output.") do
38
+ options.output = :verbose
39
+ end
40
+
41
+ opts.on_tail("-h", "--help", "Show this help message.") do
42
+ puts HELP_MESSAGE
43
+ puts opts
44
+ exit
45
+ end
46
+
47
+ opts.on_tail("-v", "--version", "Show version.") do
48
+ puts Yuzu::Version::STRING
49
+ exit
50
+ end
51
+ end
52
+
53
+ option_parser.parse!(args)
54
+ options
55
+
56
+ end # End self.parse
57
+ end # End ArgParser
58
+
59
+ end
60
+
@@ -0,0 +1,104 @@
1
+ require 'yaml'
2
+
3
+ require 'commands/base'
4
+ require 'argparse'
5
+
6
+ require 'helpers/object'
7
+ require 'helpers/string'
8
+
9
+ # Load all the commands.
10
+ Dir["#{File.dirname(__FILE__)}/commands/*"].each { |c| require c }
11
+
12
+
13
+ CONFIG_NOT_FOUND_MESSAGE = \
14
+ %Q{Please run this from the root of your project, in the folder containing
15
+ yuzu.yml or config/yuzu.yml. Or use 'create' to make a new project.}
16
+
17
+
18
+ module Yuzu::Command
19
+
20
+ def self.run(command_str, args)
21
+ start = Time.now
22
+
23
+ options = ArgParser.parse(args)
24
+
25
+ if command_str.nil?
26
+ exit
27
+ end
28
+
29
+ command_class, method = parse(command_str.downcase)
30
+ execute(command_class, method, args[1, args.length] || [], options)
31
+
32
+ stop = Time.now
33
+ delta = stop - start
34
+ if options.output == :verbose
35
+ $stderr.puts "Yuzu completed in #{delta} seconds."
36
+ else
37
+ $stderr.puts
38
+ end
39
+ end
40
+
41
+ def self.parse(command_str)
42
+ command, method = command_str.split(':').collect {|part| part.strip}
43
+ method = method.nil? ? :index : method.to_sym
44
+
45
+ return Yuzu::Command.const_get(command.capitalize), method
46
+ rescue
47
+ raise InvalidCommand
48
+ end
49
+
50
+ def self.execute(command_class, method, args, parsed_options)
51
+ if command_class.requires_config?
52
+ config_hash = load_config
53
+
54
+ config = \
55
+ Yuzu::Core::Config.new(
56
+ config_hash,
57
+ command_class.service_override,
58
+ parsed_options
59
+ )
60
+
61
+ command_instance = command_class.new(args, config)
62
+ command_instance.send(method)
63
+
64
+ else
65
+ command_instance = command_class.new(args)
66
+ command_instance.send(method)
67
+ end
68
+ rescue => e
69
+ $stderr.puts e.message
70
+ $stderr.puts e.backtrace
71
+ raise CommandFailed
72
+ end
73
+
74
+ def self.load_config
75
+ config_location = locate_config
76
+
77
+ if config_location.nil?
78
+ $stderr.puts CONFIG_NOT_FOUND_MESSAGE
79
+ Process.exit!(false)
80
+ end
81
+
82
+ YAML.load_file(config_location)
83
+ end
84
+
85
+ def self.locate_config
86
+ possible_config_locations.each do |path|
87
+ if File.exists?(path)
88
+ return path
89
+ end
90
+ end
91
+ return nil
92
+ end
93
+
94
+ def self.possible_config_locations
95
+ [
96
+ File.join(Dir.pwd, "yuzu.yml"),
97
+ File.join(Dir.pwd, "config", "yuzu.yml")
98
+ ]
99
+ end
100
+
101
+ class InvalidCommand < RuntimeError; end
102
+ class CommandFailed < RuntimeError; end
103
+ end
104
+