cloudpress 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 (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +39 -0
  7. data/Rakefile +30 -0
  8. data/app/assets/cloudpress/cloudpress.css.sass +1 -0
  9. data/app/assets/cloudpress/code/railscasts.css +70 -0
  10. data/app/controllers/cloudpress/application_controller.rb +12 -0
  11. data/app/controllers/cloudpress/archives_controller.rb +34 -0
  12. data/app/controllers/cloudpress/posts_controller.rb +23 -0
  13. data/app/controllers/cloudpress/tags_controller.rb +20 -0
  14. data/app/helpers/cloudpress/renderer_helper.rb +23 -0
  15. data/app/jobs/cloudpress/update_posts_job.rb +9 -0
  16. data/app/models/cloudpress/archive.rb +50 -0
  17. data/app/models/cloudpress/post.rb +65 -0
  18. data/app/views/cloudpress/archives/_archive.html.slim +3 -0
  19. data/app/views/cloudpress/archives/_archives.html.slim +2 -0
  20. data/app/views/cloudpress/archives/_month.html.slim +2 -0
  21. data/app/views/cloudpress/archives/show.html.slim +1 -0
  22. data/app/views/cloudpress/posts/_intro.html.slim +9 -0
  23. data/app/views/cloudpress/posts/_post.html.slim +8 -0
  24. data/app/views/cloudpress/posts/index.html.slim +1 -0
  25. data/app/views/cloudpress/posts/show.html.slim +1 -0
  26. data/app/views/cloudpress/tags/_tag.html.slim +2 -0
  27. data/app/views/cloudpress/tags/_tags.html.slim +2 -0
  28. data/app/views/cloudpress/tags/show.html.slim +1 -0
  29. data/bin/console +14 -0
  30. data/bin/rails +12 -0
  31. data/bin/setup +7 -0
  32. data/cloudpress.gemspec +35 -0
  33. data/config/initializers/tag_extensions.rb +4 -0
  34. data/config/routes.rb +9 -0
  35. data/db/migrate/20150309115639_create_cloudpress_posts.rb +19 -0
  36. data/db/migrate/20150315192521_create_tags_table.rb +30 -0
  37. data/db/migrate/20150315193206_create_friendly_id_slugs.rb +15 -0
  38. data/db/migrate/20150317121651_add_slug_to_tags.rb +6 -0
  39. data/db/migrate/20150317122608_add_taggings_counter_cache.rb +14 -0
  40. data/lib/cloudpress.rb +105 -0
  41. data/lib/cloudpress/dropbox/cleaner.rb +20 -0
  42. data/lib/cloudpress/dropbox/client.rb +15 -0
  43. data/lib/cloudpress/dropbox/connector.rb +24 -0
  44. data/lib/cloudpress/dropbox/directory.rb +57 -0
  45. data/lib/cloudpress/dropbox/file.rb +57 -0
  46. data/lib/cloudpress/engine.rb +10 -0
  47. data/lib/cloudpress/formatters/html_with_pygments.rb +7 -0
  48. data/lib/cloudpress/renderers/archive.rb +18 -0
  49. data/lib/cloudpress/renderers/archives.rb +24 -0
  50. data/lib/cloudpress/renderers/post.rb +39 -0
  51. data/lib/cloudpress/renderers/posts.rb +38 -0
  52. data/lib/cloudpress/renderers/tag.rb +18 -0
  53. data/lib/cloudpress/renderers/tags.rb +24 -0
  54. data/lib/cloudpress/version.rb +3 -0
  55. data/lib/generators/cloudpress/initializer/USAGE +8 -0
  56. data/lib/generators/cloudpress/initializer/initializer_generator.rb +32 -0
  57. metadata +283 -0
@@ -0,0 +1,3 @@
1
+ li
2
+ = link_to renderer.archive.year, archives_path(renderer.archive.year)
3
+ = content_tag(:ul, render(partial: 'cloudpress/archives/month', collection: renderer.archive.months, locals: {renderer: renderer}))
@@ -0,0 +1,2 @@
1
+ section#archive-list
2
+ = yield
@@ -0,0 +1,2 @@
1
+ li
2
+ = link_to month.strftime("%B"), archives_path(renderer.archive.year, month.month)
@@ -0,0 +1 @@
1
+ = render_cloudpress_posts(posts)
@@ -0,0 +1,9 @@
1
+ article id="post-#{renderer.post.id}"
2
+ header
3
+ h1 = link_to renderer.post.title, post_path(renderer.post)
4
+ time = renderer.post.publish_date.to_s(:long)
5
+ = render_cloudpress_tags(renderer.post.tags) if renderer.tags?
6
+ p
7
+ = renderer.post.summary
8
+ footer
9
+ = link_to 'Read more', post_path(renderer.post)
@@ -0,0 +1,8 @@
1
+ article id="post-#{renderer.post.id}"
2
+ header
3
+ h1 = renderer.post.title
4
+ time.published-at = renderer.post.publish_date.to_s(:long)
5
+ = render_cloudpress_tags(renderer.post.tags) if renderer.tags?
6
+ = renderer.post.body.html_safe
7
+ footer
8
+ = yield
@@ -0,0 +1 @@
1
+ = render_cloudpress_posts(posts)
@@ -0,0 +1 @@
1
+ = render_cloudpress_post(post)
@@ -0,0 +1,2 @@
1
+ li
2
+ = link_to renderer.tag.name, tag_path(renderer.tag.slug), class: 'tag'
@@ -0,0 +1,2 @@
1
+ ul.tag-list
2
+ = yield
@@ -0,0 +1 @@
1
+ = render_cloudpress_posts(posts)
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "cloudpress"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/rails ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
3
+
4
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
5
+ ENGINE_PATH = File.expand_path('../../lib/cloudpress/engine', __FILE__)
6
+
7
+ # Set up gems listed in the Gemfile.
8
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
9
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
10
+
11
+ require 'rails/all'
12
+ require 'rails/engine/commands'
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cloudpress/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cloudpress"
8
+ spec.version = Cloudpress::VERSION
9
+ spec.authors = ["Adam Carlile"]
10
+ spec.email = ["adam@benchmedia.co.uk"]
11
+
12
+ spec.summary = "A blogging framework that uses dropbox as it's storage engine"
13
+ spec.homepage = "http://github.com/adamcarlile/cloudpress"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "dropbox-sdk"
22
+ spec.add_dependency "metadown"
23
+ spec.add_dependency "pygments.rb"
24
+ spec.add_dependency "nokogiri"
25
+ spec.add_dependency "kaminari"
26
+ spec.add_dependency "builder"
27
+ spec.add_dependency "groupdate"
28
+ spec.add_dependency "slim"
29
+ spec.add_dependency "acts-as-taggable-on"
30
+ spec.add_dependency "friendly_id"
31
+ spec.add_dependency "rails", "~> 4.2.0"
32
+
33
+ spec.add_development_dependency "bundler", "~> 1.8"
34
+ spec.add_development_dependency "rake", "~> 10.0"
35
+ end
@@ -0,0 +1,4 @@
1
+ class ActsAsTaggableOn::Tag
2
+ extend FriendlyId
3
+ friendly_id :name, use: [:slugged, :finders]
4
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,9 @@
1
+ Cloudpress::Engine.routes.draw do
2
+
3
+ resources :tags, only: :show
4
+ resource :archives, path: '/:year(/:month)', only: :show
5
+
6
+ root to: 'posts#index'
7
+ get '*slug', to: 'posts#show', as: :post
8
+
9
+ end
@@ -0,0 +1,19 @@
1
+ class CreateCloudpressPosts < ActiveRecord::Migration
2
+ def change
3
+ create_table :cloudpress_posts do |t|
4
+ t.string :title
5
+ t.string :slug
6
+ t.string :file_path
7
+ t.string :revision
8
+ t.string :state
9
+ t.text :body
10
+ t.json :metadata
11
+ t.datetime :publish_date
12
+ t.timestamps null: false
13
+ end
14
+
15
+ add_index :cloudpress_posts, :state
16
+ add_index :cloudpress_posts, :slug
17
+ add_index :cloudpress_posts, :file_path
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ class CreateTagsTable < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :tags do |t|
4
+ t.string :name
5
+ end
6
+
7
+ create_table :taggings do |t|
8
+ t.references :tag
9
+
10
+ # You should make sure that the column created is
11
+ # long enough to store the required class names.
12
+ t.references :taggable, :polymorphic => true
13
+ t.references :tagger, :polymorphic => true
14
+
15
+ # Limit is created to prevent MySQL error on index
16
+ # length for MyISAM table type: http://bit.ly/vgW2Ql
17
+ t.string :context, :limit => 128
18
+
19
+ t.datetime :created_at
20
+ end
21
+
22
+ add_index :taggings, :tag_id
23
+ add_index :taggings, [:taggable_id, :taggable_type, :context]
24
+ end
25
+
26
+ def self.down
27
+ drop_table :taggings
28
+ drop_table :tags
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ class CreateFriendlyIdSlugs < ActiveRecord::Migration
2
+ def change
3
+ create_table :friendly_id_slugs do |t|
4
+ t.string :slug, :null => false
5
+ t.integer :sluggable_id, :null => false
6
+ t.string :sluggable_type, :limit => 50
7
+ t.string :scope
8
+ t.datetime :created_at
9
+ end
10
+ add_index :friendly_id_slugs, :sluggable_id
11
+ add_index :friendly_id_slugs, [:slug, :sluggable_type]
12
+ add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], :unique => true
13
+ add_index :friendly_id_slugs, :sluggable_type
14
+ end
15
+ end
@@ -0,0 +1,6 @@
1
+ class AddSlugToTags < ActiveRecord::Migration
2
+ def change
3
+ add_column :tags, :slug, :string
4
+ add_index :tags, :slug, unique: true
5
+ end
6
+ end
@@ -0,0 +1,14 @@
1
+ class AddTaggingsCounterCache < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :tags, :taggings_count, :integer, default: 0
4
+
5
+ ActsAsTaggableOn::Tag.reset_column_information
6
+ ActsAsTaggableOn::Tag.find_each do |tag|
7
+ ActsAsTaggableOn::Tag.reset_counters(tag.id, :taggings)
8
+ end
9
+ end
10
+
11
+ def self.down
12
+ remove_column :tags, :taggings_count
13
+ end
14
+ end
data/lib/cloudpress.rb ADDED
@@ -0,0 +1,105 @@
1
+ require "dropbox_sdk"
2
+ require "metadown"
3
+ require "redcarpet"
4
+ require "pygments"
5
+ require "kaminari"
6
+ require "groupdate"
7
+ require "slim"
8
+ require "friendly_id"
9
+ require "acts-as-taggable-on"
10
+ require "cloudpress/version"
11
+ require "cloudpress/engine"
12
+
13
+ require "cloudpress/formatters/html_with_pygments"
14
+ require "cloudpress/dropbox/client"
15
+ require "cloudpress/dropbox/connector"
16
+ require "cloudpress/dropbox/directory"
17
+ require "cloudpress/dropbox/file"
18
+ require "cloudpress/dropbox/cleaner"
19
+
20
+ require "cloudpress/renderers/archive"
21
+ require "cloudpress/renderers/archives"
22
+ require "cloudpress/renderers/post"
23
+ require "cloudpress/renderers/posts"
24
+ require "cloudpress/renderers/tag"
25
+ require "cloudpress/renderers/tags"
26
+
27
+ module Cloudpress
28
+
29
+ module_function
30
+
31
+ def config
32
+ @config ? @config : configure
33
+ end
34
+
35
+ def configure &block
36
+ @config = ActiveSupport::OrderedOptions.new
37
+ @config.app_key = nil
38
+ @config.app_secret = nil
39
+ @config.app_token = nil
40
+ @config.base_path = 'cloudpress'
41
+ @config.live_path = 'live'
42
+ @config.draft_path = 'draft'
43
+ @config.markdown_renderer = Redcarpet::Markdown.new(Formatters::HTMLWithPygments, fenced_code_blocks: true)
44
+ yield(@config) if block_given?
45
+ @config
46
+ end
47
+
48
+ def base_path
49
+ "/#{config.base_path}"
50
+ end
51
+
52
+ def reload!
53
+ @file_index = nil
54
+ end
55
+
56
+ def live_path
57
+ [base_path, config.live_path].join('/')
58
+ end
59
+
60
+ def draft_path
61
+ [base_path, config.draft_path].join('/')
62
+ end
63
+
64
+ def update!
65
+ reload!
66
+ import!
67
+ purge!
68
+ end
69
+
70
+ def import!
71
+ file_index.values.each {|x| x.import!}
72
+ end
73
+
74
+ def purge!
75
+ Cloudpress::Dropbox::Cleaner.new(remote_paths, local_paths).clean!
76
+ end
77
+
78
+ def file_index
79
+ @file_index ||= {
80
+ live: Cloudpress::Dropbox::Directory.new(client, live_path, :live),
81
+ draft: Cloudpress::Dropbox::Directory.new(client, draft_path, :draft)
82
+ }
83
+ end
84
+
85
+ def client
86
+ @client ||= Cloudpress::Dropbox::Client.new(config.app_token)
87
+ end
88
+
89
+ def unconfigured?
90
+ config.app_key.blank? || config.app_secret.blank?
91
+ end
92
+
93
+ def unauthorized?
94
+ config.app_token.blank?
95
+ end
96
+
97
+ def remote_paths
98
+ file_index.values.map {|x| x.paths }.flatten
99
+ end
100
+
101
+ def local_paths
102
+ Cloudpress::Post.pluck(:file_path)
103
+ end
104
+
105
+ end
@@ -0,0 +1,20 @@
1
+ module Cloudpress
2
+ module Dropbox
3
+ class Cleaner
4
+
5
+ def initialize(remote_paths, local_paths)
6
+ @local_paths = local_paths
7
+ @remote_paths = remote_paths
8
+ end
9
+
10
+ def orphaned_paths
11
+ @orphaned_paths ||= @local_paths - @remote_paths
12
+ end
13
+
14
+ def clean!
15
+ Post.where(file_path: orphaned_paths).destroy_all if orphaned_paths.any?
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ module Cloudpress
2
+ module Dropbox
3
+ class Client
4
+
5
+ def initialize(token)
6
+ @token = token
7
+ end
8
+
9
+ def connection
10
+ @connection ||= DropboxClient.new(@token)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ module Cloudpress
2
+ module Dropbox
3
+ class Connector
4
+
5
+ def initialize(key, secret)
6
+ @key = key
7
+ @secret = secret
8
+ end
9
+
10
+ def flow
11
+ @flow ||= DropboxOAuth2FlowNoRedirect.new(@key, @secret)
12
+ end
13
+
14
+ def authorize_url
15
+ @authorize_url ||= flow.start
16
+ end
17
+
18
+ def authorize!(code)
19
+ flow.finish(code)
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,57 @@
1
+ module Cloudpress
2
+ module Dropbox
3
+ class Directory
4
+
5
+ attr_reader :path, :client, :state
6
+
7
+ def initialize(client, path, state)
8
+ @client = client
9
+ @path = path
10
+ @state = state
11
+ end
12
+
13
+ def import!
14
+ files.each { |x| x.import! }
15
+ subdirectories.each {|x| x.import! }
16
+ end
17
+
18
+ def paths
19
+ @paths ||= begin
20
+ files.map {|x| x.path } + subdirectories.map { |x| x.paths }
21
+ end.flatten.compact.uniq
22
+ end
23
+
24
+ def directory_contents
25
+ remote_path_metadata.with_indifferent_access[:contents]
26
+ end
27
+
28
+ def files
29
+ @files ||= directory_contents.select { |x| x[:is_dir] == false }.map do |x|
30
+ Cloudpress::Dropbox::File.new(x[:path], x[:rev], state)
31
+ end
32
+ end
33
+
34
+ def subdirectories
35
+ @subdirectories ||= directory_contents.select { |x| x[:is_dir] == true }.map do |x|
36
+ Cloudpress::Dropbox::Directory.new(client, x[:path], state)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def create_path!
43
+ client.connection.file_create_folder path
44
+ end
45
+
46
+ def remote_path_metadata
47
+ @remote_path_metadata ||= begin
48
+ client.connection.metadata(path)
49
+ rescue DropboxError
50
+ create_path!
51
+ client.connection.metadata(path)
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end