zine 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 %>