zine 0.1.0

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.
@@ -0,0 +1,57 @@
1
+ require 'thor'
2
+ require 'rainbow'
3
+ require 'time'
4
+ require 'yaml'
5
+ require 'zine'
6
+ require 'zine/version'
7
+
8
+ module Zine
9
+ # CLI for zine
10
+ class CLI < Thor
11
+ include Thor::Actions
12
+
13
+ no_commands do
14
+ def init_site
15
+ @site ||= Zine::Site.new
16
+ end
17
+ end
18
+
19
+ desc 'build', 'Build the site'
20
+ def build
21
+ init_site
22
+ @site.build_site
23
+ puts Rainbow('Site built').green
24
+ end
25
+
26
+ desc 'nuke', 'Delete the build folder'
27
+ def nuke
28
+ init_site
29
+ FileUtils.remove_dir @site.options['directories']['build'], force: true
30
+ puts Rainbow('Site nuked. It\'s the only way to be sure.').green
31
+ end
32
+
33
+ desc 'post TITLE', 'Create the file for a new blog post, titled TITLE'
34
+ def post(name)
35
+ init_site
36
+ option_dir = @site.options['directories']
37
+ Zine::CLI.source_root option_dir['templates']
38
+ @date = DateTime.now
39
+ @name = name
40
+ file = "#{@date.strftime('%Y-%m-%d')}-#{Zine::Page.slug(name)}.md"
41
+ template 'new_post.erb',
42
+ File.join(Dir.pwd, option_dir['posts'], file)
43
+ end
44
+
45
+ desc 'site', 'Create the skeleton of a new site (overwriting files)'
46
+ def site
47
+ @skeleton_dir = File.join File.dirname(__FILE__), 'skeleton', '/.'
48
+ FileUtils.cp_r @skeleton_dir, Dir.pwd
49
+ puts Rainbow('New skeleton site created').green
50
+ end
51
+
52
+ desc 'version', 'Show the version number'
53
+ def version
54
+ puts VERSION
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,23 @@
1
+ require 'zine/page'
2
+
3
+ module Zine
4
+ # A page where the content comes from an array, usually an array of
5
+ # links to other pages, eg an index page like the home page
6
+ class DataPage < Zine::Page
7
+ def initialize(data, templates, site_options)
8
+ init_templates(templates)
9
+ @formatted_data = FormattedData.new({}, site_options)
10
+ @formatted_data.page[:title] = data[:title]
11
+ @formatted_data.data = data[:post_array]
12
+ @dest_path = File.join(data[:build_dir],
13
+ Zine::Page.slug(data[:name]) + '.html')
14
+ write
15
+ end
16
+
17
+ def write
18
+ html = template_the_html
19
+ compressor = HtmlCompressor::Compressor.new
20
+ File.write(@dest_path, compressor.compress(html))
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,44 @@
1
+ require 'rss'
2
+ require 'uri'
3
+ # require 'zine/page'
4
+
5
+ module Zine # < Zine::Page
6
+ # produce the RSS/Atom feed for the site
7
+ class Feed
8
+ def initialize(post_array, options)
9
+ @post_array = post_array
10
+ @options = options['options']
11
+ @rss = create_rss
12
+ @dest_path = File.join(options['directories']['build'], 'rss.xml')
13
+ end
14
+
15
+ def create_rss
16
+ RSS::Maker.make('atom') do |maker|
17
+ maker.channel.author = @options['site_author']
18
+ maker.channel.updated = @post_array[0].formatted_data
19
+ .page[:date_rfc3339].to_s
20
+ maker.channel.about = (URI.join @options['site_URL'], 'rss.xml').to_s
21
+ maker.channel.title = @options['site_name']
22
+
23
+ @post_array.each do |post|
24
+ maker.items.new_item do |item|
25
+ data = post.formatted_data
26
+ meta = data.page
27
+ item.link = data.uri
28
+ item.title = meta[:title]
29
+ item.updated = meta[:date_rfc3339].to_s
30
+ # item.content.content = data.html
31
+ # item.content.type = 'html'
32
+ # =><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
33
+ item.content.type = 'xhtml'
34
+ item.content.xml_content = data.html
35
+ end
36
+ end
37
+ end # rss
38
+ end # fn
39
+
40
+ def process
41
+ File.write(@dest_path, @rss)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,118 @@
1
+ require 'erb'
2
+ require 'date'
3
+ require 'htmlcompressor'
4
+ require 'kramdown'
5
+ require 'pathname'
6
+ require 'rainbow'
7
+ require 'yaml'
8
+ require 'zine'
9
+ require 'zine/templates'
10
+
11
+ module Zine
12
+ # A page on the site where the content comes from a file's markdown, and the
13
+ # destination's location mirrors its own
14
+ class Page
15
+ attr_reader :formatted_data
16
+ # the meta data, passed formatted to the template
17
+ class FormattedData
18
+ attr_accessor :data
19
+ attr_accessor :footer_partial
20
+ attr_accessor :header_partial
21
+ attr_accessor :html
22
+ attr_reader :page
23
+ attr_accessor :uri
24
+
25
+ def initialize(front_matter, site_opt)
26
+ @page = { date_rfc3339: front_matter['date'],
27
+ date_us: parse_date(front_matter['date']),
28
+ github_name: site_opt['options']['github_name'],
29
+ num_items_on_home: site_opt['options']['num_items_on_home'],
30
+ site_author: site_opt['options']['site_author'],
31
+ site_description: site_opt['options']['site_description'],
32
+ site_name: site_opt['options']['site_name'],
33
+ site_URL: site_opt['options']['site_URL'],
34
+ tags: slugify_tags(front_matter['tags']),
35
+ title: front_matter['title'],
36
+ twitter_name: site_opt['options']['twitter_name'] }
37
+ end
38
+
39
+ def parse_date(_d)
40
+ DateTime.rfc3339(front_matter['date']).strftime('%B %-d, %Y')
41
+ rescue
42
+ ''
43
+ end
44
+
45
+ def public_binding
46
+ binding
47
+ end
48
+
49
+ def slugify_tags(tags)
50
+ return unless tags && tags.any?
51
+ tags.map { |tag| { name: tag, tag_slug: Page.slug(tag) } }
52
+ end
53
+ end
54
+
55
+ # the Tags on a Post
56
+ TagData = Struct.new(:tagsArray, :destURL, :pageTitle, :pageDate,
57
+ :pageDateUS)
58
+
59
+ def initialize(md_file_name, dest, templates, site_options)
60
+ file_parts = File.open(md_file_name, 'r').read.split('---')
61
+ @formatted_data = FormattedData.new(parse_yaml(file_parts[1]),
62
+ site_options)
63
+ @dest_path = dest
64
+ @raw_text = file_parts[2]
65
+ init_templates(templates)
66
+ end
67
+
68
+ def init_templates(templates)
69
+ @header_partial = templates.header
70
+ @footer_partial = templates.footer
71
+ @template = templates.body
72
+ end
73
+
74
+ def parse_markdown
75
+ @formatted_data.html = Kramdown::Document.new(
76
+ @raw_text,
77
+ input: 'GFM',
78
+ auto_ids: false,
79
+ smart_quotes: %w(apos apos quot quot),
80
+ syntax_highlighter: 'rouge'
81
+ ).to_html
82
+ end
83
+
84
+ def parse_yaml(text)
85
+ YAML.safe_load text
86
+ rescue Psych::Exception
87
+ puts Rainbow("Could not parse front matter for: #{md_file_name}").red
88
+ { 'date' => DateTime.now.to_s, 'title' => md_file_name, 'tags' => [] }
89
+ end
90
+
91
+ def rel_path_from_build_dir(path)
92
+ full = Pathname(path)
93
+ full.relative_path_from(Pathname(@build_dir))
94
+ end
95
+
96
+ def process
97
+ parse_markdown
98
+ html = template_the_html
99
+
100
+ compressor = HtmlCompressor::Compressor.new
101
+ File.write(@dest_path, compressor.compress(html))
102
+ end
103
+
104
+ def self.slug(text)
105
+ text.downcase
106
+ .gsub(/[^a-z0-9]+/, '-')
107
+ .gsub(/^-|-$/, '')
108
+ end
109
+
110
+ def template_the_html
111
+ @formatted_data.header_partial = @header_partial.result(@formatted_data
112
+ .public_binding)
113
+ @formatted_data.footer_partial = @footer_partial.result(@formatted_data
114
+ .public_binding)
115
+ @template.result(@formatted_data.public_binding)
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,40 @@
1
+ require 'uri'
2
+
3
+ module Zine
4
+ # A post - content comes from the markdown, and the destination from the date
5
+ class Post < Page
6
+ def initialize(md_file_name, templates, site_options)
7
+ file_parts = File.open(md_file_name, 'r').read.split('---')
8
+ @formatted_data = FormattedData.new(parse_yaml(file_parts[1]),
9
+ site_options)
10
+ @raw_text = file_parts[2]
11
+ init_templates(templates)
12
+ option_dir = site_options['directories']
13
+ @build_dir = option_dir['build'] # for tags
14
+ @dest_path = make_path_from_date option_dir['blog']
15
+ end
16
+
17
+ def make_path_from_date(build_dir)
18
+ page_data = @formatted_data.page
19
+ date = DateTime.parse(page_data[:date_rfc3339])
20
+ dest_dir = File.join(build_dir,
21
+ date.strftime('%Y'),
22
+ date.strftime('%-m'))
23
+ FileUtils.mkdir_p dest_dir
24
+ slg = Zine::Page.slug(page_data[:title]) + '.html'
25
+ @dest_path = File.join(dest_dir, slg)
26
+ end
27
+
28
+ def process
29
+ super
30
+ page_data = @formatted_data.page
31
+ file_path = rel_path_from_build_dir(@dest_path).to_s
32
+ @formatted_data.uri = URI.join(page_data[:site_URL], file_path).to_s
33
+ TagData.new(page_data[:tags],
34
+ file_path,
35
+ page_data[:title],
36
+ page_data[:date_rfc3339],
37
+ page_data[:date_us])
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ require 'rainbow'
2
+ require 'rack'
3
+ require 'thin'
4
+
5
+ module Zine
6
+ # Local preview web server
7
+ class Server
8
+ def initialize(root)
9
+ motd
10
+ Thin::Server.start('127.0.0.1', 8080) do
11
+ use Rack::Static,
12
+ urls: ['/'],
13
+ index: 'index.html',
14
+ root: root
15
+
16
+ now = Time.now
17
+ a_long_time = 100**4
18
+ run lambda { |_env|
19
+ [200,
20
+ {
21
+ 'Content-Type' => 'text/html',
22
+ 'ETag' => nil,
23
+ 'Last-Modified' => now + a_long_time,
24
+ 'Cache-Control' =>
25
+ 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0',
26
+ 'Pragma' => 'no-cache',
27
+ 'Expires' => now - a_long_time
28
+ },
29
+ File.open(File.join(root, 'index.html'), File::RDONLY)]
30
+ }
31
+ end
32
+ end
33
+
34
+ def motd
35
+ puts "\nPreview running on " +
36
+ Rainbow('http://127.0.0.1:8080/').blue.underline +
37
+ "\nCommand double click the URL to open, Control C to quit\n"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,5 @@
1
+ ---
2
+ title: About
3
+ ---
4
+
5
+ About me and my blog.
@@ -0,0 +1,11 @@
1
+ ---
2
+ title: My new blog
3
+ date: '2017-01-25T20:21:06+11:00'
4
+ tags: [ruby, blog]
5
+ ---
6
+
7
+ Greetings from my new blog.
8
+
9
+ <!--more-->
10
+
11
+ I have so much to say, I can't wait to start.
@@ -0,0 +1 @@
1
+ *{margin:0;padding:0}html,body{height:100%}body{background-color:#fff;font-family:GillSansRegular,'Gill Sans MT','Gill Sans','Century Gothic',Calibri,'Trebuchet MS',sans-serif;line-height:1.618;color:#333;text-align:center;font-weight:300}h1,h2,h3,h4,h5,h6{color:#333;letter-spacing:.1em}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{color:#414f7c;text-decoration:none}h1 a:hover,h2 a:hover,h3 a:hover,h4 a:hover,h5 a:hover,h6 a:hover{color:#212721}h1{font-weight:300;font-size:2.618em;margin:1.809em 0 .809em}h2{font-weight:300;font-size:1.618em;margin:1.809em 0 .809em}h3,h4,h5,h6{font-weight:400;font-size:1em;margin:1.809em 0 .809em}p{margin:1em 0}a{color:#414f7c}a:hover{color:#212721}section{margin-bottom:1.618em}section>section{margin-bottom:3.236em}body>header{width:43em;text-align:center;margin:0 auto 0}body>header a{text-decoration:none;margin-left:.5em;margin-right:.5em}body>header,body>main,body>footer{display:block}body>header a{color:#414f7c}body>header a:hover{color:#212721}body>header a.extra{color:#414f7c;margin-left:1em}body>header a.extra:hover{color:#212721}body>header nav ul li{display:inline;list-style:none}.button{width:30px;height:30px;display:inline-block;background-size:100%;text-indent:-999em;text-align:left;margin:20px}.twitter{background:url('/assets/webicon-twitter-m.png');background-image:url('/assets/webicon-twitter.svg'),none}.rss{background:url('/assets/webicon-rss-m.png');background-image:url('/assets/webicon-rss.svg'),none}main{text-align:left;width:43em;margin:3em auto 2em}main li{margin-left:2.618em}.meta{color:#667}footer{width:43em;color:#667;border-top:4px solid #ddd;margin:3em auto 2em;overflow:hidden}footer .contact{float:left;margin-right:3em}footer .contact a,.tags a{color:#414f7c;text-decoration:none}footer .contact a:hover,.tags a:hover{color:#212721;text-decoration:none}.tags ul li{list-style:none;display:inline;font-variant:small-caps;font-size:1.2em}.archive a{text-decoration:none}ul.archive,ul.archive ul{margin-left:0}ul.archive li,ul.archive ul li{list-style:none;margin-left:0}.post pre{border:1px solid #ddd;background-color:#fff;padding:0 .4em}p.date{color:#667}pre{background-color:#eee;padding:1em;white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}code{font-family:Consolas,Menlo,Monaco,'Lucida Console','Courier New',monospace,serif;font-size:.8em}blockquote{margin:2em 2em 2em 1em;padding:0 .75em 0 1.25em;border-left:2px solid #ddd;border-right:0 solid #ddd}@media all and (max-width:736px){body>header,main,footer{width:86%;margin:0 auto 0;padding:12px 24px 12px}p{margin-bottom:2em}.button{width:50px;height:50px;margin:20px}}
@@ -0,0 +1,8 @@
1
+ <%= header_partial %>
2
+ <main>
3
+ <section>
4
+ <h2><%= page[:title] %></h2>
5
+ <%= html %>
6
+ </section>
7
+ </main>
8
+ <%= footer_partial %>
@@ -0,0 +1,11 @@
1
+ <footer>
2
+ <div class="contact">
3
+ <p>Twitter:&nbsp;<a href="https://twitter.com/<%= page[:twitter_name] %>">@<%= page[:twitter_name] %></a></p>
4
+ </div>
5
+ <div class="contact">
6
+ <p>&copy; 2017<span> <%= page[:site_author] %></span>
7
+ </p>
8
+ </div>
9
+ </footer>
10
+ </body>
11
+ </html>
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,user-scalable=yes">
6
+ <meta name="description" content="<%= page[:site_description] %>">
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] %>">
9
+ <link rel="stylesheet" href="/screen.css">
10
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png">
11
+ </head>
12
+ <body>
13
+ <header>
14
+ <div class="logo"></div>
15
+ <h1><a href="<%= page[:site_URL] %>"><%= page[:site_name] %></a></h1>
16
+ <nav>
17
+ <ul>
18
+ <li><a href="<%= page[:site_URL] %>">Home</a></li>
19
+ <li><a href="/articles.html">Articles</a></li>
20
+ <li><a href="/about.html">About</a></li>
21
+ </ul><a href="https://twitter.com/<%= page[:twitter_name] %>" class="button twitter">Argue with me on Twitter</a><a href="<%= page[:site_URL] %>/rss.xml" rel="home" type="application/rss+xml" class="button rss">RSS Feed</a>
22
+ </nav>
23
+ </header>
@@ -0,0 +1,18 @@
1
+ <%= header_partial %>
2
+ <main>
3
+ <section>
4
+ <% for @post in data %>
5
+ <h2><%= @post[:page][:title] %></h2>
6
+ <p class="date"><%= @post[:page][:date_us] %></p>
7
+ <%= @post[:html] %>
8
+ <div class="tags">Tags:
9
+ <ul>
10
+ <% for @tag in @post[:page][:tags] %>
11
+ <li><a href="/tags/<%= @tag[:tag_slug] %>.html"><%= @tag[:name] %></a></li>
12
+ <% end %>
13
+ </ul>
14
+ </div>
15
+ <% end %>
16
+ </section>
17
+ </main>
18
+ <%= footer_partial %>
@@ -0,0 +1,11 @@
1
+ ---
2
+ title: <%= @name %>
3
+ date: '<%= @date %>'
4
+ tags: [ripley, elixir]
5
+ ---
6
+
7
+ Lede
8
+
9
+ <!--more-->
10
+
11
+ Body
@@ -0,0 +1,16 @@
1
+ <%= header_partial %>
2
+ <main>
3
+ <section>
4
+ <h2><%= page[:title] %></h2>
5
+ <p class="date"><%= page[:date_us] %></p>
6
+ <%= html %>
7
+ <div class="tags">Tags:
8
+ <ul>
9
+ <% for @tag in page[:tags] %>
10
+ <li><a href="/tags/<%= @tag[:tag_slug] %>.html"><%= @tag[:name] %></a></li>
11
+ <% end %>
12
+ </ul>
13
+ </div>
14
+ </section>
15
+ </main>
16
+ <%= footer_partial %>