diary 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,37 @@
1
+ require 'optparse'
2
+
3
+ module Diary
4
+ module CLI
5
+ module Parser
6
+ def parse(args)
7
+ options = OpenStruct.new
8
+ options.force = false
9
+
10
+ opts = OptionParser.new do |opts|
11
+ opts.banner = "Usage: diary ACTION [-vf]"
12
+
13
+ opts.separator "\nSpecific options:"
14
+
15
+ opts.on("-f", "--[no-]force", "Force all items to compile") do |f|
16
+ options.force = f
17
+ end
18
+
19
+ opts.separator "\nCommon options:"
20
+
21
+ opts.on_tail("-h", "--help", "Show this message") do
22
+ $stdout.puts opts
23
+ exit
24
+ end
25
+
26
+ opts.on_tail("-v", "--version", "Show version") do
27
+ $stdout.puts "diary #{VERSION}"
28
+ exit
29
+ end
30
+ end
31
+
32
+ opts.parse!(args)
33
+ options
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,26 @@
1
+ module Diary
2
+ module Item
3
+ class Base
4
+ attr_reader :path
5
+
6
+ def initialize(path)
7
+ @path = path
8
+ end
9
+
10
+ def basename
11
+ File.basename(path, '.md')
12
+ end
13
+
14
+ def classname
15
+ self.class.name.downcase
16
+ end
17
+
18
+ def self.base_directory
19
+ self.name.downcase.pluralize
20
+ end
21
+
22
+ extend Creator
23
+ extend Finder
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,80 @@
1
+ # coding: utf-8
2
+ module Diary
3
+ module Item
4
+ module Creator
5
+ def self.extended(base)
6
+ base.send :include, Peristance
7
+ end
8
+
9
+ module Peristance
10
+ def save!(title = nil)
11
+ unless persisted?
12
+ self.class.create(title || basename)
13
+ else
14
+ self
15
+ end
16
+ end
17
+
18
+ def persisted?
19
+ File.exists?(path)
20
+ end
21
+ end
22
+
23
+ ACCENTS = {
24
+ ['á','à','â','ä','ã'] => 'a',
25
+ ['Ã','Ä','Â','À','�?'] => 'A',
26
+ ['é','è','ê','ë'] => 'e',
27
+ ['Ë','É','È','Ê'] => 'E',
28
+ ['í','ì','î','ï'] => 'i',
29
+ ['�?','Î','Ì','�?'] => 'I',
30
+ ['ó','ò','ô','ö','õ'] => 'o',
31
+ ['Õ','Ö','Ô','Ò','Ó'] => 'O',
32
+ ['ú','ù','û','ü'] => 'u',
33
+ ['Ú','Û','Ù','Ü'] => 'U',
34
+ ['ç'] => 'c', ['Ç'] => 'C',
35
+ ['ñ'] => 'n', ['Ñ'] => 'N'
36
+ }
37
+
38
+ def create(title)
39
+ slug = slugify(title)
40
+ path = resolve_path(slug)
41
+
42
+ ensure_directories_exists!(path)
43
+
44
+ unless File.exists?(path)
45
+ File.open(path, "w+") do |f|
46
+ f.puts "---\n"
47
+ f.puts "title: #{title}"
48
+ f.puts "\n---\n"
49
+ end
50
+
51
+ Diary.message :create, path, :green
52
+ else
53
+ Diary.message :exist, path, :yellow
54
+ end
55
+
56
+ self.new(path)
57
+ end
58
+
59
+ private
60
+
61
+ def slugify(str)
62
+ ACCENTS.each do |ac,rep|
63
+ ac.each do |s|
64
+ str = str.gsub(s, rep)
65
+ end
66
+ end
67
+
68
+ str.gsub(/[ ]+/, " ").gsub(/ /, "-").downcase
69
+ end
70
+
71
+ def resolve_path(slug)
72
+ File.join(base_directory, "#{slug}.md")
73
+ end
74
+
75
+ def ensure_directories_exists!(path)
76
+ FileUtils.mkpath File.dirname(path)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,37 @@
1
+ module Diary
2
+ module Item
3
+ module Data
4
+ attr_reader :data
5
+
6
+ def initialize(*args)
7
+ super
8
+ load_data
9
+ end
10
+
11
+ def reload_data
12
+ @data = load_data
13
+ end
14
+
15
+ private
16
+
17
+ def load_data
18
+ if File.exists?(path)
19
+ @data ||= OpenStruct.new(YAML.load_file(path)).freeze
20
+ create_attr_readers_from_data_table!
21
+
22
+ @data
23
+ else
24
+ nil
25
+ end
26
+ end
27
+
28
+ def create_attr_readers_from_data_table!
29
+ data.instance_variable_get(:@table).tap do |h|
30
+ h.each_key do |k|
31
+ self.class.send(:define_method, k) { data.send k } unless self.class.method_defined?(k)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ module Diary
2
+ module Item
3
+ module Finder
4
+ def find(r)
5
+ file_list.each do |p|
6
+ return initialize_item(p) if p.match(r)
7
+ end
8
+
9
+ nil
10
+ end
11
+
12
+ def all
13
+ file_list.map do |p|
14
+ initialize_item(p)
15
+ end
16
+ end
17
+
18
+ def first
19
+ all.first
20
+ end
21
+
22
+ def last
23
+ all.last
24
+ end
25
+
26
+ private
27
+
28
+ def file_list
29
+ Dir[File.join(base_directory, "**", "*.md")]
30
+ end
31
+
32
+ def initialize_item(p)
33
+ self.new(p)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,58 @@
1
+ module Diary
2
+ module Item
3
+ module Output
4
+ def output(force = false)
5
+ if changed? or force
6
+ FileUtils.mkpath output_directory
7
+ File.open(output_path, 'w+') { |f| f.puts render }
8
+ Diary.message (force ? :force : :update), output_path, (force ? :yellow : :green)
9
+
10
+ return true
11
+ else
12
+ Diary.message :identical, output_path, :yellow
13
+
14
+ return false
15
+ end
16
+ end
17
+
18
+ def content
19
+ return @content if @content
20
+
21
+ File.open(path, 'r') do |f|
22
+ @content ||= BlueCloth.new(f.read.split(/---\n/).slice(-1).strip).to_html
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def render
29
+ File.open(template, 'r') do |t|
30
+ ERB.new(t.read).result(binding)
31
+ end
32
+ end
33
+
34
+ def changed?
35
+ file = File.open(path, 'r') if File.exists?(path)
36
+ output_file = File.open(output_path, 'r') if File.exists?(output_path)
37
+
38
+ if file and output_file
39
+ result = file.mtime > output_file.ctime
40
+ else
41
+ result = true
42
+ end
43
+
44
+ [file, output_file].each { |f| f.close if f and f.is_a?(File) }
45
+
46
+ result
47
+ end
48
+
49
+ def output_path(directory = "public")
50
+ path.gsub(self.class.base_directory, directory).gsub('.md', (path.match("index.md") ? '.html' : '/index.html'))
51
+ end
52
+
53
+ def output_directory
54
+ File.dirname(output_path)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,9 @@
1
+ module Diary
2
+ module Item
3
+ module Snippet
4
+ def snippet(name)
5
+ Diary::Snippet.new(name.intern).content
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,38 @@
1
+ module Diary
2
+ module Item
3
+ module Template
4
+ # This method will go down the template lookup procedure and will
5
+ # return the first file path to match an existing file.
6
+ #
7
+ # The lookup order is as follow:
8
+ #
9
+ # 1 The template name supplied in the data section
10
+ # 2 A template with the same basename
11
+ # 3 A template with the name of the item class (Page, Post, etc…)
12
+ # 4 The index template file
13
+ def template
14
+ Diary.message :invoke, template_lookup, :cyan
15
+
16
+ template_lookup
17
+ end
18
+
19
+ private
20
+
21
+ def template_lookup
22
+ template_names.each do |name|
23
+ return template_path(name) if File.exists?(template_path(name))
24
+ end
25
+
26
+ nil
27
+ end
28
+
29
+ def template_names
30
+ [(defined?(Data) ? data.template : nil), basename, classname, 'index'].compact
31
+ end
32
+
33
+ def template_path(name)
34
+ File.join("templates", "#{name}.html.erb")
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ module Diary
2
+ module Item
3
+ module Url
4
+ def link(name)
5
+ "<a href=\"#{output_path.gsub("public/", "")}\">#{name}</a>"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ class Page < Diary::Item::Base
2
+ include Diary::Item::Data
3
+ include Diary::Item::Url
4
+ include Diary::Item::Template
5
+ include Diary::Item::Snippet
6
+ include Diary::Item::Output
7
+ end
@@ -0,0 +1,10 @@
1
+ class Post < Diary::Item::Base
2
+ include Diary::Item::Data
3
+ include Diary::Item::Template
4
+ include Diary::Item::Output
5
+ include Diary::Item::Url
6
+
7
+ def output_path(directory = "public/blog")
8
+ super
9
+ end
10
+ end
@@ -1,23 +1,44 @@
1
1
  module Diary
2
- module Message
3
- # Red
4
- Error = " \e[1;31merror\e[0m"
5
- Skip = " \e[1;31mskip\e[0m"
6
-
7
- # Green
8
- Publish = " \e[1;32mpublish\e[0m"
9
- Create = " \e[1;32mcreate\e[0m"
10
- Update = " \e[1;32mupdate\e[0m"
11
-
12
- # Yellow
13
- Identical = " \e[1;33midentical\e[0m"
14
- Exist = " \e[1;33mexist\e[0m"
15
-
16
- # Cyan
17
- Invoke = " \e[1;36minvoke\e[0m"
18
-
19
- def say(const, message)
20
- puts "#{const} #{message}"
2
+ class Message
3
+ DEFAULT_PADDING = 12
4
+
5
+ # Embed in a String to clear all previous ANSI sequences.
6
+ CLEAR = "\e[0m"
7
+ # Embed in a String to add bold
8
+ BOLD = "\e[1m"
9
+ # Embed in a String to add color
10
+ BLACK = "\e[30m"
11
+ RED = "\e[31m"
12
+ GREEN = "\e[32m"
13
+ YELLOW = "\e[33m"
14
+ BLUE = "\e[34m"
15
+ MAGENTA = "\e[35m"
16
+ CYAN = "\e[36m"
17
+ WHITE = "\e[37m"
18
+
19
+ def initialize(keyword, message, color = false)
20
+ keyword = add_padding(keyword.to_s)
21
+ keyword = set_color(keyword, color) if color
22
+
23
+ $stdout.puts "#{keyword} #{message}"
24
+ end
25
+
26
+ private
27
+
28
+ def add_padding(str, padding = DEFAULT_PADDING)
29
+ spaces = " " * [0, (padding - str.size)].max
30
+ spaces + str
31
+ end
32
+
33
+ def set_color(str, color)
34
+ color = self.class.const_get(color.to_s.upcase)
35
+ color + str + CLEAR
36
+ end
37
+
38
+ module Shorthand
39
+ def message(*args)
40
+ ::Diary::Message.new(*args)
41
+ end
21
42
  end
22
43
  end
23
44
  end
@@ -0,0 +1,5 @@
1
+ require 'sinatra'
2
+
3
+ get '*/' do
4
+ File.read(File.join("public#{request.path_info}", 'index.html'))
5
+ end
@@ -0,0 +1,19 @@
1
+ module Diary
2
+ class Snippet
3
+ attr_reader :path
4
+
5
+ def initialize(name)
6
+ @path = File.join("snippets", "#{name}.html")
7
+ end
8
+
9
+ def content
10
+ unless @content
11
+ File.open(path, 'r') do |file|
12
+ @content ||= file.read
13
+ end
14
+ end
15
+
16
+ @content
17
+ end
18
+ end
19
+ end