artifact 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e79feef5a99e24c60cc8a5bd3cc45e9b8c02739c
4
+ data.tar.gz: 1cc7825dad9f2d6240626055db3c1f863bd9f88f
5
+ SHA512:
6
+ metadata.gz: 1bb13911e6984f007950ca058d7959538547d9410f73481350c54721870b3a96be2eec8acfd5d755bcf3e857b3d3ff141eb2b60c23a68203598b6cdc4f63205a
7
+ data.tar.gz: 4b07499de205b9248530b699f5a016fd01b9ea4afa253d2a1fd3ad819cb1129bc2175b7332ef68a12adbaac5df9a8afe3fd412723c4bb61c258f53ad1e12d260
@@ -0,0 +1,5 @@
1
+ *~
2
+ pkg
3
+ site
4
+ Gemfile.lock
5
+ site.base
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in artifact.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Tomás Pollak
2
+
3
+ MIT License
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.
@@ -0,0 +1,29 @@
1
+ # Artifact
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'artifact'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install artifact
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'artifact/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "artifact"
8
+ spec.version = Artifact::VERSION
9
+ spec.authors = ["Tomás Pollak"]
10
+ spec.email = ["tomas@forkhq.com"]
11
+ spec.description = %q{Frontend UI for git blogging.}
12
+ spec.summary = %q{Frontend UI for git blogging.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec", "~> 2.6"
24
+
25
+ spec.add_dependency "rack"
26
+ spec.add_dependency "activesupport"
27
+ spec.add_dependency "rugged"
28
+ spec.add_dependency "sinatra"
29
+ spec.add_dependency "sinatra-contrib"
30
+ spec.add_dependency "rack-flash3"
31
+ end
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'artifact/cli'
4
+ Artifact::CLI.start
@@ -0,0 +1,337 @@
1
+ require 'yaml'
2
+ require 'rugged'
3
+ require 'artifact/version'
4
+
5
+ module Artifact
6
+
7
+ FORMATS = {
8
+ '.markdown' => "Artifact::MarkdownFile",
9
+ '.html' => "Artifact::HTMLFile"
10
+ }
11
+
12
+ def self.config
13
+ @config ||= Config.new
14
+ end
15
+
16
+ def self.configure
17
+ yield config
18
+ end
19
+
20
+ def self.root
21
+ config.root
22
+ end
23
+
24
+ def self.full_path(path)
25
+ File.join(root, path)
26
+ end
27
+
28
+ def self.relative_path(path)
29
+ path.sub(root + '/', '')
30
+ end
31
+
32
+ def self.open_file(path)
33
+ load_file(path, ReadableFile)
34
+ end
35
+
36
+ def self.new_file(path)
37
+ load_file(path, WritableFile)
38
+ end
39
+
40
+ def self.load_file(path, fallback)
41
+ if klass = FORMATS[File.extname(path)]
42
+ Kernel.const_get(klass).new(path)
43
+ else
44
+ fallback.new(path)
45
+ end
46
+ end
47
+
48
+ def self.repo
49
+ @repo ||= Repo.new(File.expand_path(config.root))
50
+ end
51
+
52
+ def self.ensure_dir!(full_path)
53
+ dir = File.dirname(full_path)
54
+ FileUtils.mkdir_p(dir) unless File.exist?(dir)
55
+ end
56
+
57
+ class Config
58
+
59
+ attr_accessor *%w(
60
+ root source_root posts_path drafts_path uploads_path directory_indexes
61
+ )
62
+
63
+ def initialize
64
+ @root = Dir.pwd # current working path
65
+ @source_root = 'source'
66
+ @posts_path = 'posts'
67
+ @drafts_path = 'drafts'
68
+ @uploads_path = 'uploads'
69
+ @directory_indexes = true
70
+ end
71
+
72
+ end
73
+
74
+ class Tree
75
+
76
+ DEFAULT_NEW_FILE_PATH = '%Y/%m'
77
+
78
+ attr_reader :path, :full_path
79
+
80
+ def initialize(full_path, options = {})
81
+ @full_path = File.expand_path(full_path)
82
+ @path = Artifact.relative_path(full_path)
83
+ @options = options
84
+
85
+ Artifact.ensure_dir!(full_path)
86
+ end
87
+
88
+ def new(filename, extension = nil)
89
+ file_path = File.join(new_file_path, [filename, extension].compact.join)
90
+
91
+ if File.exist?(file_path)
92
+ appended = filename.sub(/\-?(\d{1,2})?$/) { |a| "-#{((a || 0).to_i*-1)+1}" }
93
+ return new(appended, extension)
94
+ end
95
+
96
+ file_path.sub(filename, filename + '-1')
97
+ Artifact.new_file(file_path)
98
+ end
99
+
100
+ def new_file_path
101
+ path = File.join(full_path, @options[:new_file_path] || DEFAULT_NEW_FILE_PATH)
102
+ path.split(File::SEPARATOR).map do |n|
103
+ n['%'] ? Time.now.strftime(n) : n
104
+ end.join(File::SEPARATOR)
105
+ end
106
+
107
+ def all
108
+ file_list.map { |f| Artifact.open_file(f) }.sort
109
+ end
110
+
111
+ def find(match)
112
+ file_list.select { |f| f[match] }.map { |f| Artifact.open_file(f) }.sort
113
+ end
114
+
115
+ def file_list
116
+ file_list_at(full_path)
117
+ end
118
+
119
+ def file_list_at(dir, matching = '*')
120
+ files = []
121
+ Dir.glob(File.join(dir, matching)).each do |entry|
122
+ if File.directory?(entry)
123
+ files = files + file_list_at(entry, matching)
124
+ elsif File.file?(entry) # skip sockets and symlinks
125
+ files << entry
126
+ end
127
+ end
128
+ files
129
+ end
130
+
131
+ end
132
+
133
+ class Repo
134
+
135
+ def initialize(full_path)
136
+ @root = full_path
137
+ @git = Rugged::Repository.new(full_path)
138
+ end
139
+
140
+ def save(path, author, check = true)
141
+ if check && !new_file?(path) && !modified?(path)
142
+ raise "No changes in file: #{path}"
143
+ end
144
+
145
+ @git.index.add(path)
146
+ @git.index.write
147
+ write_commit("Saved #{path}", author)
148
+ end
149
+
150
+ def move(from, to, author)
151
+ Artifact.ensure_dir!(Artifact.full_path(to))
152
+ FileUtils.mv(Artifact.full_path(from), Artifact.full_path(to))
153
+
154
+ @git.index.remove(from)
155
+ @git.index.add(to)
156
+ @git.index.write
157
+ write_commit("Moved #{from} --> #{to}", author)
158
+ end
159
+
160
+ def remove(path, author)
161
+ @git.index.remove(path)
162
+ @git.index.write
163
+ write_commit("Removed #{path}", author)
164
+ end
165
+
166
+ def exists?(path)
167
+ !!file_status(path) rescue false
168
+ end
169
+
170
+ private
171
+
172
+ def new_file?(file_path)
173
+ file_status(file_path) == 'new'
174
+ end
175
+
176
+ def modified?(file_path)
177
+ file_status(file_path) == 'modified'
178
+ end
179
+
180
+ def file_status(file_path)
181
+ if s = @git.status(file_path).first
182
+ return s.to_s.sub('worktree_', '')
183
+ end
184
+ end
185
+
186
+ def write_commit(message, author)
187
+ commit_sha = Rugged::Commit.create(@git,
188
+ :author => author,
189
+ :message => message,
190
+ :committer => author,
191
+ :parents => @git.empty? ? [] : [ @git.head.target ].compact,
192
+ :tree => @git.index.write_tree,
193
+ :update_ref => 'HEAD'
194
+ )
195
+ end
196
+
197
+ end
198
+
199
+ class ReadableFile
200
+
201
+ attr_reader :path, :full_path
202
+
203
+ def initialize(path)
204
+ if path[Artifact.root] # path contains root, meaning absolute path
205
+ @path = Artifact.relative_path(path)
206
+ @full_path = path
207
+ else
208
+ @path = path
209
+ @full_path = Artifact.full_path(path)
210
+ end
211
+ end
212
+
213
+ def filename
214
+ File.basename(path)
215
+ end
216
+
217
+ def slug
218
+ File.basename(path, File.extname(path))
219
+ end
220
+
221
+ def dirname
222
+ File.dirname(full_path)
223
+ end
224
+
225
+ def last_modified
226
+ File.mtime(full_path)
227
+ end
228
+
229
+ def body
230
+ @content
231
+ end
232
+
233
+ def meta
234
+ {}
235
+ end
236
+
237
+ def exists?
238
+ File.exist?(full_path)
239
+ end
240
+
241
+ private
242
+
243
+ def content
244
+ @content ||= read
245
+ end
246
+
247
+ def read
248
+ IO.read(@full_path)
249
+ end
250
+
251
+ def <=>(other_file)
252
+ self.last_modified <=> other_file.last_modified
253
+ end
254
+
255
+ end
256
+
257
+ class WritableFile < ReadableFile
258
+
259
+ def update(content, meta = nil)
260
+ Artifact.ensure_dir!(@full_path)
261
+ # File.open(@full_path, 'wb') { |f| f.write(content.gsub("\r\n", "\n")) }
262
+ File.open(@full_path, 'wb') { |f| f.write(content) }
263
+ @content = nil
264
+ end
265
+
266
+ def delete
267
+ FileUtils.rm(full_path)
268
+ end
269
+
270
+ end
271
+
272
+ class HTMLFile < WritableFile
273
+
274
+ end
275
+
276
+ class MarkdownFile < WritableFile
277
+
278
+ def title
279
+ data['title']
280
+ end
281
+
282
+ def date
283
+ data['date']
284
+ end
285
+
286
+ def author
287
+ data['author']
288
+ end
289
+
290
+ def category
291
+ data['category']
292
+ end
293
+
294
+ def last_updated_by
295
+ data['last_updated_by']
296
+ end
297
+
298
+ def update(content, meta = {})
299
+ yaml_data = sanitized_meta(meta)
300
+ body = "#{yaml_data}---\n\n#{content}"
301
+ super(body)
302
+ @data = nil
303
+ end
304
+
305
+ def update_meta(key, val)
306
+ hash = {}
307
+ hash[key.to_s] = val
308
+ update(content, data.merge(hash))
309
+ end
310
+
311
+ def content
312
+ parts[1..-1].join("---\n\n")
313
+ end
314
+
315
+ def data
316
+ @data ||= YAML.load(parts[0])
317
+ rescue Psych::SyntaxError => e
318
+ raise "Invalid YAML at #{path}: #{parts[0]}"
319
+ end
320
+
321
+ def parts
322
+ read.split("---\n\n")
323
+ end
324
+
325
+ def sanitized_meta(hash)
326
+ hash.stringify_keys!
327
+ if exists?
328
+ %w(date author).each do |key|
329
+ hash[key] = self.send(key) if !hash[key] and self.send(key)
330
+ end
331
+ end
332
+ hash.to_yaml
333
+ end
334
+
335
+ end
336
+
337
+ end