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,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