days 0.0.1.earlier → 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.
Files changed (76) hide show
  1. data/.gitignore +6 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +9 -0
  4. data/README.md +2 -0
  5. data/app/images/glyphicons-halflings-white.png +0 -0
  6. data/app/images/glyphicons-halflings.png +0 -0
  7. data/app/javascripts/bootstrap.js +2159 -0
  8. data/app/javascripts/bootstrap.min.js +6 -0
  9. data/app/javascripts/jquery-1.8.3.min.js +2 -0
  10. data/app/stylesheets/admin/login.scss +26 -0
  11. data/app/stylesheets/admin.scss +28 -0
  12. data/app/stylesheets/bootstrap-responsive.css +1092 -0
  13. data/app/stylesheets/bootstrap-responsive.min.css +9 -0
  14. data/app/stylesheets/bootstrap.css +6039 -0
  15. data/app/stylesheets/bootstrap.min.css +9 -0
  16. data/app/stylesheets/style.scss +132 -0
  17. data/app/views/admin/categories.haml +53 -0
  18. data/app/views/admin/entries/form.haml +55 -0
  19. data/app/views/admin/entries/index.haml +31 -0
  20. data/app/views/admin/index.haml +0 -0
  21. data/app/views/admin/login.haml +19 -0
  22. data/app/views/admin/setup.haml +22 -0
  23. data/app/views/admin/users/form.haml +25 -0
  24. data/app/views/admin/users/index.haml +23 -0
  25. data/app/views/admin.haml +37 -0
  26. data/app/views/entries.haml +4 -0
  27. data/app/views/entry.haml +32 -0
  28. data/app/views/layout.haml +32 -0
  29. data/bin/days +5 -0
  30. data/bootstrap.sh +41 -0
  31. data/days.gemspec +25 -2
  32. data/lib/days/app/admin/categories.rb +43 -0
  33. data/lib/days/app/admin/entries.rb +72 -0
  34. data/lib/days/app/admin/session.rb +28 -0
  35. data/lib/days/app/admin/setup.rb +20 -0
  36. data/lib/days/app/admin/users.rb +59 -0
  37. data/lib/days/app/admin.rb +11 -0
  38. data/lib/days/app/entries.rb +84 -0
  39. data/lib/days/app.rb +110 -0
  40. data/lib/days/command.rb +158 -0
  41. data/lib/days/config.rb +42 -0
  42. data/lib/days/helpers.rb +101 -0
  43. data/lib/days/migrate/20121221000000_create_entries.rb +18 -0
  44. data/lib/days/migrate/20121221001000_create_users.rb +12 -0
  45. data/lib/days/migrate/20121221002000_create_categories.rb +17 -0
  46. data/lib/days/migrator.rb +33 -0
  47. data/lib/days/models/category.rb +12 -0
  48. data/lib/days/models/entry.rb +63 -0
  49. data/lib/days/models/user.rb +14 -0
  50. data/lib/days/models.rb +7 -0
  51. data/lib/days/version.rb +1 -1
  52. data/lib/days.rb +3 -1
  53. data/scripts/lokka_export.rb +45 -0
  54. data/skeleton/days/Gemfile +11 -0
  55. data/skeleton/days/config.ru +6 -0
  56. data/skeleton/days/config.yml +33 -0
  57. data/skeleton/days/db/.gitkeep +0 -0
  58. data/spec/controllers/admin/categories_spec.rb +100 -0
  59. data/spec/controllers/admin/entries_spec.rb +185 -0
  60. data/spec/controllers/admin/session_spec.rb +112 -0
  61. data/spec/controllers/admin/setup_spec.rb +85 -0
  62. data/spec/controllers/admin/users_spec.rb +163 -0
  63. data/spec/controllers/entries_spec.rb +129 -0
  64. data/spec/environment/Gemfile +11 -0
  65. data/spec/environment/config.ru +6 -0
  66. data/spec/environment/config.yml +32 -0
  67. data/spec/environment/db/.gitkeep +0 -0
  68. data/spec/fixtures/categories.yml +5 -0
  69. data/spec/fixtures/entries.yml +25 -0
  70. data/spec/fixtures/users.yml +6 -0
  71. data/spec/helpers_spec.rb +117 -0
  72. data/spec/models/entry_spec.rb +238 -0
  73. data/spec/shared/admin.rb +8 -0
  74. data/spec/spec_helper.rb +134 -0
  75. data/tasks +9 -0
  76. metadata +353 -9
@@ -0,0 +1,129 @@
1
+ require 'spec_helper'
2
+
3
+ describe Days::App, type: :controller do
4
+ describe "entry page" do
5
+ fixtures :categories, :entries
6
+ let(:path) { '/2012/12/slug' }
7
+ subject { get path, {}, {} }
8
+
9
+ before do
10
+ Days::App.any_instance.should_receive(:lookup_entry).at_least(:once).with(path).and_return(result)
11
+ end
12
+
13
+ let(:result) { entries(:entry_one) }
14
+
15
+ it { should be_ok }
16
+
17
+ it "renders entry.haml" do
18
+ render[:data].should == :entry
19
+ render[:options][:locals][:entry].should == result
20
+ end
21
+
22
+ context "when lookup_entry returned nil" do
23
+ let(:result) { nil }
24
+
25
+ it { should be_not_found }
26
+ end
27
+
28
+ context "when lookup_entry returned Array" do
29
+ let(:result) { [entries(:entry_one), entries(:entry_two)] }
30
+
31
+ it { should be_ok }
32
+
33
+ it "renders entry.haml" do
34
+ render[:data].should == :entries
35
+ render[:ivars][:@entries].should == result
36
+ end
37
+ end
38
+ end
39
+
40
+ describe "GET /:year/:month" do
41
+ fixtures :categories, :entries
42
+ subject { get '/2012/12', params }
43
+ let(:params) { {} }
44
+
45
+ it { should be_ok }
46
+
47
+ it "renders entries" do
48
+ render[:data].should == :entries
49
+
50
+ base = Time.local(2012, 12, 1, 0, 0, 0)
51
+ range = (base.beginning_of_month .. base.end_of_month)
52
+ render[:ivars][:@entries].to_a.should == Days::Entry.where(published_at: range).published.to_a
53
+ render[:ivars][:@entries].current_page.should == 1
54
+ end
55
+
56
+ context "with page param" do
57
+ let(:params) { {page: 2} }
58
+
59
+ it "renders entries" do
60
+ render[:data].should == :entries
61
+ render[:ivars][:@entries].current_page.should == 2
62
+ end
63
+ end
64
+
65
+ context "with character" do
66
+ it "returns not found" do
67
+ get('/aaa/01').should be_not_found
68
+ get('/2012/bbb').should be_not_found
69
+ get('/aaa/bbb').should be_not_found
70
+ end
71
+ end
72
+ end
73
+
74
+ describe "GET /category/:name" do
75
+ fixtures :categories, :entries
76
+ subject { get '/category/daily', params }
77
+ let(:params) { {} }
78
+
79
+ it { should be_ok }
80
+
81
+ it "renders entries" do
82
+ render[:data].should == :entries
83
+
84
+ render[:ivars][:@entries].to_a.should == categories(:daily).entries.published.to_a
85
+ render[:ivars][:@entries].current_page.should == 1
86
+ end
87
+
88
+ context "with page param" do
89
+ let(:params) { {page: 2} }
90
+
91
+ it "renders entries" do
92
+ render[:data].should == :entries
93
+ render[:ivars][:@entries].current_page.should == 2
94
+ end
95
+ end
96
+ end
97
+
98
+ describe "GET /" do
99
+ fixtures :categories, :entries
100
+ subject { get '/', params }
101
+ let(:params) { {} }
102
+
103
+ it "renders entries" do
104
+ render[:data].should == :entries
105
+ render[:ivars][:@entries].current_page.should == 1
106
+ render[:ivars][:@entries].should_not include(entries(:entry_draft))
107
+ end
108
+
109
+ context "with page param" do
110
+ let(:params) { {page: 2} }
111
+
112
+ it "renders entries" do
113
+ render[:data].should == :entries
114
+ render[:ivars][:@entries].current_page.should == 2
115
+ end
116
+ end
117
+ end
118
+
119
+ describe "GET /feed" do
120
+ fixtures :categories, :entries
121
+ subject { get '/feed' }
122
+
123
+ it { should be_ok }
124
+
125
+ specify do
126
+ subject.content_type.should == 'application/atom+xml'
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,11 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "days"
4
+
5
+ group :production do
6
+ # gem "mysql2"
7
+ end
8
+
9
+ group :development do
10
+ gem "sqlite3"
11
+ end
@@ -0,0 +1,6 @@
1
+ require 'bundler'
2
+ require 'days'
3
+ Bundler.require(:default, Days::App.environment)
4
+
5
+ Days::App.set :config, Days::Config.new("#{File.dirname(__FILE__)}/config.yml")
6
+ run Days::App.rack
@@ -0,0 +1,32 @@
1
+ defaults: &defaults
2
+ base_path: "/"
3
+ permalink: "/{year}/{month}/{slug}"
4
+ title: "My Blog"
5
+
6
+ development:
7
+ <<: *defaults
8
+ database:
9
+ adapter: sqlite3
10
+ database: db/development.sqlite3
11
+ pool: 5
12
+ timeout: 5000
13
+
14
+ test:
15
+ <<: *defaults
16
+ database:
17
+ adapter: sqlite3
18
+ database: db/test.sqlite3
19
+ pool: 5
20
+ timeout: 5000
21
+
22
+ production:
23
+ <<: *defaults
24
+ database:
25
+ adapter: mysql2
26
+ encoding: utf8
27
+ database: days_production
28
+ pool: 5
29
+ username: foo
30
+ password: bar
31
+ socket: /tmp/mysql.sock
32
+
File without changes
@@ -0,0 +1,5 @@
1
+ daily:
2
+ name: "daily"
3
+
4
+ rainy:
5
+ name: "rainy"
@@ -0,0 +1,25 @@
1
+ entry_one:
2
+ title: "Today was"
3
+ body: "a rainy day"
4
+ rendered: "<p>a rainy day</p>"
5
+ slug: "today-is-a-rainy-day"
6
+ published_at: 2012-11-15 00:00:00
7
+ user: blogger
8
+ categories: daily, rainy
9
+
10
+ entry_two:
11
+ title: "Today was"
12
+ body: "a beautiful day"
13
+ rendered: "<p>a beautiful day</p>"
14
+ slug: "today-is-a-beautiful-day"
15
+ published_at: 2012-12-25 00:00:00
16
+ user: blogger
17
+ categories: daily
18
+
19
+ entry_draft:
20
+ title: "Today was"
21
+ body: "a draft"
22
+ rendered: "<p>a draft</p>"
23
+ slug: "draft"
24
+ user: blogger
25
+ categories: daily
@@ -0,0 +1,6 @@
1
+ blogger:
2
+ name: "blogger"
3
+ login_name: "admin"
4
+ writer:
5
+ name: "writer"
6
+ login_name: "john"
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+ require 'days/helpers'
3
+
4
+ describe Days::Helpers do
5
+ include described_class
6
+
7
+ let(:config) do
8
+ double.tap do |_|
9
+ _.stub(permalink: permalink)
10
+ end
11
+ end
12
+
13
+ let(:entry_a) do
14
+ Days::Entry.where(slug: "sushi").first
15
+ end
16
+
17
+ let(:entry_b) do
18
+ Days::Entry.where(slug: "neko").first
19
+ end
20
+
21
+ before(:all) do
22
+ Days::Entry.destroy_all
23
+ entry_a = Days::Entry.create(
24
+ title: 'Sushi', body: 'Sushi!',
25
+ published_at: Time.local(2012, 12, 31, 9, 24, 42),
26
+ slug: "sushi"
27
+ )
28
+ entry_b = Days::Entry.create(
29
+ title: 'Neko', body: 'Meow!',
30
+ published_at: Time.local(2012, 12, 31, 19, 14, 32),
31
+ slug: "neko"
32
+ )
33
+
34
+ end
35
+
36
+ after(:all) do
37
+ Days::Entry.destroy_all
38
+ end
39
+
40
+ let(:permalink) { "/{year}/{month}/{day}/{hour}/{minute}/{second}/{id}-{slug}" }
41
+
42
+ describe ".#entry_path" do
43
+ subject { entry_path(entry_a) }
44
+
45
+ it { should == "/2012/12/31/09/24/42/#{entry_a.id}-sushi" }
46
+
47
+ context "with invalid format (unbalanced parenthesis)" do
48
+ let(:permalink) { "/{year}/{month}/{day/{hour}/{minute}/{second}/{id}-{slug" }
49
+
50
+ it { should == "/2012/12/{day/09/24/42/#{entry_a.id}-{slug" }
51
+ end
52
+
53
+ context "with invalid format (invalid tag name)" do
54
+ let(:permalink) { "/{foo}-{slug}" }
55
+
56
+ it { should == "/-sushi" }
57
+ end
58
+
59
+ context "with not published entry" do
60
+ before do
61
+ entry_a.stub(published?: false)
62
+ end
63
+
64
+ it { should be_nil }
65
+ end
66
+ end
67
+
68
+ describe ".#lookup_entry" do
69
+ # Regexp.compile(Regexp.escape(a).gsub(/\\{(\w+?)\\}/) { "(?<#{$1}>.+?)" } + "$")
70
+ subject { lookup_entry(path) }
71
+
72
+ let(:path) { "/2012/12/31/09/24/42/#{entry_a.id}-sushi" }
73
+
74
+ it { should == entry_a }
75
+
76
+ context "with invalid link" do
77
+ let(:path) { "/2012/12/31/09/24//#{entry_a.id}-sushi" }
78
+
79
+ it { should be_nil }
80
+ end
81
+
82
+ context "with another format" do
83
+ let(:permalink) { "/{year}/{month}/{day}/{slug}" }
84
+ let(:path) { "/2012/12/31/sushi" }
85
+
86
+ it { should == entry_a }
87
+ end
88
+
89
+ context "with invalid format (unbalanced parenthesis)" do
90
+ let(:permalink) { "/{year}/{month}/{day/{slug}" }
91
+ let(:path) { "/2012/12/31/sushi" }
92
+
93
+ it { should be_nil }
94
+ end
95
+
96
+ context "with invalid format (invalid tag name)" do
97
+ let(:permalink) { "/{year}/{month}/{sushi}/{slug}" }
98
+ let(:path) { "/2012/12/31/sushi" }
99
+ end
100
+
101
+ context "when query has multiple result" do
102
+ let(:permalink) { "/{year}/{month}/{day}" }
103
+ let(:path) { "/2012/12/31" }
104
+
105
+ it { should == [entry_a, entry_b] }
106
+ end
107
+
108
+ context "when entry has not published" do
109
+ before do
110
+ entry_a.draft = true
111
+ entry_a.save
112
+ end
113
+
114
+ it { should be_nil }
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,238 @@
1
+ require 'spec_helper'
2
+
3
+ describe Days::Entry do
4
+ before do
5
+ described_class.destroy_all
6
+ end
7
+
8
+ after do
9
+ described_class.destroy_all
10
+ end
11
+
12
+ subject do
13
+ described_class.new(title: 'title', body: 'a')
14
+ end
15
+
16
+
17
+ describe "rendering" do
18
+ subject do
19
+ described_class.new(title: 'title', body: 'a')
20
+ end
21
+
22
+ before do
23
+ Redcarpet::Markdown.any_instance.stub(render: "(sun)")
24
+ subject.save
25
+ end
26
+
27
+ it { should be_valid }
28
+
29
+ specify do
30
+ subject.rendered.should == "(sun)"
31
+ end
32
+ end
33
+
34
+ describe "slug" do
35
+ before do
36
+ String.any_instance.stub(to_url: "slug")
37
+ subject.save
38
+ end
39
+
40
+ it { should be_valid }
41
+
42
+ it "generates slug"do
43
+ subject.slug.should == 'slug'
44
+ end
45
+
46
+ context "with empty slug" do
47
+ before do
48
+ String.any_instance.stub(to_url: "slug")
49
+ subject.slug = ""
50
+ subject.save
51
+ end
52
+
53
+ it { should be_valid }
54
+
55
+ it "generates slug"do
56
+ subject.slug.should == 'slug'
57
+ end
58
+ end
59
+ end
60
+
61
+ describe "#published?" do
62
+ context "with nullified published_at" do
63
+ before do
64
+ subject.published_at = nil
65
+ end
66
+
67
+ specify do
68
+ subject.should_not be_published
69
+ end
70
+
71
+ specify do
72
+ subject.should_not be_scheduled
73
+ end
74
+ end
75
+
76
+ context "when after published_at" do
77
+ before do
78
+ base = Time.now
79
+ Time.stub(now: base)
80
+ subject.published_at = base - 1
81
+ end
82
+
83
+ specify do
84
+ subject.should be_published
85
+ end
86
+
87
+ specify do
88
+ subject.should_not be_scheduled
89
+ end
90
+ end
91
+
92
+ context "when just published_at" do
93
+ before do
94
+ base = Time.now
95
+ Time.stub(now: base)
96
+ subject.published_at = base
97
+ end
98
+
99
+ specify do
100
+ subject.should be_published
101
+ end
102
+
103
+ specify do
104
+ subject.should_not be_scheduled
105
+ end
106
+ end
107
+
108
+ context "when before published_at" do
109
+ before do
110
+ base = Time.now
111
+ Time.stub(now: base)
112
+ subject.published_at = base + 1
113
+ end
114
+
115
+ specify do
116
+ subject.should_not be_published
117
+ end
118
+
119
+ specify do
120
+ subject.should be_scheduled
121
+ end
122
+ end
123
+ end
124
+
125
+ describe "published scope" do
126
+ before do
127
+ described_class.create(title: 'A', body: 'a', draft: true)
128
+ described_class.create(title: 'B', body: 'b', published_at: Time.local(2012,12,30,11,0,0))
129
+ described_class.create(title: 'C', body: 'c', published_at: Time.local(2012,12,30, 9,0,0))
130
+ described_class.create(title: 'D', body: 'd', published_at: Time.local(2013, 1, 1, 9,0,0))
131
+ Time.stub(now: Time.local(2012,12,30, 14,0,0))
132
+ end
133
+
134
+ subject do
135
+ described_class.published
136
+ end
137
+
138
+ it "orders by published_at" do
139
+ subject.map(&:title).should == ['B', 'C']
140
+ end
141
+
142
+ it "rejects not published entries" do
143
+ subject.map(&:title).should_not include('A')
144
+ subject.map(&:title).should_not include('D')
145
+ end
146
+
147
+ context "all published" do
148
+ before do
149
+ Time.stub(now: Time.local(2013,1,1,14,0,0))
150
+ end
151
+
152
+ it "orders by published_at" do
153
+ subject.map(&:title).should == ['D', 'B', 'C']
154
+ end
155
+
156
+ it "rejects not published entries" do
157
+ subject.map(&:title).should_not include('A')
158
+ subject.map(&:title).should include('D')
159
+ end
160
+ end
161
+ end
162
+
163
+ describe "draft attribute" do
164
+ context "with true" do
165
+ before do
166
+ subject.published_at = Time.now
167
+ subject.draft = true
168
+ subject.valid?
169
+ end
170
+
171
+ it "nullifies published_at" do
172
+ subject.published_at.should be_nil
173
+ end
174
+ end
175
+
176
+ context "with false" do
177
+ let!(:base) { Time.now }
178
+
179
+ before do
180
+ subject.draft = false
181
+ end
182
+
183
+ context "when published_at is nil" do
184
+ before do
185
+ Time.stub(now: base)
186
+ subject.published_at = nil
187
+ subject.valid?
188
+ end
189
+
190
+ it "fills published_at" do
191
+ subject.published_at.should == base
192
+ end
193
+ end
194
+
195
+ context "when published_at is not nil" do
196
+ before do
197
+ Time.stub(now: base + 1)
198
+ subject.published_at = base
199
+ subject.valid?
200
+ end
201
+
202
+ it "keeps published_at" do
203
+ subject.published_at.should == base
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ describe "#short_rendered" do
210
+ subject do
211
+ described_class.new(title: 'title', body: body)
212
+ end
213
+
214
+ before do
215
+ subject.valid?
216
+ end
217
+
218
+ let(:body) { "a\n\n<!--more-->\n\nb" }
219
+
220
+ it "deletes after <!--more-->" do
221
+ subject.short_rendered.should == "<p>a</p>\n\n"
222
+ end
223
+
224
+ context "without <!--more--> in body" do
225
+ let(:body) { "a\n\nb" }
226
+
227
+ it "returns entire rendered body" do
228
+ subject.short_rendered.should == subject.rendered
229
+ end
230
+ end
231
+
232
+ context "with block" do
233
+ it "replaces by block evaluation result" do
234
+ subject.short_rendered { "hi!" }.should == "<p>a</p>\n\nhi!"
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,8 @@
1
+ shared_examples "an admin page" do
2
+ before { env.delete 'rack.session' }
3
+
4
+ it "redirects to /admin/login" do
5
+ subject.should be_redirect
6
+ URI.parse(subject.location).path.should == '/admin/login'
7
+ end
8
+ end