artifact 0.0.2

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.
@@ -0,0 +1,55 @@
1
+ require File.join(File.dirname(__FILE__), 'helpers')
2
+ %w(sinatra/base active_support/inflector json).each { |lib| require lib }
3
+
4
+ module Artifact
5
+
6
+ class App < Sinatra::Base
7
+
8
+ set :method_override, true
9
+ set :sessions, true
10
+ set :views, File.expand_path(File.join(File.dirname(__FILE__), 'views'))
11
+
12
+ use Rack::ShowExceptions
13
+ # use Rack::CommonLogger, LOGGER
14
+ # use Rack::Flash
15
+ use Rack::Static,
16
+ :urls => %w(/css /img /js),
17
+ :root => File.expand_path(File.join(File.dirname(__FILE__), 'public'))
18
+
19
+ helpers do
20
+ # include Rack::Utils # provides escape, parse_query and unescape
21
+ # include Sinatra::ContentFor
22
+ include Helpers
23
+ def logger; LOGGER; end
24
+ end
25
+
26
+ error do
27
+ if request.env['sinatra.error'].class.name['DocumentNotFound']
28
+ not_found
29
+ else
30
+ erb :'errors/500', :layout => false
31
+ end
32
+ end
33
+
34
+ not_found do
35
+ not_found
36
+ end
37
+
38
+ private
39
+
40
+ # FIXME: SOON!
41
+ def current_user
42
+ author = {
43
+ :email => "ofofofof@gmail.com",
44
+ :time => Time.now,
45
+ :name => "The User"
46
+ }
47
+ end
48
+
49
+ def not_found
50
+ erb :'errors/404', :layout => false
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,60 @@
1
+ require File.join(File.dirname(__FILE__), 'app')
2
+
3
+ module Artifact
4
+
5
+ def self.files
6
+ @files ||= Tree.new(config.root)
7
+ end
8
+
9
+ class Files < App
10
+
11
+ get '/' do
12
+ redirect to('/files')
13
+ end
14
+
15
+ get '/files' do
16
+ @files = Artifact.files.all
17
+ view 'files/index'
18
+ end
19
+
20
+ get '/files/new' do
21
+ view 'files/new'
22
+ end
23
+
24
+ post '/files/create' do
25
+ @file = Artifact.files.new(params[:path])
26
+ @file.update(params[:content], params[:meta])
27
+ redirect to('/')
28
+ end
29
+
30
+ get '/files/edit/*' do
31
+ get_file
32
+ view 'files/edit'
33
+ end
34
+
35
+ post '/files/update/*' do
36
+ get_file
37
+ @file.update(params[:content], params[:meta])
38
+
39
+ if params[:submit] and params[:submit][/push/i]
40
+ return redirect to("/commit/#{params[:splat][0]}")
41
+ end
42
+
43
+ redirect to('/')
44
+ end
45
+
46
+ get '/commit/*' do
47
+ get_file
48
+ Artifact.repo.save(@file.full_path, current_user)
49
+ redirect to('/')
50
+ end
51
+
52
+ private
53
+
54
+ def get_file
55
+ Artifact.open_file(params[:splat][0]) or raise 'File not found!'
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,130 @@
1
+ require 'uri'
2
+
3
+ module Artifact
4
+
5
+ module Helpers
6
+
7
+ #########################################
8
+ # main
9
+ #########################################
10
+
11
+ def app_name
12
+ 'Artifact'
13
+ end
14
+
15
+ def action
16
+ request.path_info.gsub('/','').empty? ? 'home' : request.path_info.gsub('/',' ')
17
+ end
18
+
19
+ def requires!(required, hash = params)
20
+ if required.is_a?(Hash)
21
+ required.each { |k, vals| requires!(vals, hash[k]) }
22
+ elsif hash.nil? or hash.is_a?(String) or required.any?{ |p| hash[p].nil? }
23
+ halt(400, 'Missing parameters.')
24
+ end
25
+ end
26
+
27
+ #########################################
28
+ # rendering
29
+ #########################################
30
+
31
+ def partial(name, *args)
32
+ partial_name = name.to_s["/"] ? name.to_s.reverse.sub("/", "_/").reverse : "_#{name}"
33
+ erb(partial_name.to_sym, {:layout => false}, *args)
34
+ end
35
+
36
+ def view(view_name, *args)
37
+ layout = request.xhr? ? false : @mobile_host ? :'layout.mobile' : true
38
+ erb(view_name.to_sym, {:layout => layout}, *args)
39
+ end
40
+
41
+ #########################################
42
+ # paths, links
43
+ #########################################
44
+
45
+ def base_path
46
+ env['SCRIPT_NAME']
47
+ end
48
+
49
+ def get_class_name(klass)
50
+ klass.name.to_s.split("::").last
51
+ end
52
+
53
+ def guess_model_path(m)
54
+ base_path + '/' + get_class_name(m).pluralize.downcase
55
+ end
56
+
57
+ def url_for(object)
58
+ object.nil? ? "#" : object.is_a?(String) ? object \
59
+ : object.respond_to?(:path) ? object.path : "#{guess_model_path(object.class)}/#{object.to_param}"
60
+ end
61
+
62
+ def link_to(string, resource)
63
+ "<a href='#{url_for(resource)}'>#{string}</a>"
64
+ end
65
+
66
+ def active?(link)
67
+ request.path[link] ? 'active' : ''
68
+ end
69
+
70
+ def view_upload_path(file)
71
+ url(upload_relative_path(file))
72
+ end
73
+
74
+ def upload_relative_path(file)
75
+ # we need to start with a /, otherwise middleman will insert his image_path as prefix
76
+ '/' + Artifact.config.uploads_path + file.path.sub(Artifact.uploads.path, '')
77
+ end
78
+
79
+ def is_image?(filename)
80
+ filename.match(/\.(png|jpg|jpeg|gif|bmp)$/i)
81
+ end
82
+
83
+ def post_preview_path(post)
84
+ str = post.path.sub(Artifact.config.source_root, '')
85
+ if Artifact.config.directory_indexes
86
+ str.sub(post.filename, post.filename.split('.').first)
87
+ end
88
+ end
89
+
90
+ def delete_link(resource, options = {})
91
+ css_class = options[:class] || ''
92
+ text = options[:text] || 'Delete'
93
+ path = url_for(resource)
94
+ '<form class="destroy" style="display:inline" method="post" action="'+ path +'" onsubmit="return confirm(\'Are you sure?\');">
95
+ <input type="hidden" name="_method" value="delete" />
96
+ <button type="submit" class="' + css_class +' btn btn-danger">' + text + '</button>
97
+ </form>'
98
+ end
99
+
100
+ def in_words(time)
101
+ minutes = (((Time.now - time).abs)/60).round
102
+ return nil if minutes < 0
103
+
104
+ case minutes
105
+ when 0..1 then 'less than a minute'
106
+ when 2..4 then 'less than 5 minutes'
107
+ when 5..14 then 'less than 15 minutes'
108
+ when 15..29 then "half an hour"
109
+ when 30..59 then "#{minutes} minutes"
110
+ when 60..119 then '1 hour'
111
+ when 120..239 then '2 hours'
112
+ when 240..479 then '4 hours'
113
+ when 480..719 then '8 hours'
114
+ when 720..1439 then '12 hours'
115
+ when 1440..11519 then "#{(minutes/1440).floor} days"
116
+ when 11520..43199 then "#{(minutes/11520).floor} weeks"
117
+ when 43200..525599 then "#{(minutes/43200).floor} months"
118
+ else "#{(minutes/525600).floor} years"
119
+ end
120
+ end
121
+
122
+ def time_ago_in_words(time)
123
+ if str = in_words(time)
124
+ "#{str} ago"
125
+ end
126
+ end
127
+
128
+ end
129
+
130
+ end
@@ -0,0 +1,213 @@
1
+ require File.join(File.dirname(__FILE__), 'app')
2
+ require 'active_support'
3
+
4
+ class Middleman::Sitemap::Store
5
+
6
+ alias_method :original_initialize, :initialize
7
+
8
+ def initialize(app)
9
+ Artifact.middleman = app
10
+ original_initialize(app)
11
+ end
12
+
13
+ end
14
+
15
+ class Middleman::CoreExtensions::FrontMatter
16
+
17
+ # extension's clear_data method doesn't work
18
+ # because it uses the full file path, while
19
+ # the cache setter uses only the relative path
20
+ def flush_cache!
21
+ @cache = {}
22
+ end
23
+
24
+ end
25
+
26
+ module Artifact
27
+
28
+ def self.middleman=(app)
29
+ @app = app
30
+ end
31
+
32
+ def self.middleman
33
+ @app
34
+ end
35
+
36
+ def self.source_root
37
+ File.join(config.root, config.source_root)
38
+ end
39
+
40
+ def self.posts
41
+ @posts ||= Tree.new(File.join(source_root, Artifact.config.posts_path))
42
+ end
43
+
44
+ def self.drafts
45
+ opts = {:new_file_path => ''} # flat, no date
46
+ @drafts ||= Tree.new(File.join(source_root, Artifact.config.drafts_path), opts)
47
+ end
48
+
49
+ def self.uploads
50
+ @uploads ||= Tree.new(File.join(source_root, Artifact.config.uploads_path))
51
+ end
52
+
53
+ class Middleman < App
54
+
55
+ POST_EXTENSION = '.html.markdown'
56
+
57
+ get '/' do
58
+ redirect to('/posts')
59
+ end
60
+
61
+ post '/build' do
62
+ # updated! and rebuild!
63
+ rebuild!
64
+ redirect to('/')
65
+ end
66
+
67
+ get '/search' do
68
+ if params[:q].present?
69
+ normalized = params[:q].gsub(' ', '-').downcase
70
+ @list = filter_and_sort(Artifact.posts.find(normalized), :date)
71
+ else
72
+ @list = filter_and_sort(Artifact.posts.all.last(10), :date)
73
+ end
74
+ partial 'posts/list'
75
+ end
76
+
77
+ get '/posts' do
78
+ @posts = filter_and_sort(Artifact.posts.all.last(10), :date)
79
+ @drafts = filter_and_sort(Artifact.drafts.all, :last_modified)
80
+ view 'posts/index'
81
+ end
82
+
83
+ get '/posts/new' do
84
+ view 'posts/new'
85
+ end
86
+
87
+ post '/posts' do
88
+ data = params[:meta] # includes title and maybe something else
89
+ filename = data[:title].parameterize
90
+
91
+ @file = Artifact.drafts.new(filename, POST_EXTENSION)
92
+ @file.update(params[:content], data)
93
+ redirect to('/posts')
94
+ end
95
+
96
+ get '/drafts/*' do
97
+ @post = MarkdownFile.new(params[:splat][0])
98
+ @draft = true
99
+ view 'posts/edit'
100
+ end
101
+
102
+ get '/posts/*' do
103
+ @post = MarkdownFile.new(params[:splat][0])
104
+ view 'posts/edit'
105
+ end
106
+
107
+ post '/posts/*' do
108
+ @post = MarkdownFile.new(params[:splat][0])
109
+
110
+ meta = (params[:meta] || {}).merge('last_updated_by' => current_user[:email])
111
+ @post.update(params[:content].gsub("\r\n", "\n"), meta)
112
+
113
+ if params[:save] # user clicked on 'save' or 'published'
114
+ # if draft was previously saved this will raise 'coz no changes.
115
+ Artifact.repo.save(@post.path, current_user, false)
116
+
117
+ if params[:save] == 'Publish'
118
+ @post.update_meta(:date, Time.now.utc.strftime("%Y-%m-%d %H:%M %Z"))
119
+ publish_post!(@post)
120
+ elsif params[:save] == 'Unpublish'
121
+ unpublish_post!(@post)
122
+ end
123
+ end
124
+
125
+ updated!
126
+ redirect to('/posts')
127
+ end
128
+
129
+ delete '/posts/*' do
130
+ @post = MarkdownFile.new(params[:splat][0])
131
+ @post.delete
132
+ Artifact.repo.remove(@post.path, current_user) if Artifact.repo.exists?(@post.path)
133
+ updated!
134
+
135
+ redirect to('/posts')
136
+ end
137
+
138
+ get '/uploads/*' do
139
+ file = params[:splat].first
140
+ send_file File.join(Artifact.uploads.path, file)
141
+ end
142
+
143
+ get '/uploads' do
144
+ get_uploads
145
+ # @uploads.select! {|i| i.match(/\.(png|jpg|jpeg|gif)/i) }
146
+ partial 'uploads/list'
147
+ end
148
+
149
+ post '/uploads' do
150
+ unless params[:file] &&
151
+ (tmpfile = params[:file][:tempfile]) &&
152
+ (name = params[:file][:filename])
153
+ raise 'No file uploaded.'
154
+ else
155
+ filename = File.basename(name, File.extname(name)).parameterize # without extension
156
+ file = Artifact.uploads.new(filename, File.extname(name))
157
+ file.update(tmpfile.read)
158
+ Artifact.repo.save(file.path, current_user) # pass relative path
159
+
160
+ updated!
161
+ redirect to('/')
162
+ end
163
+ end
164
+
165
+ private
166
+
167
+ def get_uploads
168
+ @uploads = Artifact.uploads.all.last(10).reverse
169
+ end
170
+
171
+ def filter_and_sort(posts, criteria)
172
+ posts.select { |f| f.is_a?(WritableFile) }.sort do |a,b|
173
+ b.send(criteria) <=> a.send(criteria)
174
+ end
175
+ end
176
+
177
+ def publish_post!(post)
178
+ filename = post.title.parameterize
179
+ new_file = Artifact.posts.new(filename, POST_EXTENSION)
180
+ Artifact.repo.move(post.path, new_file.path, current_user)
181
+ end
182
+
183
+ def unpublish_post!(post)
184
+ filename = post.title.parameterize
185
+ new_file = Artifact.drafts.new(filename, POST_EXTENSION)
186
+ Artifact.repo.move(post.path, new_file.path, current_user)
187
+ end
188
+
189
+ def rebuild!
190
+ puts `bundle exec middleman build`
191
+ end
192
+
193
+ def updated!
194
+ return if Artifact.middleman.nil? # not loaded yet!
195
+
196
+ puts "Reloading Middleman..."
197
+ Artifact.middleman.files.reload_path(Artifact.drafts.path)
198
+ Artifact.middleman.files.reload_path(Artifact.posts.path)
199
+ Artifact.middleman.files.reload_path(Artifact.uploads.path)
200
+
201
+ Artifact.middleman.cache.clear # Tilt (template) cache
202
+ # Artifact.middleman.sitemap.send(:reset_lookup_cache!)
203
+ Artifact.middleman.sitemap.rebuild_resource_list!
204
+
205
+ if Artifact.middleman.extensions[:frontmatter]
206
+ puts "Flushing frontmatter cache..."
207
+ Artifact.middleman.extensions[:frontmatter].flush_cache!
208
+ end
209
+ end
210
+
211
+ end
212
+
213
+ end