zine 0.13.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -2,38 +2,34 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/zine.svg)](https://badge.fury.io/rb/zine)
4
4
 
5
- Yet another blog aware static site generator.
6
-
7
- ## Why yet another static blog engine?
8
-
9
- Despite the [proliferation in these things][engine_list] (!) I still find it more comfortable to use my own tools.
5
+ Zine is an open source, command line, blog-aware, static website generator.
10
6
 
11
7
  Distinguishing features include:
12
8
 
13
9
  - ERB templates
14
10
  - Sass stylesheets
15
11
  - fast incremental builds
16
- - GitHub & SFTP file uploads
12
+ - a choice of AWS S3, GitHub & SFTP file uploaders
17
13
 
18
- Presented here in the hope it's of use to someone else too.
14
+ ## How do I get it?
19
15
 
20
- ## Installation
16
+ Zine is a Ruby Gem, so if you have Ruby on your machine (it comes installed standard on a Mac), open Terminal & type
21
17
 
22
- Install the gem.
18
+ ````bash
19
+ gem install zine
20
+ ````
23
21
 
24
- ```shell
25
- $ gem install zine
26
- ```
22
+ And you're away.
27
23
 
28
- To generate a new site scaffold, cd to a new folder and:
24
+ To generate a new scaffold site, cd to a new directory and:
29
25
 
30
26
  ```shell
31
27
  $ zine site
32
28
  ```
33
29
 
34
- Then update your site's name, your name & so on in zine.yaml. Pay particular care to the Upload section, if you want to use Zine as an SFTP uploader to deploy files that've changed, you'll need to edit this section to include your remote server's details, as well as the path to a YAML file with your username & password (nil for that if you're using SSH without a password).
30
+ Then update your site's name, your name & so on in zine.yaml. Pay particular care to the Upload section, if you want to use Zine to deploy files you've changed, you'll need to edit this section to include your remote server's details, including the path to a YAML file with your credentials.
35
31
 
36
- ## Day to day usage
32
+ ## Day to day use
37
33
 
38
34
  To set up a new blog post:
39
35
 
@@ -41,15 +37,25 @@ To set up a new blog post:
41
37
  $ zine post 'Your chosen title'
42
38
  ```
43
39
 
44
- Your new post will have some fields set up in the YAML front matter, feel free to edit them too. Markdown files you create outside of the posts folder will be rendered into HTML in the same relative position in the build folder.
40
+ Your new post will have some fields set up in the YAML front matter, feel free to edit them too.
41
+
42
+ You can also create other Markdown files outside of the posts folder, those will be rendered into HTML in the same relative position in the build folder. That's how the project, about etc pages on my site are made for example.
45
43
 
46
- Once you're done writing, build your new site:
44
+ Type zine build before you start writing to serve up a local copy of your site that you can refresh to see what the build version will look like.
47
45
 
48
46
  ```shell
49
- $ zine build # or zine force
47
+ $ zine build
50
48
  ```
51
49
 
52
- Build only writes files for things that have changed while it's running, so the first time you build your site you should use force -- force writes all of the files (& so also uploads them all too if you've set up uploads).
50
+ or
51
+
52
+ ```shell
53
+ $ zine force
54
+ ```
55
+
56
+ Build will only watch for the things that change while it's running, so the first time you build your site you should use force -- force writes all of the files (& so also uploads them all too if you've set up uploads).
57
+
58
+ Control-C in Terminal when you're done.
53
59
 
54
60
  ## Design & development
55
61
 
@@ -73,11 +79,11 @@ Commands:
73
79
  zine version # Show the version number
74
80
  ```
75
81
 
76
- ### Up next
77
-
78
- Many versions on this is only an early cut of this gem, the stuff I considered a (barely) minimum viable product. More to come...
82
+ ## Links
79
83
 
80
- A brief TODO list at the end of the change log.
84
+ - [Github][github] - show me the code
85
+ - [Ruby gems][rubygems] - show me the Ruby details (pick up some gems while you're there)
86
+ - [Project site][mk] - Zine's home on the web
81
87
 
82
88
  ## Contributing
83
89
 
@@ -93,4 +99,6 @@ rake
93
99
 
94
100
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
95
101
 
96
- [engine_list]: https://staticsitegenerators.net
102
+ [github]: https://github.com/mikekreuzer/zine
103
+ [mk]: https://mikekreuzer/projects/zine/
104
+ [rubygems]: https://rubygems.org/gems/zine
data/lib/zine/cli.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
  require 'rainbow'
3
5
  require 'time'
@@ -13,9 +15,24 @@ module Zine
13
15
  attr_accessor :the_site # only used in testing
14
16
 
15
17
  no_commands do
18
+ def self.exit_on_failure?
19
+ true
20
+ end
21
+
16
22
  def init_site
17
23
  @the_site ||= Zine::Site.new
18
24
  end
25
+
26
+ def options
27
+ init_site
28
+
29
+ # for v0.16.0 only
30
+ unless @the_site.options['templates']['atom']
31
+ puts "v0.16.0 changes how rss & atom works, see CHANGELOG.md if you haven't already\n\n"
32
+ end
33
+
34
+ @the_site.options
35
+ end
19
36
  end
20
37
 
21
38
  desc 'build', 'Build the site'
@@ -47,7 +64,7 @@ module Zine
47
64
  desc 'nuke', 'Delete the build folder'
48
65
  def nuke
49
66
  init_site
50
- FileUtils.remove_dir @the_site.options['directories']['build'],
67
+ FileUtils.remove_dir options['directories']['build'],
51
68
  force: true
52
69
  puts Rainbow('Site nuked. It\'s the only way to be sure.').green
53
70
  end
@@ -55,7 +72,6 @@ module Zine
55
72
  desc 'post TITLE', 'Create the file for a new blog post, titled TITLE'
56
73
  def post(name)
57
74
  init_site
58
- options = @the_site.options
59
75
  option_dir = options['directories']
60
76
  Zine::CLI.source_root option_dir['templates']
61
77
  @date = DateTime.now
@@ -77,7 +93,7 @@ module Zine
77
93
  desc 'style', 'Build the site\'s stylesheet'
78
94
  def style
79
95
  init_site
80
- style = Zine::Style.new(@the_site.options['directories'])
96
+ style = Zine::Style.new(options['directories'])
81
97
  style.process(File)
82
98
  puts Rainbow('Stylesheet rendered').green
83
99
  end
@@ -5,6 +5,7 @@ module Zine
5
5
  # links to other pages, eg an index page like the home page
6
6
  class DataPage < Zine::Page
7
7
  def initialize(data, templates, site_options, suffix = '.html')
8
+ # super(front_matter, site_opt)
8
9
  init_templates(templates)
9
10
  @formatted_data = FormattedData.new({}, site_options)
10
11
  @formatted_data.page[:title] = data[:title]
data/lib/zine/page.rb CHANGED
@@ -30,6 +30,7 @@ module Zine
30
30
  def initialize(front_matter, site_opt)
31
31
  site = site_opt['options']
32
32
  @page = { date_rfc3339: front_matter['date'],
33
+ date_rfc822: parse_822_date(front_matter['date']),
33
34
  date_us: parse_date(front_matter['date']),
34
35
  github_name: site['github_name'],
35
36
  links_array: site_opt['links'],
@@ -56,6 +57,12 @@ module Zine
56
57
  ''
57
58
  end
58
59
 
60
+ def parse_822_date(front_matter_date)
61
+ DateTime.rfc3339(front_matter_date).rfc2822
62
+ rescue ArgumentError
63
+ ''
64
+ end
65
+
59
66
  def slugify_tags(tags)
60
67
  return unless tags && tags.any?
61
68
  tags.map { |tag| { name: tag, tag_slug: Page.slug(tag) } }
@@ -32,15 +32,54 @@ module Zine
32
32
  dir = @options['directories']['build']
33
33
  options = @options['options']
34
34
  templates = @options['templates']
35
- [{ build_dir: dir, name: templates['articles'], number: @post_array.size,
36
- suffix: '.html', template_name: templates['articles'],
37
- title: 'Articles' },
38
- { build_dir: dir, name: 'index',
39
- number: options['num_items_on_home'], suffix: '.html',
40
- template_name: templates['home'], title: 'Home' },
41
- { build_dir: dir, name: 'rss',
42
- number: options['number_items_in_RSS'], suffix: '.xml',
43
- template_name: templates['rss'], title: '' }]
35
+
36
+ headlines = []
37
+ if templates['home']
38
+ headlines <<
39
+ {
40
+ build_dir: dir,
41
+ name: 'index',
42
+ number: options['num_items_on_home'],
43
+ suffix: '.html',
44
+ template_name: templates['home'],
45
+ title: 'Home'
46
+ }
47
+ end
48
+ if templates['articles']
49
+ headlines <<
50
+ {
51
+ build_dir: dir,
52
+ name: templates['articles'],
53
+ number: @post_array.size,
54
+ suffix: '.html',
55
+ template_name: templates['articles'],
56
+ title: 'Articles'
57
+ }
58
+ end
59
+ if templates['rss']
60
+ headlines <<
61
+ {
62
+ build_dir: dir,
63
+ name: 'rss',
64
+ number: options['number_items_in_RSS'],
65
+ suffix: '.xml',
66
+ template_name: templates['rss'],
67
+ title: ''
68
+ }
69
+ end
70
+ if templates['atom']
71
+ headlines <<
72
+ {
73
+ build_dir: dir,
74
+ name: 'atom',
75
+ number: options['number_items_in_RSS'],
76
+ suffix: '.xml',
77
+ template_name: templates['atom'],
78
+ title: ''
79
+ }
80
+ end
81
+
82
+ headlines
44
83
  end
45
84
 
46
85
  def once_only(source_file)
@@ -114,7 +153,7 @@ module Zine
114
153
  def preview_straight_delete(file)
115
154
  FileUtils.rm(File.join(
116
155
  preview_relative_equivalent(file), File.basename(file)
117
- ))
156
+ ))
118
157
  end
119
158
 
120
159
  # rebuild a page that's not a post - doesn't create the file structure for a
@@ -192,7 +231,7 @@ module Zine
192
231
  @post_array.first(page[:number]).each do |post|
193
232
  post_data = post.formatted_data
194
233
  data[:post_array] << { page: post_data.page, html: post_data.html,
195
- uri: post_data.uri }
234
+ uri: post_data.uri }
196
235
  end
197
236
  data_page = DataPage.new(data,
198
237
  @site.make_template_bundle(data[:template_name]),
data/lib/zine/query.rb CHANGED
@@ -3,7 +3,8 @@ module Zine
3
3
  class Query
4
4
  def call(question)
5
5
  puts question
6
- $stdin.gets.chomp
6
+ result = $stdin.gets.chomp
7
+ result == '' ? 'Y' : result
7
8
  end
8
9
  end
9
- end
10
+ end
@@ -0,0 +1,22 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <feed xmlns="http://www.w3.org/2005/Atom"
3
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
4
+ <author>
5
+ <name><%= page[:site_author] %></name>
6
+ </author>
7
+ <id><%= page[:site_URL] %>/atom.xml</id>
8
+ <title><%= page[:site_name] %></title>
9
+ <dc:date><%= data[0][:page][:date_rfc3339] %></dc:date>
10
+ <updated><%= data[0][:page][:date_rfc3339] %></updated>
11
+ <link href="<%= page[:site_URL] %>/atom.xml" rel="self" type="application/atom+xml"/>
12
+ <% for @post in data %>
13
+ <entry>
14
+ <content type="html"><![CDATA[<%= @post[:html] %>]]></content>
15
+ <id><%= @post[:uri] %></id>
16
+ <link href="<%= @post[:uri] %>"/>
17
+ <title><%= @post[:page][:title] %></title>
18
+ <updated><%= @post[:page][:date_rfc3339] %></updated>
19
+ <dc:date><%= @post[:page][:date_rfc3339] %></dc:date>
20
+ </entry>
21
+ <% end %>
22
+ </feed>
@@ -17,8 +17,8 @@
17
17
  <div class="column">
18
18
  <h3>The fine print</h3>
19
19
  <ul>
20
- <li>Built with <a href="https://github.com/mikekreuzer/zine">Zine</a></li>
21
- <li>&copy; 2019<span> <%= page[:site_author] %></span></li>
20
+ <li>Built with <a href="https://mikekreuzer.com/projects/zine/">Zine</a></li>
21
+ <li>&copy;2017-2022<span> <%= page[:site_author] %></span></li>
22
22
  </ul>
23
23
  </div>
24
24
  </footer>
@@ -5,7 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width,user-scalable=yes">
6
6
  <meta name="description" content="<%= page[:site_description] %>">
7
7
  <title><%= page[:site_name] %> | <%= page[:title] %></title>
8
- <link rel="home" href="<%= page[:site_URL] %>/rss.xml" type="application/rss+xml" title="<%= page[:site_name] %>">
8
+ <link rel="home alternate" href="<%= page[:site_URL] %>/atom.xml" type="application/atom+xml" title="<%= page[:site_name] %>">
9
+ <link rel="home alternate" href="<%= page[:site_URL] %>/rss.xml" type="application/rss+xml" title="<%= page[:site_name] %>">
9
10
  <link rel="stylesheet" href="/screen.css">
10
11
  <link rel="apple-touch-icon" href="/apple-touch-icon.png">
11
12
  <!--<link rel="canonical" href="<%= page[:uri] %>" />-->
@@ -1,21 +1,19 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <feed xmlns="http://www.w3.org/2005/Atom"
3
- xmlns:dc="http://purl.org/dc/elements/1.1/">
4
- <author>
5
- <name><%= page[:site_author] %></name>
6
- </author>
7
- <id><%= page[:site_URL] %>/rss.xml</id>
8
- <title><%= page[:site_name] %></title>
9
- <dc:date><%= data[0][:page][:date_rfc3339] %></dc:date>
10
- <updated><%= data[0][:page][:date_rfc3339] %></updated>
11
- <% for @post in data %>
12
- <entry>
13
- <content type="html"><![CDATA[<%= @post[:html] %>]]></content>
14
- <id><%= @post[:uri] %></id>
15
- <link href="<%= @post[:uri] %>"/>
1
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
2
+ <channel>
3
+ <title><%= page[:site_name] %></title>
4
+ <link><%= page[:site_URL] %>/rss.xml</link>
5
+ <description><%= page[:site_description] %></description>
6
+ <pubDate><%= data[0][:page][:date_rfc822] %></pubDate>
7
+ <lastBuildDate><%= data[0][:page][:date_rfc822] %></lastBuildDate>
8
+ <atom:link href="<%= page[:site_URL] %>/rss.xml" rel="self" type="application/rss+xml" />
9
+ <% for @post in data %>
10
+ <item>
16
11
  <title><%= @post[:page][:title] %></title>
17
- <updated><%= @post[:page][:date_rfc3339] %></updated>
18
- <dc:date><%= @post[:page][:date_rfc3339] %></dc:date>
19
- </entry>
20
- <% end %>
21
- </feed>
12
+ <link><%= @post[:uri] %></link>
13
+ <description><![CDATA[<%= @post[:html] %>]]></description>
14
+ <pubDate><%= @post[:page][:date_rfc822] %></pubDate>
15
+ <guid><%= @post[:uri] %></guid>
16
+ </item>
17
+ <% end %>
18
+ </channel>
19
+ </rss>
@@ -25,6 +25,7 @@ options:
25
25
  twitter_name: mikekreuzer
26
26
  templates:
27
27
  articles: articles
28
+ atom: atom
28
29
  default: default
29
30
  home: home
30
31
  new_post: new_post.erb
@@ -33,8 +34,9 @@ templates:
33
34
  tag: tag
34
35
  tag_index: tag_index
35
36
  upload:
37
+ cloudfront_distrib: ID or ''
36
38
  credentials: /local/absolute/path/to/yaml/file/with/username/password/or/access_token
37
- host: 127.0.0.1
38
- method: none|github|sftp
39
+ host: eg 127.0.0.1, or for AWS region:bucket_name
40
+ method: none|aws|github|sftp
39
41
  path_or_repo: /remote/absolute/path/to/html|fullname/repo
40
42
  verbose: true
data/lib/zine/upload.rb CHANGED
@@ -1,11 +1,12 @@
1
1
  require 'rainbow'
2
2
  require 'set'
3
3
  require 'zine'
4
+ require 'zine/uploader_aws'
4
5
  require 'zine/uploader_github'
5
6
  require 'zine/uploader_sftp'
6
7
 
7
8
  module Zine
8
- # Deploy changes to a remote host, via SFTP or using the GitHub Rest API
9
+ # Deploy changes to a remote host, via SFTP or using the AWS or GitHub API
9
10
  class Upload
10
11
  def initialize(build_dir, options, delete_file_array, upload_file_array)
11
12
  if options['method'] == 'none'
@@ -45,6 +46,15 @@ module Zine
45
46
  exit
46
47
  end
47
48
 
49
+ def aws_upload
50
+ uploader = Zine::UploaderAWS.new(@build_dir,
51
+ @options,
52
+ @credentials,
53
+ @delete_file_array,
54
+ @upload_file_array)
55
+ uploader.upload
56
+ end
57
+
48
58
  def github_upload
49
59
  uploader = Zine::UploaderGitHub.new(@build_dir,
50
60
  @options,
@@ -64,7 +74,9 @@ module Zine
64
74
  end
65
75
 
66
76
  def upload
67
- if @options['method'] == 'sftp'
77
+ if @options['method'] == 'aws'
78
+ aws_upload
79
+ elsif @options['method'] == 'sftp'
68
80
  sftp_upload
69
81
  elsif @options['method'] == 'github'
70
82
  github_upload
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk-cloudfront'
4
+ require 'aws-sdk-s3'
5
+ require 'rack/mime'
6
+ require 'rainbow'
7
+ require 'set'
8
+
9
+ module Zine
10
+ # Deploy changes to an AWS S3 bucket and invalidate the Cloudfront cache
11
+ class UploaderAWS
12
+ def initialize(build_dir, options, credentials, delete_file_array,
13
+ upload_file_array)
14
+ return unless options['method'] == 'aws'
15
+
16
+ @build_dir = build_dir
17
+ ENV['AWS_REGION'], @bucket_name = options['host'].split(':')
18
+ @path = options['path_or_repo']
19
+ @cf_distribution_id = options['cloudfront_distrib']
20
+
21
+ @verbose = options['verbose']
22
+ store_credentials credentials
23
+ @delete_file_array = Set.new(delete_file_array).to_a
24
+ @upload_file_array = Set.new(upload_file_array).to_a
25
+
26
+ @s3 = Aws::S3::Client.new
27
+ @cf = Aws::CloudFront::Client.new
28
+ end
29
+
30
+ def upload
31
+ delete
32
+ deploy
33
+ invalidate
34
+ rescue Aws::S3::Errors::ServiceError => err
35
+ puts Rainbow("S3 error: #{err}").red
36
+ end
37
+
38
+ private
39
+
40
+ def clean_path(path)
41
+ path.to_s
42
+ .delete_prefix('/')
43
+ .delete_suffix('/')
44
+ end
45
+
46
+ def delete
47
+ @delete_file_array.each do |rel_path|
48
+ remote_path = clean_path(rel_path)
49
+ @s3.delete_object(bucket: @bucket_name, key: remote_path)
50
+ puts "Delete: #{remote_path}" if @verbose
51
+ end
52
+ end
53
+
54
+ def deploy
55
+ @upload_file_array.each do |rel_path|
56
+ deploy_one_file(File.join(@build_dir, rel_path),
57
+ clean_path(rel_path))
58
+ end
59
+ end
60
+
61
+ def deploy_one_file(local_path, remote_path)
62
+ File.open(local_path, 'rb') do |file|
63
+ content_type = Rack::Mime.mime_type(File.extname(local_path))
64
+ @s3.put_object(bucket: @bucket_name,
65
+ key: remote_path,
66
+ content_type: content_type,
67
+ body: file)
68
+ puts "Add: #{remote_path}" if @verbose
69
+ end
70
+ end
71
+
72
+ def invalidate
73
+ changes = Set.new(@upload_file_array + @delete_file_array).to_a
74
+ return unless changes.count.positive?
75
+
76
+ puts 'Invalidating cache' if @verbose
77
+ string_changes = changes.map do |path|
78
+ path.to_s
79
+ .delete_prefix('/')
80
+ .prepend('/')
81
+ end
82
+ string_changes << '/'
83
+ @cf.create_invalidation(
84
+ distribution_id: @cf_distribution_id,
85
+ invalidation_batch: { paths: { quantity: string_changes.count,
86
+ items: string_changes },
87
+ caller_reference: Time.now.to_i.to_s }
88
+ )
89
+ end
90
+
91
+ def store_credentials(creds)
92
+ ENV['AWS_ACCESS_KEY_ID'] = creds['username']
93
+ ENV['AWS_SECRET_ACCESS_KEY'] = creds['password']
94
+ end
95
+ end
96
+ end
data/lib/zine/version.rb CHANGED
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Zine
2
4
  # The version
3
- VERSION = '0.13.0'.freeze
5
+ VERSION = '0.16.0'
4
6
  end
data/lib/zine.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erb'
2
4
  require 'rainbow'
3
5
  require 'zine/page'
@@ -102,10 +104,11 @@ module Zine
102
104
  # preview posts_and_guard -- no preview needed...
103
105
  return if @options['upload']['method'] == 'none' ||
104
106
  (guard.delete_array.empty? && guard.upload_array.empty?)
105
- uploader = Zine::Upload.new @options['directories']['build'],
107
+
108
+ uploader = Zine::Upload.new(@options['directories']['build'],
106
109
  @options['upload'],
107
110
  guard.delete_array,
108
- guard.upload_array
111
+ guard.upload_array)
109
112
  uploader.upload_decision MockYes
110
113
  end
111
114
 
@@ -158,18 +161,18 @@ module Zine
158
161
  end
159
162
 
160
163
  def init_options
161
- @options ||= begin
162
- YAML.safe_load File.open('zine.yaml')
163
- rescue ArgumentError => err
164
- puts Rainbow("Could not parse YAML options: #{err.message}").red
165
- end
164
+ @options ||= YAML.safe_load File.open('zine.yaml')
165
+ rescue ArgumentError => e
166
+ puts Rainbow("Could not parse YAML options: #{e.message}").red
166
167
  end
167
168
 
168
169
  def init_templates
170
+ Encoding.default_external = 'UTF-8'
169
171
  tem_array = Dir[File.join(@options['directories']['templates'], '*.erb')]
170
172
  tem_array.each do |tem|
171
173
  @templates_by_name.merge!(File.basename(tem, '.*') =>
172
- ERB.new(File.read(tem), 0, '-'))
174
+ ERB.new(File.read(tem),
175
+ trim_mode: '-'))
173
176
  end
174
177
  end
175
178