diary-ruby 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +2 -0
- data/README.md +1 -1
- data/diary-ruby.gemspec +1 -0
- data/exe/diaryrb +57 -50
- data/lib/diary-ruby.rb +6 -2
- data/lib/diary-ruby/database.rb +22 -0
- data/lib/diary-ruby/database/migrator.rb +90 -0
- data/lib/diary-ruby/database/query.rb +269 -0
- data/lib/diary-ruby/ext/concern.rb +166 -0
- data/lib/diary-ruby/model.rb +98 -0
- data/lib/diary-ruby/models/entry.rb +151 -0
- data/lib/diary-ruby/parser.rb +1 -2
- data/lib/diary-ruby/server/server.rb +8 -28
- data/lib/diary-ruby/server/views/index.erb +1 -1
- data/lib/diary-ruby/version.rb +1 -1
- metadata +22 -3
- data/lib/diary-ruby/entry.rb +0 -85
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2f7c7aa9aab6f4119776f1ec8e197487beddc6b0
|
4
|
+
data.tar.gz: d706e3f057f2ba5911e79bf8273057127b21db19
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b43c04f2ae1cd94d0e2df2ea58c351a3969b65cf95778f1018bf1d90d586f337cee7cbb8b2be4dd9e39cb435956f10f8b334e79c27881dbc595c9929b9ea88d
|
7
|
+
data.tar.gz: 82806e6364fb1405b7c521769a1cfac4b2cab2a8da4cc495e42f0e7cc4e1f0ad10bb69d6fc7d7702cf042eb5a29aa4881dd4d5620c89817b3d745117560785aa
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
data/diary-ruby.gemspec
CHANGED
data/exe/diaryrb
CHANGED
@@ -7,21 +7,23 @@ require 'thread'
|
|
7
7
|
|
8
8
|
DEFAULT_DIARY = "diaryrb.store"
|
9
9
|
|
10
|
-
prompt_for_password = false
|
10
|
+
# prompt_for_password = false
|
11
11
|
opts = Slop.parse do |o|
|
12
|
-
# o.string '-c', '--configuration', 'config file location'
|
13
12
|
o.string '-d', '--diary', "choose diary storage file (leave blank for default, #{DEFAULT_DIARY})"
|
14
|
-
o.string '-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
13
|
+
# o.string '-c', '--configuration', 'config file location'
|
14
|
+
# o.string '-p', '--passphrase', 'Use given encryption passphrase or prompt if option is used but no passphrase is given.', default: false do |v|
|
15
|
+
# if v.start_with?('-') || v.nil? || v.strip.size == 0
|
16
|
+
# prompt_for_password = true
|
17
|
+
# end
|
18
|
+
# end
|
19
19
|
|
20
20
|
# usage modes
|
21
21
|
o.separator ''
|
22
22
|
o.separator "Actions (can't be used in combination):"
|
23
|
+
o.bool '-x', '--export', 'export all entries immediately to JSON'
|
23
24
|
o.string '-e', '--edit', 'edit a specific post'
|
24
25
|
o.bool '-l', '--list', 'list all posts by date'
|
26
|
+
o.string '-t', '--tag', 'list entries, filtered by tag'
|
25
27
|
o.bool '-s', '--serve', 'start Diary webserver'
|
26
28
|
|
27
29
|
o.separator ''
|
@@ -57,50 +59,50 @@ end
|
|
57
59
|
|
58
60
|
Diary::Configuration.current_diary = _diary
|
59
61
|
|
60
|
-
_passphrase = nil
|
61
|
-
if prompt_for_password
|
62
|
-
require 'io/console'
|
63
|
-
print "Enter passphrase (leave blank for none): "
|
64
|
-
_passphrase = STDIN.noecho {|io| io.gets}.chomp
|
65
|
-
elsif opts[:passphrase] && opts[:passphrase].strip.size > 0
|
66
|
-
_passphrase = opts[:passphrase]
|
67
|
-
elsif ENV['PASSPHRASE']
|
68
|
-
_passphrase = ENV['PASSPHRASE']
|
69
|
-
elsif Diary::Configuration.passphrase
|
70
|
-
_passphrase = Diary::Configuration.passphrase
|
71
|
-
end
|
72
|
-
|
73
62
|
diary_path = Diary::Configuration.path || Diary::Configuration.current_diary
|
74
|
-
|
75
|
-
if _passphrase.nil? || _passphrase.size == 0
|
76
|
-
Diary.debug "LOADING WITH NO PASSPHRASE!"
|
77
|
-
$store = Diary::Store.new(diary_path)
|
78
|
-
else
|
79
|
-
Diary.debug "LOADING WITH PASSPHRASE #{ _passphrase.gsub(/./, '*') }"
|
80
|
-
$store = Diary::SecureStore.new(diary_path, _passphrase)
|
81
|
-
end
|
63
|
+
database = Diary::Database.new(diary_path)
|
82
64
|
|
83
|
-
#
|
84
|
-
|
85
|
-
|
86
|
-
db[:entries] = db[:entries].compact.uniq.sort
|
87
|
-
db[:tags] ||= []
|
88
|
-
end
|
65
|
+
# make sure we're always up to date
|
66
|
+
migrator = Diary::Migrator.new(database)
|
67
|
+
migrator.migrate!
|
89
68
|
|
90
|
-
|
91
|
-
|
69
|
+
# initialize ORM
|
70
|
+
Diary::Model.connection = database
|
71
|
+
|
72
|
+
if opts.export?
|
92
73
|
puts ''
|
93
74
|
|
94
|
-
if
|
75
|
+
if Diary::Entry.count == 0
|
95
76
|
puts "No entries"
|
96
77
|
exit
|
97
78
|
else
|
98
|
-
|
99
|
-
|
79
|
+
require 'json'
|
80
|
+
|
81
|
+
output = []
|
82
|
+
|
83
|
+
Diary::Entry.order('created_at DESC').each do |entry|
|
84
|
+
output << entry.to_hash
|
85
|
+
end
|
86
|
+
|
87
|
+
puts JSON.pretty_generate(output)
|
88
|
+
end
|
89
|
+
elsif opts.list?
|
90
|
+
puts ''
|
91
|
+
if Diary::Entry.count == 0
|
92
|
+
puts "No entries"
|
93
|
+
exit
|
94
|
+
else
|
95
|
+
Diary::Entry.order('created_at DESC').each do |entry|
|
96
|
+
puts entry.summary
|
100
97
|
end
|
101
98
|
end
|
99
|
+
elsif opts[:tag] && (tag_id = Diary::Model.select_value('select rowid from tags where name = ?', opts[:tag]))
|
100
|
+
entry_ids = Diary::Model.select_values('select entry_id from taggings where tag_id = ?', tag_id)
|
101
|
+
Diary::Entry.where(date_key: entry_ids).each do |entry|
|
102
|
+
puts entry.summary
|
103
|
+
end
|
102
104
|
elsif opts.serve?
|
103
|
-
Diary::Server.store = $store
|
105
|
+
# Diary::Server.store = $store
|
104
106
|
t = Thread.new do
|
105
107
|
Diary::Server.run!
|
106
108
|
end
|
@@ -114,7 +116,7 @@ else
|
|
114
116
|
|
115
117
|
def parse_and_store(file)
|
116
118
|
diary_entry = Diary::Parser.parse_file(file)
|
117
|
-
|
119
|
+
diary_entry.save!
|
118
120
|
end
|
119
121
|
|
120
122
|
# create a tempfile to store entry in progress in EDITOR
|
@@ -127,18 +129,18 @@ else
|
|
127
129
|
time: Time.now.strftime("%T"),
|
128
130
|
tags: "",
|
129
131
|
title: "",
|
130
|
-
|
132
|
+
body: "text goes here"
|
131
133
|
}
|
132
134
|
|
133
|
-
# if --edit option is used with a valid entry, load it
|
134
|
-
if opts[:edit] && (
|
135
|
-
entry = Diary::Entry.
|
136
|
-
entry_source = entry.to_hash
|
137
|
-
|
135
|
+
# # if --edit option is used with a valid entry, load it
|
136
|
+
if opts[:edit] && Diary::Entry.exists?(date_key: opts[:edit])
|
137
|
+
entry = Diary::Entry.find(date_key: opts[:edit])
|
138
|
+
entry_source = entry.to_hash if entry
|
139
|
+
# FIXME: set tags
|
138
140
|
end
|
139
141
|
|
140
142
|
# prepare entry and launch editor
|
141
|
-
tmpl = Diary::Entry.generate(entry_source
|
143
|
+
tmpl = Diary::Entry.generate(entry_source)
|
142
144
|
file.write(tmpl)
|
143
145
|
|
144
146
|
ed = "vim -f"
|
@@ -149,12 +151,17 @@ else
|
|
149
151
|
end
|
150
152
|
|
151
153
|
pid = fork do
|
152
|
-
|
154
|
+
# split the editor into a separate process
|
155
|
+
command = if /%s/ =~ ed
|
156
|
+
ed % [file.path]
|
157
|
+
else
|
158
|
+
"#{ ed } #{ file.path }"
|
159
|
+
end
|
160
|
+
exec(command)
|
153
161
|
end
|
154
162
|
|
155
163
|
# wait for child to finish, exit when the editor exits
|
156
164
|
exit_signal = Queue.new
|
157
|
-
|
158
165
|
trap("CLD") do
|
159
166
|
Diary.log "CHILD PID #{pid} TERMINATED"
|
160
167
|
exit_signal.push(true)
|
data/lib/diary-ruby.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
require "diary-ruby/version"
|
2
|
-
require "diary-ruby/store"
|
3
|
-
require "diary-ruby/
|
2
|
+
# require "diary-ruby/store"
|
3
|
+
require "diary-ruby/database"
|
4
|
+
require "diary-ruby/database/migrator"
|
5
|
+
require "diary-ruby/database/query"
|
6
|
+
require "diary-ruby/model"
|
7
|
+
require "diary-ruby/models/entry"
|
4
8
|
require "diary-ruby/parser"
|
5
9
|
require "diary-ruby/configuration"
|
6
10
|
require "diary-ruby/server/server"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
require 'diary-ruby/database/query'
|
3
|
+
|
4
|
+
module Diary
|
5
|
+
class Database
|
6
|
+
attr_reader :database
|
7
|
+
|
8
|
+
def initialize(path)
|
9
|
+
@database = SQLite3::Database.new(path)
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute(*query)
|
13
|
+
if block_given?
|
14
|
+
@database.execute(*query) do |row|
|
15
|
+
yield row
|
16
|
+
end
|
17
|
+
else
|
18
|
+
@database.execute(*query)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
MIGRATIONS = {}
|
2
|
+
|
3
|
+
INITIALIZE = %[
|
4
|
+
CREATE TABLE IF NOT EXISTS `versions` (
|
5
|
+
`version` TEXT NOT NULL,
|
6
|
+
`migrated_at` TEXT DEFAULT NULL
|
7
|
+
);
|
8
|
+
]
|
9
|
+
|
10
|
+
## MIGRATION FORMAT:
|
11
|
+
# MIGRATIONS[number] = array of sql statements
|
12
|
+
|
13
|
+
# 001 - create initial tables
|
14
|
+
MIGRATIONS['001'] = [%[
|
15
|
+
CREATE TABLE IF NOT EXISTS `entries` (
|
16
|
+
`date_key` TEXT NOT NULL PRIMARY KEY,
|
17
|
+
`day` TEXT NOT NULL,
|
18
|
+
`time` TEXT NOT NULL,
|
19
|
+
`title` TEXT,
|
20
|
+
`link` TEXT,
|
21
|
+
`body` TEXT,
|
22
|
+
`created_at` TEXT NOT NULL,
|
23
|
+
`updated_at` TEXT DEFAULT NULL
|
24
|
+
);
|
25
|
+
], %[
|
26
|
+
CREATE INDEX IF NOT EXISTS `index_entries_on_key` on `entries` (`date_key`);
|
27
|
+
], %[
|
28
|
+
CREATE TABLE IF NOT EXISTS `tags` (
|
29
|
+
`name` TEXT DEFAULT NULL
|
30
|
+
);
|
31
|
+
], %[
|
32
|
+
CREATE TABLE IF NOT EXISTS `taggings` (
|
33
|
+
`tag_id` INTEGER NOT NULL,
|
34
|
+
`entry_id` TEXT NOT NULL
|
35
|
+
);
|
36
|
+
], %[
|
37
|
+
CREATE INDEX IF NOT EXISTS `index_taggings_on_tag_id` on `taggings` (`tag_id`);
|
38
|
+
], %[
|
39
|
+
CREATE INDEX IF NOT EXISTS `index_taggings_on_entry_id` on `taggings` (`entry_id`);
|
40
|
+
]]
|
41
|
+
|
42
|
+
MIGRATION_VERSIONS = MIGRATIONS.keys.sort
|
43
|
+
|
44
|
+
module Diary
|
45
|
+
class Migrator
|
46
|
+
attr_reader :db
|
47
|
+
|
48
|
+
def initialize(db)
|
49
|
+
@db = db.database
|
50
|
+
end
|
51
|
+
|
52
|
+
def migrate!
|
53
|
+
exists = false
|
54
|
+
db.execute( "SELECT name FROM sqlite_master WHERE type='table' AND name='versions';" ) do |row|
|
55
|
+
if row
|
56
|
+
exists = true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if !exists
|
61
|
+
db.execute(INITIALIZE)
|
62
|
+
end
|
63
|
+
|
64
|
+
MIGRATION_VERSIONS.each do |version|
|
65
|
+
exists = false
|
66
|
+
on_date = nil
|
67
|
+
db.execute( "select rowid, migrated_at from versions WHERE version = '#{version}'" ) do |row|
|
68
|
+
if row
|
69
|
+
exists = true
|
70
|
+
on_date = row[1]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
if !exists
|
75
|
+
Diary.debug("UPDATING DATABASE TO VERSION #{ version }")
|
76
|
+
if MIGRATIONS[version].is_a?(Array)
|
77
|
+
MIGRATIONS[version].each do |statement|
|
78
|
+
db.execute(statement)
|
79
|
+
end
|
80
|
+
else
|
81
|
+
db.execute(MIGRATIONS[version])
|
82
|
+
end
|
83
|
+
db.execute("INSERT INTO versions VALUES ('#{version}', strftime('%Y-%m-%dT%H:%M:%S+0000'));")
|
84
|
+
else
|
85
|
+
Diary.debug("AT #{ version } SINCE #{ on_date }")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,269 @@
|
|
1
|
+
|
2
|
+
module Diary
|
3
|
+
module Query
|
4
|
+
class Select
|
5
|
+
def initialize(table, context=nil)
|
6
|
+
@table_name = table
|
7
|
+
@context = context
|
8
|
+
@additions = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def context
|
12
|
+
@context
|
13
|
+
end
|
14
|
+
|
15
|
+
## evaluation conditions, called when Select is given a context
|
16
|
+
# FIXME: it's gross that Query::Select knows about Model
|
17
|
+
|
18
|
+
def execute_in_context(sql)
|
19
|
+
Diary.debug("[Query::Select execute_in_context] connection.execute(#{ sql.inspect })")
|
20
|
+
if Array === sql
|
21
|
+
context.connection.execute(*sql)
|
22
|
+
else
|
23
|
+
context.connection.execute(sql)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def first
|
28
|
+
return self unless context
|
29
|
+
result = execute_in_context(self.limit(1).to_sql)
|
30
|
+
context.materialize(result)[0]
|
31
|
+
end
|
32
|
+
|
33
|
+
def all
|
34
|
+
return self unless context
|
35
|
+
result = execute_in_context(self.to_sql)
|
36
|
+
context.materialize(result)
|
37
|
+
end
|
38
|
+
|
39
|
+
def each(&block)
|
40
|
+
return self unless context
|
41
|
+
result = execute_in_context(self.to_sql)
|
42
|
+
context.materialize(result).each(&block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def map(&block)
|
46
|
+
return self unless context
|
47
|
+
result = execute_in_context(self.to_sql)
|
48
|
+
context.materialize(result).map(&block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def size
|
52
|
+
return self unless context
|
53
|
+
result = execute_in_context(self.to_sql)
|
54
|
+
result.size
|
55
|
+
end
|
56
|
+
alias :count :size
|
57
|
+
|
58
|
+
##
|
59
|
+
|
60
|
+
def select(column_query)
|
61
|
+
@column_query = column_query
|
62
|
+
end
|
63
|
+
|
64
|
+
def exists?(*conditions)
|
65
|
+
if conditions.size > 0
|
66
|
+
@additions = []
|
67
|
+
@additions << Where.new(*conditions)
|
68
|
+
@additions << Limit.new(1)
|
69
|
+
end
|
70
|
+
c = self.count
|
71
|
+
c && c > 0
|
72
|
+
end
|
73
|
+
|
74
|
+
def where(*conditions)
|
75
|
+
# multiple wheres are OR'd
|
76
|
+
@additions << Where.new(*conditions)
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
def order(*conditions)
|
81
|
+
@additions << Order.new(*conditions)
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def limit(*conditions)
|
86
|
+
@additions << Limit.new(*conditions)
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
def group_by(*conditions)
|
91
|
+
@additions << GroupBy.new(*conditions)
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_sql
|
96
|
+
# combine @additions in order: WHERE () GROUP BY () ORDER () LIMIT ()
|
97
|
+
|
98
|
+
sql_string = []
|
99
|
+
bind_vars = []
|
100
|
+
|
101
|
+
wheres = @additions.select {|a| Where === a}
|
102
|
+
group_bys = @additions.select {|a| GroupBy === a}
|
103
|
+
orders = @additions.select {|a| Order === a}
|
104
|
+
limits = @additions.select {|a| Limit === a}
|
105
|
+
|
106
|
+
if wheres.size > 0
|
107
|
+
sql_string << "WHERE"
|
108
|
+
|
109
|
+
where_params = []
|
110
|
+
|
111
|
+
wheres = wheres.each do |w|
|
112
|
+
if w.has_bound_vars?
|
113
|
+
bind_vars << w.prepared_statement.bind_vars
|
114
|
+
end
|
115
|
+
|
116
|
+
where_params << w.prepared_statement.sql_string
|
117
|
+
end
|
118
|
+
|
119
|
+
sql_string << where_params.map {|wp|
|
120
|
+
"(#{ wp })"
|
121
|
+
}.join(' OR ')
|
122
|
+
end
|
123
|
+
|
124
|
+
if group_bys.size > 0
|
125
|
+
sql_string << "GROUP BY #{group_bys.map {|gb| gb.prepared_statement.sql_string}.join(', ')}"
|
126
|
+
end
|
127
|
+
|
128
|
+
if orders.size > 0
|
129
|
+
sql_string << "ORDER BY #{orders.map {|ord| ord.prepared_statement.sql_string}.join(', ')}"
|
130
|
+
end
|
131
|
+
|
132
|
+
if limits.size > 0
|
133
|
+
# only 1 allowed, last takes precedence
|
134
|
+
limit = limits.last
|
135
|
+
sql_string << "LIMIT #{limit.prepared_statement.sql_string}"
|
136
|
+
end
|
137
|
+
|
138
|
+
query = [
|
139
|
+
"SELECT #{ @column_query || '*' }",
|
140
|
+
"FROM `#{ @table_name }`",
|
141
|
+
sql_string
|
142
|
+
].join(' ')
|
143
|
+
|
144
|
+
# once to_sql is called, the Query is reset
|
145
|
+
@additions = []
|
146
|
+
|
147
|
+
# return sqlite compatible SQL
|
148
|
+
returning = if bind_vars.size > 0
|
149
|
+
[query, bind_vars.flatten]
|
150
|
+
else
|
151
|
+
query
|
152
|
+
end
|
153
|
+
returning
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class SQLBoundParams
|
158
|
+
def initialize(left, right)
|
159
|
+
@for_sql_query = [left, right]
|
160
|
+
end
|
161
|
+
|
162
|
+
def sql_string
|
163
|
+
@for_sql_query[0]
|
164
|
+
end
|
165
|
+
|
166
|
+
def bind_vars
|
167
|
+
@for_sql_query[1]
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class SQLString
|
172
|
+
def initialize(value)
|
173
|
+
@for_sql_query = value
|
174
|
+
end
|
175
|
+
|
176
|
+
def sql_string
|
177
|
+
@for_sql_query
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class Node
|
182
|
+
def string_or_symbol?(value)
|
183
|
+
String === value || Symbol === value
|
184
|
+
end
|
185
|
+
|
186
|
+
def prepared_statement
|
187
|
+
@sql_result
|
188
|
+
end
|
189
|
+
|
190
|
+
def has_bound_vars?
|
191
|
+
SQLBoundParams === prepared_statement
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
class Where < Node
|
196
|
+
# convert conditions to AND'd list
|
197
|
+
# returns either string or (string, bind_params) 2-tuple
|
198
|
+
def initialize(*conditions)
|
199
|
+
@sql_result = if Hash === conditions[0]
|
200
|
+
attrs = conditions[0]
|
201
|
+
|
202
|
+
keys = attrs.keys
|
203
|
+
vals = keys.map {|k| attrs[k]}
|
204
|
+
|
205
|
+
and_string = keys.map do |k|
|
206
|
+
if attrs[k].is_a?(Array)
|
207
|
+
bind_hold = attrs[k].map {|_| '?'}.join(',')
|
208
|
+
"`#{k}` in (#{bind_hold})"
|
209
|
+
else
|
210
|
+
"`#{k}` = ?"
|
211
|
+
end
|
212
|
+
end.join(' AND ')
|
213
|
+
|
214
|
+
# (string, bind)
|
215
|
+
SQLBoundParams.new(and_string, vals.flatten)
|
216
|
+
elsif conditions.size > 1 && String === conditions[0]
|
217
|
+
# assume (string, bind) given
|
218
|
+
SQLBoundParams.new(conditions[0], conditions[1..-1])
|
219
|
+
elsif conditions.size == 1 && String === conditions[0]
|
220
|
+
SQLString.new(conditions[0])
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
class Order < Node
|
226
|
+
def initialize(*conditions)
|
227
|
+
sql_string = if conditions.size == 1
|
228
|
+
if string_or_symbol?(conditions[0])
|
229
|
+
conditions[0]
|
230
|
+
elsif Array === conditions[0]
|
231
|
+
conditions.join(', ')
|
232
|
+
else
|
233
|
+
conditions[0].to_s
|
234
|
+
end
|
235
|
+
elsif conditions.size > 1
|
236
|
+
conditions.join(', ')
|
237
|
+
end
|
238
|
+
|
239
|
+
@sql_result = SQLString.new(sql_string)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
class Limit < Node
|
244
|
+
def initialize(*conditions)
|
245
|
+
sql_string = if conditions.size == 1
|
246
|
+
conditions[0]
|
247
|
+
elsif conditions.size > 1
|
248
|
+
conditions.join(', ')
|
249
|
+
end
|
250
|
+
@sql_result = SQLString.new(sql_string)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
class GroupBy < Node
|
255
|
+
def initialize(*conditions)
|
256
|
+
sql_string = if conditions.size == 1
|
257
|
+
conditions[0]
|
258
|
+
elsif conditions.size > 1
|
259
|
+
conditions.join(', ')
|
260
|
+
end
|
261
|
+
@sql_result = SQLString.new(sql_string)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# class Table
|
266
|
+
# extend Select
|
267
|
+
# end
|
268
|
+
end
|
269
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# taken from: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/concern.rb
|
2
|
+
#
|
3
|
+
# Copyright (c) 2005-2016 David Heinemeier Hansson
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
# a copy of this software and associated documentation files (the
|
7
|
+
# "Software"), to deal in the Software without restriction, including
|
8
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
# the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
#
|
24
|
+
module ActiveSupport
|
25
|
+
# A typical module looks like this:
|
26
|
+
#
|
27
|
+
# module M
|
28
|
+
# def self.included(base)
|
29
|
+
# base.extend ClassMethods
|
30
|
+
# base.class_eval do
|
31
|
+
# scope :disabled, -> { where(disabled: true) }
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# module ClassMethods
|
36
|
+
# ...
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# By using <tt>ActiveSupport::Concern</tt> the above module could instead be
|
41
|
+
# written as:
|
42
|
+
#
|
43
|
+
# require 'active_support/concern'
|
44
|
+
#
|
45
|
+
# module M
|
46
|
+
# extend ActiveSupport::Concern
|
47
|
+
#
|
48
|
+
# included do
|
49
|
+
# scope :disabled, -> { where(disabled: true) }
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# class_methods do
|
53
|
+
# ...
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# Moreover, it gracefully handles module dependencies. Given a +Foo+ module
|
58
|
+
# and a +Bar+ module which depends on the former, we would typically write the
|
59
|
+
# following:
|
60
|
+
#
|
61
|
+
# module Foo
|
62
|
+
# def self.included(base)
|
63
|
+
# base.class_eval do
|
64
|
+
# def self.method_injected_by_foo
|
65
|
+
# ...
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# module Bar
|
72
|
+
# def self.included(base)
|
73
|
+
# base.method_injected_by_foo
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# class Host
|
78
|
+
# include Foo # We need to include this dependency for Bar
|
79
|
+
# include Bar # Bar is the module that Host really needs
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
|
83
|
+
# could try to hide these from +Host+ directly including +Foo+ in +Bar+:
|
84
|
+
#
|
85
|
+
# module Bar
|
86
|
+
# include Foo
|
87
|
+
# def self.included(base)
|
88
|
+
# base.method_injected_by_foo
|
89
|
+
# end
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# class Host
|
93
|
+
# include Bar
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
|
97
|
+
# is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
|
98
|
+
# module dependencies are properly resolved:
|
99
|
+
#
|
100
|
+
# require 'active_support/concern'
|
101
|
+
#
|
102
|
+
# module Foo
|
103
|
+
# extend ActiveSupport::Concern
|
104
|
+
# included do
|
105
|
+
# def self.method_injected_by_foo
|
106
|
+
# ...
|
107
|
+
# end
|
108
|
+
# end
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# module Bar
|
112
|
+
# extend ActiveSupport::Concern
|
113
|
+
# include Foo
|
114
|
+
#
|
115
|
+
# included do
|
116
|
+
# self.method_injected_by_foo
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# class Host
|
121
|
+
# include Bar # It works, now Bar takes care of its dependencies
|
122
|
+
# end
|
123
|
+
module Concern
|
124
|
+
class MultipleIncludedBlocks < StandardError #:nodoc:
|
125
|
+
def initialize
|
126
|
+
super "Cannot define multiple 'included' blocks for a Concern"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.extended(base) #:nodoc:
|
131
|
+
base.instance_variable_set(:@_dependencies, [])
|
132
|
+
end
|
133
|
+
|
134
|
+
def append_features(base)
|
135
|
+
if base.instance_variable_defined?(:@_dependencies)
|
136
|
+
base.instance_variable_get(:@_dependencies) << self
|
137
|
+
return false
|
138
|
+
else
|
139
|
+
return false if base < self
|
140
|
+
@_dependencies.each { |dep| base.send(:include, dep) }
|
141
|
+
super
|
142
|
+
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
|
143
|
+
base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def included(base = nil, &block)
|
148
|
+
if base.nil?
|
149
|
+
raise MultipleIncludedBlocks if instance_variable_defined?(:@_included_block)
|
150
|
+
|
151
|
+
@_included_block = block
|
152
|
+
else
|
153
|
+
super
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def class_methods(&class_methods_module_definition)
|
158
|
+
mod = const_defined?(:ClassMethods, false) ?
|
159
|
+
const_get(:ClassMethods) :
|
160
|
+
const_set(:ClassMethods, Module.new)
|
161
|
+
|
162
|
+
mod.module_eval(&class_methods_module_definition)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'diary-ruby/ext/concern'
|
2
|
+
|
3
|
+
module Diary
|
4
|
+
module ModelQuery
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Instance Methods
|
8
|
+
def timestamp_sql
|
9
|
+
"strftime('%Y-%m-%dT%H:%M:%S+0000')"
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def columns
|
14
|
+
@columns ||= connection.execute("PRAGMA table_info(#{table_name})")
|
15
|
+
end
|
16
|
+
|
17
|
+
def column_names
|
18
|
+
@column_names ||= columns.map {|col_info| col_info[1]}
|
19
|
+
end
|
20
|
+
|
21
|
+
def results_to_hashes(array_of_rows)
|
22
|
+
array_of_rows.map do |row|
|
23
|
+
Hash[ column_names.zip(row) ]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def materialize(array_of_rows)
|
28
|
+
results_to_hashes(array_of_rows).map do |record_hash|
|
29
|
+
if respond_to?(:from_hash)
|
30
|
+
from_hash(record_hash)
|
31
|
+
else
|
32
|
+
record_hash
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def find(attrs)
|
38
|
+
where(attrs).first
|
39
|
+
end
|
40
|
+
|
41
|
+
def new_select_relation
|
42
|
+
Diary::Query::Select.new(table_name, self)
|
43
|
+
end
|
44
|
+
|
45
|
+
%w(where order group_by limit exists?).each do |q_type|
|
46
|
+
define_method(q_type.to_sym) do |*conditions|
|
47
|
+
new_select_relation.send(q_type.to_sym, *conditions)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
%w(all first count).each do |q_type|
|
52
|
+
define_method(q_type.to_sym) do
|
53
|
+
new_select_relation.send(q_type.to_sym)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
%w(each map).each do |q_type|
|
58
|
+
define_method(q_type.to_sym) do |&block|
|
59
|
+
new_select_relation.send(q_type.to_sym, &block)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Model
|
67
|
+
include ModelQuery
|
68
|
+
|
69
|
+
class << self
|
70
|
+
def connection=(db)
|
71
|
+
@@connection = db
|
72
|
+
end
|
73
|
+
|
74
|
+
def connection
|
75
|
+
@@connection
|
76
|
+
end
|
77
|
+
|
78
|
+
# one-off queries
|
79
|
+
def execute(sql, *binds)
|
80
|
+
Diary.debug("[Model execute] #{ sql } #{ binds.inspect }")
|
81
|
+
connection.execute(sql, binds)
|
82
|
+
end
|
83
|
+
|
84
|
+
# one-off queries
|
85
|
+
def select_rows(sql, *binds)
|
86
|
+
execute(sql, *binds)
|
87
|
+
end
|
88
|
+
|
89
|
+
def select_values(sql, *binds)
|
90
|
+
select_rows(sql, *binds).map {|row| row[0]}
|
91
|
+
end
|
92
|
+
|
93
|
+
def select_value(sql, *binds)
|
94
|
+
select_values(sql, *binds).first
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
TEMPLATE = "# last entry posted at %{last_update_at}
|
2
|
+
|
3
|
+
DAY %{day}
|
4
|
+
TIME %{time}
|
5
|
+
TAGS %{tags}
|
6
|
+
TITLE %{title}
|
7
|
+
|
8
|
+
---
|
9
|
+
|
10
|
+
%{body}
|
11
|
+
"
|
12
|
+
|
13
|
+
require 'rdiscount'
|
14
|
+
require 'diary-ruby/model'
|
15
|
+
|
16
|
+
module Diary
|
17
|
+
class Entry < Model
|
18
|
+
attr_accessor :day, :time, :tags, :body, :link, :title, :date_key
|
19
|
+
|
20
|
+
def self.table_name
|
21
|
+
'entries'
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.from_hash(record)
|
25
|
+
entry = self.new(
|
26
|
+
day: record['day'],
|
27
|
+
time: record['time'],
|
28
|
+
body: record['body'],
|
29
|
+
title: record['title'],
|
30
|
+
date_key: record['date_key'],
|
31
|
+
)
|
32
|
+
|
33
|
+
# taggings!
|
34
|
+
begin
|
35
|
+
tag_ids = select_values('select tag_id from taggings where entry_id = ?', [entry.identifier])
|
36
|
+
bind_hold = tag_ids.map {|_| '?'}.join(',')
|
37
|
+
entry.tags = select_values("select name from tags where rowid in (#{ bind_hold })", *tag_ids)
|
38
|
+
rescue => ex
|
39
|
+
Diary.debug "FAILED TO LOAD TAGS. #{ ex.message }"
|
40
|
+
end
|
41
|
+
|
42
|
+
entry
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.keygen(day, time)
|
46
|
+
"%s-%s" % [day, time]
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.generate(options={})
|
50
|
+
options[:last_update_at] = connection.execute("select max(updated_at) from #{table_name}")[0] || ''
|
51
|
+
|
52
|
+
# convert Arrays to dumb CSV
|
53
|
+
options.each do |(k, v)|
|
54
|
+
if v.is_a?(Array)
|
55
|
+
options[k] = v.join(', ')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
TEMPLATE % options
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize(options={})
|
63
|
+
@day = options[:day]
|
64
|
+
@time = options[:time]
|
65
|
+
@tags = options[:tags] || []
|
66
|
+
@body = options[:body]
|
67
|
+
@title = options[:title]
|
68
|
+
|
69
|
+
if options[:date_key].nil?
|
70
|
+
@date_key = identifier
|
71
|
+
else
|
72
|
+
@date_key = options[:date_key]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def identifier
|
77
|
+
self.class.keygen(day, time)
|
78
|
+
end
|
79
|
+
|
80
|
+
def formatted_body
|
81
|
+
RDiscount.new(body).to_html
|
82
|
+
end
|
83
|
+
|
84
|
+
def truncated_body
|
85
|
+
_truncated = body
|
86
|
+
if _truncated.size > 40
|
87
|
+
_truncated = "#{ _truncated[0..40] }..."
|
88
|
+
end
|
89
|
+
_truncated
|
90
|
+
end
|
91
|
+
|
92
|
+
def summary
|
93
|
+
"%-24s%-46s%s" % [date_key, truncated_body, tags.join(', ')]
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_hash
|
97
|
+
{
|
98
|
+
day: day,
|
99
|
+
time: time,
|
100
|
+
tags: tags,
|
101
|
+
body: body,
|
102
|
+
title: title,
|
103
|
+
date_key: date_key,
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
def save!
|
108
|
+
if self.class.find(date_key: date_key)
|
109
|
+
# update record
|
110
|
+
sql = "UPDATE entries SET day=?, time=?, body=?, link=?, title=?, updated_at=#{timestamp_sql} WHERE date_key=?"
|
111
|
+
self.class.execute(sql, day, time, body, link, title, date_key)
|
112
|
+
else
|
113
|
+
# insert
|
114
|
+
sql = %[INSERT INTO entries (day, time, body, link, title, date_key, created_at, updated_at)
|
115
|
+
VALUES (?, ?, ?, ?, ?, ?, #{timestamp_sql}, #{timestamp_sql})]
|
116
|
+
self.class.execute(sql, day, time, body, link, title, date_key)
|
117
|
+
end
|
118
|
+
|
119
|
+
begin
|
120
|
+
update_tags!
|
121
|
+
rescue => ex
|
122
|
+
Diary.debug "FAILED TO UPDATE TAGS #{ tags.inspect }. #{ ex.message }"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def update_tags!
|
127
|
+
# clean out existing
|
128
|
+
Diary.debug "CLEANING `taggings`"
|
129
|
+
self.class.execute('delete from taggings where entry_id = ?', [identifier])
|
130
|
+
|
131
|
+
# add back
|
132
|
+
tags.each do |tag|
|
133
|
+
# is tag in db?
|
134
|
+
tag_id = self.class.select_value('select rowid from tags where name = ?', tag)
|
135
|
+
|
136
|
+
if tag_id.nil?
|
137
|
+
Diary.debug "CREATING TAG #{ tag.inspect }"
|
138
|
+
|
139
|
+
# exists
|
140
|
+
Diary.debug self.class.select_rows('PRAGMA table_info(tags)').inspect
|
141
|
+
self.class.execute("insert into tags (name) values (?)", [tag])
|
142
|
+
tag_id = self.class.select_value('select last_insert_rowid()')
|
143
|
+
end
|
144
|
+
|
145
|
+
Diary.debug "CREATING tagging"
|
146
|
+
self.class.execute('insert into taggings (tag_id, entry_id) values (?, ?)', [tag_id, identifier])
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
data/lib/diary-ruby/parser.rb
CHANGED
@@ -46,11 +46,10 @@ module Diary
|
|
46
46
|
Diary.debug "BODY #{ body.join(" ") }"
|
47
47
|
|
48
48
|
return Entry.new(
|
49
|
-
nil,
|
50
49
|
day: metadata['day'],
|
51
50
|
time: metadata['time'],
|
52
51
|
tags: metadata['tags'],
|
53
|
-
|
52
|
+
body: body.join("\n").strip,
|
54
53
|
title: metadata['title'],
|
55
54
|
key: key,
|
56
55
|
)
|
@@ -22,27 +22,10 @@ module Diary
|
|
22
22
|
end
|
23
23
|
|
24
24
|
get '/' do
|
25
|
-
|
26
|
-
|
27
|
-
if keys.nil?
|
28
|
-
store.write do |db|
|
29
|
-
db[:entries] = []
|
30
|
-
end
|
31
|
-
|
32
|
-
keys = []
|
25
|
+
@entries = Entry.order('created_at DESC').map do |entry_hash|
|
26
|
+
Entry.from_hash(entry_hash)
|
33
27
|
end
|
34
28
|
|
35
|
-
@entries = keys.uniq.map {|entry_key|
|
36
|
-
entry = store.read(entry_key)
|
37
|
-
|
38
|
-
if entry
|
39
|
-
logger.debug "LOAD #{ entry }"
|
40
|
-
Entry.from_store(entry)
|
41
|
-
else
|
42
|
-
nil
|
43
|
-
end
|
44
|
-
}.compact
|
45
|
-
|
46
29
|
logger.info "returning keys: #{ keys }"
|
47
30
|
logger.info "returning entries: #{ @entries }"
|
48
31
|
|
@@ -53,16 +36,13 @@ module Diary
|
|
53
36
|
content_type :json
|
54
37
|
|
55
38
|
key = params[:key]
|
56
|
-
entry_hash =
|
57
|
-
entry = Entry.from_store(entry_hash)
|
58
|
-
|
59
|
-
content = RDiscount.new entry.text
|
60
|
-
entry_hash[:formatted] = content.to_html
|
39
|
+
entry_hash = Entry.find(date_key: key)
|
61
40
|
|
62
|
-
if
|
41
|
+
if entry_hash
|
42
|
+
entry = Entry.from_store(entry_hash)
|
43
|
+
content = RDiscount.new entry['body']
|
44
|
+
entry_hash[:formatted] = content.to_html
|
63
45
|
entry_hash.to_json
|
64
|
-
else
|
65
|
-
{}
|
66
46
|
end
|
67
47
|
end
|
68
48
|
|
@@ -81,7 +61,7 @@ module Diary
|
|
81
61
|
day: params[:day],
|
82
62
|
time: params[:time],
|
83
63
|
tags: tags,
|
84
|
-
|
64
|
+
body: params[:body],
|
85
65
|
)
|
86
66
|
|
87
67
|
store.write_entry(entry)
|
data/lib/diary-ruby/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: diary-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Bachman
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-01-
|
11
|
+
date: 2016-01-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '2.4'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: sqlite3
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '1.3'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '1.3'
|
125
139
|
description: 'A command line diary: diaryrb'
|
126
140
|
email:
|
127
141
|
- adam.bachman@gmail.com
|
@@ -144,9 +158,14 @@ files:
|
|
144
158
|
- exe/diaryrb
|
145
159
|
- lib/diary-ruby.rb
|
146
160
|
- lib/diary-ruby/configuration.rb
|
147
|
-
- lib/diary-ruby/
|
161
|
+
- lib/diary-ruby/database.rb
|
162
|
+
- lib/diary-ruby/database/migrator.rb
|
163
|
+
- lib/diary-ruby/database/query.rb
|
164
|
+
- lib/diary-ruby/ext/concern.rb
|
148
165
|
- lib/diary-ruby/ext/encryptor.rb
|
149
166
|
- lib/diary-ruby/ext/secure_pstore.rb
|
167
|
+
- lib/diary-ruby/model.rb
|
168
|
+
- lib/diary-ruby/models/entry.rb
|
150
169
|
- lib/diary-ruby/parser.rb
|
151
170
|
- lib/diary-ruby/server/public/script.js
|
152
171
|
- lib/diary-ruby/server/public/style.css
|
data/lib/diary-ruby/entry.rb
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
TEMPLATE = "# last entry posted at %{last_update_at}
|
2
|
-
|
3
|
-
DAY %{day}
|
4
|
-
TIME %{time}
|
5
|
-
TAGS %{tags}
|
6
|
-
TITLE %{title}
|
7
|
-
|
8
|
-
---
|
9
|
-
|
10
|
-
%{text}
|
11
|
-
"
|
12
|
-
|
13
|
-
require 'rdiscount'
|
14
|
-
|
15
|
-
module Diary
|
16
|
-
class Entry
|
17
|
-
attr_accessor :version, :day, :time, :tags, :text, :title, :key
|
18
|
-
|
19
|
-
CURRENT_VERSION = 1
|
20
|
-
|
21
|
-
def self.from_store(record)
|
22
|
-
if record[:version] == 1
|
23
|
-
self.new(
|
24
|
-
record[:version],
|
25
|
-
day: record[:day],
|
26
|
-
time: record[:time],
|
27
|
-
tags: record[:tags],
|
28
|
-
text: record[:text],
|
29
|
-
title: record[:title],
|
30
|
-
key: record[:key],
|
31
|
-
)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.keygen(day, time)
|
36
|
-
"%s-%s" % [day, time]
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.generate(options={}, store)
|
40
|
-
options[:last_update_at] = store.read(:last_update_at)
|
41
|
-
|
42
|
-
# convert Arrays to dumb CSV
|
43
|
-
options.each do |(k, v)|
|
44
|
-
if v.is_a?(Array)
|
45
|
-
options[k] = v.join(', ')
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
TEMPLATE % options
|
50
|
-
end
|
51
|
-
|
52
|
-
def initialize(version, options={})
|
53
|
-
@version = version || CURRENT_VERSION
|
54
|
-
|
55
|
-
@day = options[:day]
|
56
|
-
@time = options[:time]
|
57
|
-
@tags = options[:tags] || []
|
58
|
-
@text = options[:text]
|
59
|
-
@title = options[:title]
|
60
|
-
|
61
|
-
if options[:key].nil?
|
62
|
-
@key = Entry.keygen(day, time)
|
63
|
-
else
|
64
|
-
@key = options[:key]
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def formatted_text
|
69
|
-
RDiscount.new(text).to_html
|
70
|
-
end
|
71
|
-
|
72
|
-
def to_hash
|
73
|
-
{
|
74
|
-
version: version,
|
75
|
-
day: day,
|
76
|
-
time: time,
|
77
|
-
tags: tags,
|
78
|
-
text: text,
|
79
|
-
title: '',
|
80
|
-
key: key,
|
81
|
-
}
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|