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,158 @@
1
+ require 'thor'
2
+ require 'fileutils'
3
+ require_relative 'config'
4
+ require_relative 'app'
5
+ require_relative 'migrator'
6
+
7
+ module Days
8
+ class Command < Thor
9
+ SKELETON_PATH = File.expand_path(File.join(__FILE__, '..', '..', '..', 'skeleton', 'days'))
10
+ desc "init [DIR]", "Initialize days.gem world in the directory (default = ./)"
11
+ def init(dir = ".")
12
+ puts "Initializing new Days environment on #{File.expand_path dir}"
13
+ FileUtils.cp_r Dir["#{SKELETON_PATH}/*"], "#{dir}/"
14
+ end
15
+
16
+ desc "init_theme [DIR]", "Generate template of theme (views) to DIR (default = ./)"
17
+ def init_theme(dir = ".")
18
+ if File.exists?(File.join(dir, 'views')) || File.exists?(File.join(dir, 'stylesheets'))
19
+ puts "This will override the following:"
20
+ puts "* #{dir}/views/entries.haml"
21
+ puts "* #{dir}/views/entry.haml"
22
+ puts "* #{dir}/views/layout.haml"
23
+ puts "* #{dir}/stylesheets/style.scss"
24
+
25
+ print "Continue (y/n)? "
26
+
27
+ while _ = $stdin.gets
28
+ case _
29
+ when /^y|yes$/
30
+ break
31
+ when /^n|no$/
32
+ puts 'Cancelled.'
33
+ return
34
+ else
35
+ print "Please answer in 'yes' or 'no' or 'y' or 'n': "
36
+ end
37
+ end
38
+ end
39
+
40
+ require 'fileutils'
41
+
42
+ root = File.expand_path(File.join(__FILE__, '..', '..', '..', 'app'))
43
+ FileUtils.mkdir_p File.join(dir, 'views')
44
+ FileUtils.mkdir_p File.join(dir, 'stylesheets')
45
+
46
+ %w(entries.haml entry.haml layout.haml).each do |file|
47
+ FileUtils.cp(File.join(root, 'views', file),
48
+ File.join(dir, 'views', file))
49
+ end
50
+
51
+ FileUtils.cp(File.join(root, 'stylesheets', 'style.scss'),
52
+ File.join(dir, 'stylesheets', 'style.scss'))
53
+ end
54
+
55
+ desc "server", "Starts the server"
56
+ method_option :config, :type => :string, :aliases => "-c"
57
+ method_option :port, :type => :numeric, :aliases => "-p", :default => 3162
58
+ method_option :bind, :type => :string, :aliases => "-b", :default => nil
59
+ method_option :environment, :type => :string, :aliases => "-e", :default => "development"
60
+ method_option :pid, :type => :string, :aliases => "-c"
61
+ def server
62
+ set_env
63
+ App.config = config
64
+ rack_options = {
65
+ app: App.rack,
66
+ Port: options[:port],
67
+ Host: options[:bind],
68
+ daemonize: options[:daemonize],
69
+ environment: options[:environment],
70
+ server: options[:server],
71
+ }
72
+ rack_options.merge!(pid: File.expand_path(options[:pid])) if options[:pid]
73
+ Rack::Server.start(rack_options)
74
+ end
75
+
76
+ # desc "precompile", "Precompile the assets for production"
77
+ # method_option :config, :type => :string, :aliases => "-c"
78
+ # def precompile
79
+ # end
80
+
81
+ desc "migrate [ENV]", "Run database migration for environment (default = development)"
82
+ method_option :config, :type => :string, :aliases => "-c"
83
+ method_option :version, :type => :numeric, :aliases => "-v", :default => nil
84
+ method_option :scope, :type => :string, :aliases => "-s"
85
+ method_option :verbose, :type => :boolean, :aliases => "-V", :default => true
86
+ def migrate(env = "development")
87
+ set_env env
88
+ Days::Migrator.start(config, options)
89
+ end
90
+
91
+ desc "console [ENV]", "Start console using pry (default = development)"
92
+ method_option :config, :type => :string, :aliases => "-c"
93
+ def console(env = "development")
94
+ set_env env
95
+ require 'pry'
96
+ require_relative 'models'
97
+ config.establish_db_connection()
98
+ Pry.start(binding)
99
+ end
100
+
101
+ desc "import FILE", "Import entries from file."
102
+ method_option :config, :type => :string, :aliases => "-c"
103
+ method_option :environment, :type => :string, :aliases => "-e", :default => "development"
104
+ def import(file)
105
+ set_env
106
+ config.establish_db_connection()
107
+ require 'json'
108
+ users = {}
109
+ categories = {}
110
+ open(file, 'r') do |io|
111
+ io.readlines.each do |line|
112
+ line = JSON.parse(line)
113
+
114
+ attributes ={}
115
+ if Entry.where(id: line['id']).count.zero?
116
+ attributes[:id] = line['id']
117
+ end
118
+
119
+ if line['user']
120
+ if users.has_key?(line['user'])
121
+ user = users[line['user']]
122
+ else
123
+ user = users[line['user']] = User.where(login_name: line['user']).first
124
+ end
125
+
126
+
127
+ attributes[:user] = user if user
128
+ end
129
+
130
+ if line['category']
131
+ attributes[:categories] = line['category'].map do |category_name|
132
+ categories[category_name] ||= Category.find_or_create_by_name!(category_name)
133
+ end
134
+ end
135
+
136
+ attributes[:slug] = line['slug'] if line['slug']
137
+ attributes[:title] = line['title'] if line['title']
138
+ attributes[:body] = line['body'] if line['body']
139
+ attributes[:published_at] = line['published_at'] if line['published_at']
140
+ attributes[:draft] = line['draft'] if line['draft']
141
+
142
+ p attributes[:title]
143
+ Entry.create!(attributes)
144
+ end
145
+ end
146
+ end
147
+
148
+ private
149
+
150
+ def set_env(env = nil)
151
+ App.environment = env || options[:environment] || ENV["RACK_ENV"] || :development
152
+ end
153
+
154
+ def config
155
+ @_days_config ||= Config.new(options[:config] || "./config.yml")
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,42 @@
1
+ require_relative 'app'
2
+ require 'active_record'
3
+ require 'logger'
4
+ require 'settingslogic'
5
+
6
+ module Days
7
+ class Config < Settingslogic
8
+ namespace App.environment.to_s
9
+
10
+ def initialize(hash_or_file = {}, section = nil)
11
+ super
12
+ if section.nil?
13
+ if String === hash_or_file
14
+ self[:root] = File.dirname(hash_or_file)
15
+ else
16
+ self[:root] = "."
17
+ end
18
+
19
+ self['database'].tap do |hash|
20
+ next unless hash
21
+ if hash['adapter'] == 'sqlite3' && /^\// !~ hash['database']
22
+ hash['database'] = File.join(self.root, hash['database'])
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ def establish_db_connection(force=false)
29
+ if !self.has_key?(:activerecord_log) || self.activerecord_log == true
30
+ ActiveRecord::Base.logger = Logger.new($stdout)
31
+ end
32
+
33
+ begin
34
+ raise ActiveRecord::ConnectionNotEstablished if force
35
+ return ActiveRecord::Base.connection
36
+ rescue ActiveRecord::ConnectionNotEstablished
37
+ ActiveRecord::Base.establish_connection(self['database'] ? Hash[self.database] : ENV["DATABASE_URL"])
38
+ retry
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,101 @@
1
+ require 'rack/csrf'
2
+
3
+ module Days
4
+ module Helpers
5
+ def config
6
+ Days::App.config
7
+ end
8
+
9
+ def logged_in?
10
+ !!session[:user_id]
11
+ end
12
+
13
+ def current_user
14
+ @current_user ||= session[:user_id] ? User.where(id: session[:user_id]).first : nil
15
+ end
16
+
17
+ def csrf_token
18
+ Rack::Csrf.csrf_token(env)
19
+ end
20
+
21
+ def csrf_tag
22
+ Rack::Csrf.csrf_tag(env)
23
+ end
24
+
25
+ def entry_path(entry)
26
+ return nil unless entry.published?
27
+
28
+ published_at = entry.published_at
29
+ hash = {
30
+ year: published_at.year.to_s.rjust(2, '0'),
31
+ month: published_at.month.to_s.rjust(2, '0'),
32
+ day: published_at.day.to_s.rjust(2, '0'),
33
+ hour: published_at.hour.to_s.rjust(2, '0'),
34
+ minute: published_at.min.to_s.rjust(2, '0'),
35
+ second: published_at.sec.to_s.rjust(2, '0'),
36
+ slug: entry.slug, id: entry.id
37
+ }
38
+ config.permalink.gsub(/{(\w+?)}/) { hash[$1.to_sym] }
39
+ end
40
+
41
+ def lookup_entry(path)
42
+ regexp = Regexp.compile(Regexp.escape(config.permalink).gsub(/\\{(\w+?)\\}/) { "(?<#{$1}>.+?)" } + "$")
43
+ m = regexp.match(path)
44
+ return nil unless m
45
+
46
+ match = m.names.inject({}) do |hash, k|
47
+ hash[k.to_sym] = m[k]
48
+ hash
49
+ end
50
+
51
+
52
+ if match[:id] || match[:slug]
53
+ if match[:id]
54
+ query = Entry.where(id: match[:id])
55
+ else
56
+ query = Entry.where(slug: match[:slug])
57
+ end
58
+
59
+ entry = query.first
60
+ return nil unless entry
61
+ published_at = entry.published_at
62
+ return nil unless published_at
63
+
64
+ return nil if match[:slug] && match[:slug] != entry.slug
65
+ return nil if match[:id] && match[:id].to_i != entry.id
66
+ return nil if match[:year] && match[:year].to_i != published_at.year
67
+ return nil if match[:month] && match[:month].to_i != published_at.month
68
+ return nil if match[:day] && match[:day].to_i != published_at.day
69
+ return nil if match[:hour] && match[:hour].to_i != published_at.hour
70
+ return nil if match[:minute] && match[:minute].to_i != published_at.min
71
+ return nil if match[:second] && match[:second].to_i != published_at.sec
72
+
73
+ return entry
74
+ else
75
+ match_time = {}.tap do |h|
76
+ [:year, :month, :day, :hour, :minute, :second].each do |k|
77
+ h[k] = match[k] && match[k].to_i
78
+ end
79
+ end
80
+ range_begin = Time.local(
81
+ match_time[:year], match_time[:month] || 1, match_time[:day] || 1,
82
+ match_time[:hour] || 0, match_time[:minute] || 0, match_time[:second] || 0
83
+ )
84
+ range_end = Time.local(
85
+ match_time[:year], match_time[:month] || 12, match_time[:day] || 31,
86
+ match_time[:hour] || 23, match_time[:minute] || 59, match_time[:second] || 59
87
+ )
88
+
89
+ query = Entry.where(published_at: (range_begin .. range_end))
90
+
91
+ if query.count.zero?
92
+ return nil
93
+ elsif query.count == 1
94
+ return query.first
95
+ else
96
+ return query
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,18 @@
1
+ class CreateEntries < ActiveRecord::Migration
2
+ def change
3
+ create_table :entries do |t|
4
+ t.string :title
5
+ t.text :body
6
+ t.text :rendered
7
+ t.datetime :published_at
8
+ t.integer :user_id
9
+ t.string :slug
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :entries, :published_at
15
+ add_index :entries, :user_id
16
+ add_index :entries, :slug
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ def change
3
+ create_table :users do |t|
4
+ t.string :login_name
5
+ t.binary :password_digest
6
+
7
+ t.string :name
8
+ end
9
+
10
+ add_index :users, :login_name
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ class CreateCategories < ActiveRecord::Migration
2
+ def change
3
+ create_table :categories do |t|
4
+ t.string :name
5
+ end
6
+
7
+ create_table :categories_entries do |t|
8
+ t.integer :category_id
9
+ t.integer :entry_id
10
+ end
11
+
12
+ add_index :categories, :name
13
+
14
+ add_index :categories_entries, :entry_id
15
+ add_index :categories_entries, :category_id
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ module Days
2
+ module Migrator
3
+ def self.start(config, options = {})
4
+ require 'active_record'
5
+ require 'active_support/core_ext/class/attribute_accessors.rb'
6
+ require 'active_record/schema_dumper'
7
+
8
+ config.establish_db_connection()
9
+ orig_logger = ActiveRecord::Base.logger
10
+ begin
11
+ ActiveRecord::Base.logger = nil unless options[:show_sql]
12
+ ActiveRecord::Migration.verbose = options[:verbose]
13
+
14
+ migration_paths = [
15
+ config[:migration_path] || "#{config.root}/db/migrate",
16
+ File.expand_path(File.join(__FILE__, '..', 'migrate'))
17
+ ]
18
+ ActiveRecord::Migrator.migrate(migration_paths, options[:version]) do |migration|
19
+ options[:scope].blank? || (options[:scope] == migration.scope)
20
+ end
21
+
22
+ schema_file = config[:schema] || "#{config.root}/db/schema.rb"
23
+ File.open(schema_file, "w:utf-8") do |io|
24
+ ActiveRecord::SchemaDumper.dump ActiveRecord::Base.connection, io
25
+ end
26
+
27
+ self
28
+ ensure
29
+ ActiveRecord::Base.logger = orig_logger
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,12 @@
1
+ require 'active_record'
2
+
3
+ module Days
4
+ class Category < ActiveRecord::Base
5
+ attr_accessible :name
6
+
7
+ validates_presence_of :name
8
+ validates_uniqueness_of :name
9
+
10
+ has_and_belongs_to_many :entries, class_name: 'Days::Entry'
11
+ end
12
+ end
@@ -0,0 +1,63 @@
1
+ require 'active_record'
2
+ require 'stringex'
3
+ require 'redcarpet'
4
+
5
+ module Days
6
+ class Entry < ActiveRecord::Base
7
+ attr_accessible :title, :body, :slug, :published_at, :categories, :user, :draft
8
+
9
+ validates_uniqueness_of :slug
10
+ validates_presence_of :title, :body, :rendered, :slug
11
+
12
+ belongs_to :user, class_name: 'Days::User'
13
+ has_and_belongs_to_many :categories, class_name: 'Days::Category'
14
+
15
+ scope :published, -> do
16
+ includes(:categories).
17
+ where('published_at IS NOT NULL AND published_at <= ?', Time.now).
18
+ order('published_at DESC')
19
+ end
20
+
21
+ def draft=(x)
22
+ if x.present?
23
+ @draft = true
24
+ else
25
+ @draft = false
26
+ end
27
+ end
28
+
29
+ def draft?
30
+ @draft
31
+ end
32
+ alias draft draft?
33
+
34
+ def published?
35
+ self.published_at && self.published_at <= Time.now
36
+ end
37
+
38
+ def scheduled?
39
+ self.published_at && Time.now < self.published_at
40
+ end
41
+
42
+ def short_rendered
43
+ self.rendered.gsub(/<!-- *more *-->.+\z/m, block_given? ? yield(self) : '')
44
+ end
45
+
46
+ before_validation do
47
+ if draft?
48
+ self.published_at = nil
49
+ else
50
+ self.published_at ||= Time.now
51
+ end
52
+
53
+ if self.title && (!self.slug || self.slug.empty?)
54
+ self.slug = self.title.to_url
55
+ end
56
+ markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML,
57
+ :autolink => true, :space_after_headers => true,
58
+ :no_intra_emphasis => true, :fenced_code_blocks => true,
59
+ :tables => true, :superscript => true)
60
+ self.rendered = markdown.render(self.body)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,14 @@
1
+ require 'active_record'
2
+
3
+ module Days
4
+ class User < ActiveRecord::Base
5
+ has_secure_password
6
+
7
+ attr_accessible :login_name, :name, :password, :password_confirmation
8
+
9
+ validates_uniqueness_of :login_name
10
+ validates_presence_of :login_name, :name
11
+
12
+ has_many :entries, class_name: 'Days::Entry'
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ %w[
2
+ user
3
+ category
4
+ entry
5
+ ].each do |m|
6
+ require "days/models/#{m}"
7
+ end
data/lib/days/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Days
2
- VERSION = "0.0.1.earlier"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/days.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require "days/version"
2
+ require "days/config"
3
+ require "days/models"
4
+ require "days/app"
2
5
 
3
6
  module Days
4
- # Your code goes here...
5
7
  end
@@ -0,0 +1,45 @@
1
+ require 'active_record'
2
+ require 'json'
3
+
4
+ ActiveRecord::Base.establish_connection ENV["DATABASE_URL"]
5
+
6
+ class User < ActiveRecord::Base
7
+ has_many :entries
8
+ end
9
+
10
+ class Category < ActiveRecord::Base
11
+ has_many :entries
12
+ end
13
+
14
+ class Entry < ActiveRecord::Base
15
+ belongs_to :user
16
+ belongs_to :category
17
+ end
18
+
19
+ class Post < Entry
20
+ end
21
+
22
+ Post.order('id ASC').all.each do |post|
23
+ if 'kramdown' != post.markup
24
+ warn "WARN: #{post.slug} (#{post.id}) is not written in kramdown"
25
+ end
26
+
27
+ hash = {}
28
+ hash[:id] = post.id
29
+ hash[:user] = post.user.name
30
+ hash[:slug] = post.slug
31
+ hash[:title] = post.title
32
+ hash[:body] = post.body
33
+ if post.draft == 1
34
+ hash[:published_at] = post.created_at
35
+ else
36
+ hash[:published_at] = nil
37
+ hash[:draft] = true
38
+ end
39
+ if post.category
40
+ hash[:category] = [post.category.title]
41
+ else
42
+ hash[:category] = []
43
+ end
44
+ puts hash.to_json
45
+ 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,33 @@
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
+ <% unless ENV["DATABASE_URL"] %>
25
+ database:
26
+ adapter: mysql2
27
+ encoding: utf8
28
+ database: days_production
29
+ pool: 5
30
+ username: foo
31
+ password: bar
32
+ socket: /tmp/mysql.sock
33
+ <% end %>
File without changes