amiba 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,28 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "thor", "~>0.14.6", :require => %w{thor thor/group thor/runner}
4
+ gem "tilt", "~>1.2.1"
5
+ gem "haml", "~>3.0.25"
6
+ gem "activesupport", "~>3.0.4", :require => 'active_support/all'
7
+ gem "activemodel", "~>3.0.3", :require => 'active_model'
8
+ gem "i18n", "~> 0.5.0"
9
+ gem "rdiscount", "~> 1.6.8"
10
+ gem "grit", "~> 2.4.1"
11
+ gem "yajl-ruby", :require => "yajl/json_gem"
12
+ gem "fog", ">= 0.6.0"
13
+
14
+ group :development do
15
+ gem "rspec"
16
+ gem "rspec_tag_matchers"
17
+ gem "autotest"
18
+ gem "factory_girl", ">=2.0.0.beta1"
19
+ gem "ruby-debug19", :require => "ruby-debug"
20
+ end
21
+
22
+ group :wp do
23
+ gem "sequel"
24
+ end
25
+
26
+ if ENV['AMIBA_BIN'] == 'true'
27
+ gem 'amiba', :require => %w{amiba amiba/all}
28
+ end
data/Thorfile ADDED
@@ -0,0 +1,2 @@
1
+ $:.unshift(File.expand_path(File.join(Dir.pwd, "lib")))
2
+ require "amiba"
data/bin/amiba ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- mode: ruby -*-
3
+ require 'rubygems'
4
+
5
+ if !File.exist?('Gemfile')
6
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
7
+ end
8
+
9
+ ENV["AMIBA_BIN"] = "true"
10
+
11
+ require 'bundler/setup'
12
+ Bundler.require(:default, :production)
13
+
14
+ $thor_runner = true
15
+ Thor::Runner.start
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- mode: ruby -*-
3
+ require 'rubygems'
4
+
5
+ if !File.exist?('Gemfile')
6
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
7
+ end
8
+
9
+ ENV["AMIBA_BIN"] = "true"
10
+
11
+ IMAGE_ROOT="http://wp-uploads.s3.amazonaws.com/wp-content/uploads/"
12
+
13
+ require 'bundler/setup'
14
+ Bundler.require(:default, :production, :wp)
15
+
16
+ require 'optparse'
17
+ require 'amiba/reverse_markdown'
18
+
19
+ require 'cgi'
20
+ require 'uri'
21
+
22
+ include Amiba::Repo
23
+
24
+ options = {}
25
+ options[:dbtype] = "mysql"
26
+
27
+ OptionParser.new do |opt|
28
+ opt.on("--dbuser [USER]", "The database user to connect as") do |u|
29
+ options[:dbuser] = u
30
+ end
31
+ opt.on("--dbtype [TYPE]", "The type of database to connect to. Default is MySQL") do |u|
32
+ options[:dbtype] = u
33
+ end
34
+ opt.on("--dbhost [HOST]", "The database host to connect to") do |u|
35
+ options[:dbhost] = u
36
+ end
37
+ opt.on("--db [NAME]", "The database to connect to") do |u|
38
+ options[:db] = u
39
+ end
40
+ opt.on("--dbpass [PASS]", "The database password to connect with") do |u|
41
+ options[:dbpass] = u
42
+ end
43
+ opt.on("--target [TARGET]", "The directory to create an amiba structure in") do |u|
44
+ options[:target] = u
45
+ end
46
+
47
+ end.parse!
48
+
49
+ path, name = File.split options[:target]
50
+ if ! system "amiba create #{name} --path #{path}"
51
+ puts "Couldn't create a basic amiba site at #{options[:target]}!"
52
+ exit 1
53
+ end
54
+
55
+ Dir.chdir options[:target]
56
+
57
+
58
+ DB = Sequel.connect(:adapter=>options[:dbtype], :host => options[:dbhost], :database=>options[:db], :user => options[:dbuser], :password => options[:dbpass], :encoding => 'utf8')
59
+ Sequel::MySQL.convert_invalid_date_time = nil
60
+
61
+ DB[:wp_posts].filter(:post_type=>"post").each do |post|
62
+ rm = Amiba::ReverseMarkdown.new
63
+ meta = {}
64
+ category = DB[:wp_terms].select(:name).join(:wp_term_relationships, :term_taxonomy_id=>:term_id).filter(:object_id=>post[:ID]).first
65
+ next unless category
66
+
67
+ meta[:author] = DB[:wp_users].select(:display_name).filter(:ID=>post[:post_author]).first[:display_name]
68
+ meta[:slug] = post[:post_excerpt].to_s
69
+ meta[:layout] = 'default'
70
+ meta[:state] = post[:post_status] == "publish" ? "published" : "draft"
71
+ meta[:title] = post[:post_title].to_s
72
+ meta[:id] = post[:ID]
73
+
74
+ #yet more assumptions (like the fact that an attached file is an
75
+ #image!)
76
+ attach = DB[:wp_postmeta].select(:meta_value).filter(:post_id=>DB[:wp_posts].select(:ID).filter({:ID=>post[:ID]} | {:post_parent=>post[:ID]})).filter(:meta_key=>"_wp_attached_file").first
77
+
78
+ meta[:image] = URI.join(IMAGE_ROOT, attach[:meta_value]).to_s if attach
79
+
80
+ # FIXME: (or just run away) kludge to get round UTF-8 madness - just
81
+ # turn UTF-8 raw chars into form encoded equivalent, then cgi unescape
82
+ if post[:post_content].encoding.name == "ASCII-8BIT"
83
+ content = CGI.unescape(post[:post_content].to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) { '%'+$1.unpack('H2'*$1.bytesize).join('%').upcase }).gsub(/(\w+\s?)&(\s?\w+)/,'\1&\2')
84
+ else
85
+ content = post[:post_content].gsub(/\s(\w+\s?)&(\s?\w+)/,'\1 & \2')
86
+ end
87
+ begin
88
+ md = rm.parse_string("<p>#{content}</p>")
89
+ rescue
90
+ puts "Failed to parse content for ID: #{post[:ID]}: #{$!}"
91
+ end
92
+
93
+ entry = Amiba::Source::Entry.new(category[:name].to_s, post[:post_name], 'markdown', meta, md.nil? ? post[:post_content].to_s : md)
94
+
95
+ unless entry.valid?
96
+ str = ""
97
+ entry.errors.each_pair do |area, msg|
98
+ if msg.is_a? Array
99
+ msg.each {|m| str += "Error detected in #{area}: #{m}\n" }
100
+ else
101
+ str += "Error detected in #{area}: #{msg.to_s}\n"
102
+ end
103
+ end
104
+ puts str
105
+ end
106
+
107
+ entry.save do |file, data|
108
+ FileUtils.mkdir_p File.dirname(file)
109
+ File.open(file, 'w') { |f| f.write(data) }
110
+ add_and_commit file
111
+ end
112
+
113
+ end
data/lib/amiba/all.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'amiba/core_ext/file'
2
+ require 'amiba/core_ext/symbol'
3
+ require 'amiba/configuration'
4
+ require 'amiba/repo'
5
+ require 'amiba/source'
6
+ require 'amiba/source/entry_finder'
7
+ require 'amiba/source/entry'
8
+ require 'amiba/source/feed'
9
+ require 'amiba/source/partial'
10
+ require 'amiba/page'
11
+ require 'amiba/entry'
12
+ require 'amiba/site'
13
+ require 'amiba/scope'
@@ -0,0 +1,40 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+
3
+ module Amiba
4
+ class Configuration
5
+
6
+ class << self
7
+
8
+ def method_missing(name, *args, &block)
9
+ raise ArgumentError if args.length > 1
10
+ if name.to_s[-1] == '='
11
+ write_setting(name[0..-2].to_sym, args[0])
12
+ else
13
+ read_setting(name)
14
+ end
15
+ end
16
+
17
+ protected
18
+
19
+ def load_defaults
20
+ defaults = YAML.load(File.read('.amiba'))
21
+ @config.merge!(defaults)
22
+ end
23
+
24
+ def write_setting(name, value)
25
+ config[name] = value
26
+ end
27
+
28
+ def read_setting(name)
29
+ config[name]
30
+ end
31
+
32
+ def config
33
+ return @config unless @config.nil?
34
+ @config = HashWithIndifferentAccess.new
35
+ load_defaults
36
+ @config
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,7 @@
1
+ class File
2
+
3
+ def self.relpath(fn, dir)
4
+ File.join(File.expand_path(fn).split(File::SEPARATOR) - File.expand_path(dir).split(File::SEPARATOR))
5
+ end
6
+
7
+ end
@@ -0,0 +1,11 @@
1
+ require 'active_support/inflector/inflections'
2
+
3
+ class Symbol
4
+ def pluralize
5
+ ActiveSupport::Inflector.pluralize(self.to_s).to_sym
6
+ end
7
+
8
+ def singularize
9
+ ActiveSupport::Inflector.singularize(self.to_s).to_sym
10
+ end
11
+ end
@@ -0,0 +1,108 @@
1
+ module Amiba
2
+ module Entry
3
+
4
+ class Create < Thor::Group
5
+ include Amiba::Generator
6
+ include Amiba::Repo
7
+
8
+ namespace :"entry:create"
9
+ argument :format, :default => 'markdown'
10
+ class_option :category, :required => true
11
+ class_option :title, :required => true
12
+ class_option :state, :default => 'draft'
13
+ class_option :layout, :default => 'default'
14
+ class_option :slug
15
+
16
+ def init_source
17
+ @source = Amiba::Source::Entry.new(options[:category].to_sym,
18
+ name,
19
+ format,
20
+ options,
21
+ "h1. New post\n")
22
+ end
23
+
24
+ def should_not_exist
25
+ unless @source.new?
26
+ raise Thor::Error.new("Error: An entry called '#{name}' already exists.")
27
+ end
28
+ end
29
+
30
+ def should_be_valid
31
+ unless @source.valid?
32
+ str = ""
33
+ @source.errors.each_pair do |area, msg|
34
+ if msg.is_a? Array
35
+ msg.each {|m| str += "Error detected in #{area}: #{m}\n" }
36
+ else
37
+ str += "Error detected in #{area}: #{msg.to_s}\n"
38
+ end
39
+ end
40
+ raise Thor::Error.new("Errors detected:\n" + str)
41
+ end
42
+ end
43
+
44
+ def save_entry
45
+ @source.save do |filename, file_data|
46
+ create_file filename, file_data
47
+ end
48
+ end
49
+
50
+ def add_to_git
51
+ add_and_commit @source.filename
52
+ end
53
+
54
+ protected
55
+
56
+ no_tasks do
57
+ def name
58
+ options[:title].parameterize
59
+ end
60
+ end
61
+ end
62
+
63
+ # Thor task to mark an entry published.
64
+ class Publish < Thor::Group
65
+ include Amiba::Generator
66
+ include Amiba::Repo
67
+
68
+ namespace :"entry:publish"
69
+ argument :name
70
+ argument :format, :default => 'markdown'
71
+ class_option :category, :required => true
72
+
73
+ def init_source
74
+ @source = Amiba::Source::Entry.new(options[:category].to_sym, name, format)
75
+ end
76
+
77
+ def should_exist
78
+ if @source.new?
79
+ raise Thor::Error.new("Error: Can't publish an entry that doesn't exist.")
80
+ end
81
+ end
82
+
83
+ def should_not_be_published
84
+ if @source.state == "published"
85
+ raise Thor::Error.new("Entry already published")
86
+ end
87
+ end
88
+
89
+ def save_page
90
+ @source.state = "published"
91
+ @source.save do |filename, file_data|
92
+ remove_file filename, :verbose => false
93
+ create_file(filename, :verbose => false) do
94
+ file_data
95
+ end
96
+ say_status :published, filename, :green
97
+ end
98
+ end
99
+
100
+ def add_to_git
101
+ add_and_commit @source.filename, "Published #{@source.filename}"
102
+ end
103
+
104
+ end
105
+
106
+
107
+ end
108
+ end
data/lib/amiba/page.rb ADDED
@@ -0,0 +1,139 @@
1
+ require 'amiba/source'
2
+
3
+ module Amiba
4
+ module Page
5
+
6
+ # Thor task to create a new page. It checks for the existance of a page already existing
7
+ # and that the user specified a valid format before progressing.
8
+ class Create < Thor::Group
9
+ include Amiba::Generator
10
+ include Amiba::Repo
11
+
12
+ namespace :"page:create"
13
+ argument :name
14
+ argument :format, :default => "haml"
15
+ class_option :layout, :default =>"default"
16
+ class_option :title, :required => true
17
+ class_option :description, :required => true
18
+ class_option :category, :default => "plain"
19
+ class_option :state, :default => "draft"
20
+
21
+ def init_source
22
+ @source = Amiba::Source::Page.new(name, format, options, Templates.send(format.to_sym))
23
+ end
24
+
25
+ def should_not_exist
26
+ unless @source.new?
27
+ raise Thor::Error.new("Error:A page called '#{name}' has already been created.")
28
+ end
29
+ end
30
+
31
+ def should_be_correct_format
32
+ if !@source.valid? && !@source.errors[:format].nil?
33
+ raise Thor::Error.new("Error: format should be one of " +
34
+ Amiba::Source::Page::VALID_FORMATS.join(','))
35
+ end
36
+ end
37
+
38
+ def save_page
39
+ @source.save do |filename, file_data|
40
+ create_file filename, file_data
41
+ end
42
+ end
43
+
44
+ def add_to_git
45
+ add_and_commit @source.filename
46
+ end
47
+
48
+ end
49
+
50
+ # Thor task to mark a page published.
51
+ class Publish < Thor::Group
52
+ include Amiba::Generator
53
+ include Amiba::Repo
54
+
55
+ namespace :"page:publish"
56
+ argument :name
57
+ argument :format, :default => 'haml'
58
+
59
+ def init_source
60
+ @source = Amiba::Source::Page.new(name, format)
61
+ end
62
+
63
+ def should_exist
64
+ if @source.new?
65
+ raise Thor::Error.new("Error: Can't publish a page that doesn't exist.")
66
+ end
67
+ end
68
+
69
+ def should_not_be_published
70
+ if @source.state == "published"
71
+ raise Thor::Error.new("Page already published")
72
+ end
73
+ end
74
+
75
+ def save_page
76
+ @source.state = "published"
77
+ @source.save do |filename, file_data|
78
+ remove_file filename, :verbose => false
79
+ create_file(filename, :verbose => false) do
80
+ file_data
81
+ end
82
+ say_status :published, filename, :green
83
+ end
84
+ end
85
+
86
+ def add_to_git
87
+ add_and_commit @source.filename, "Published #{@source.filename}"
88
+ end
89
+
90
+ end
91
+
92
+ # Thor task to destroy a page. It will delete all files matching the page name
93
+ class Destroy < Thor::Group
94
+ include Amiba::Generator
95
+
96
+ namespace :"page:destroy"
97
+ argument :name
98
+ argument :format, :default => 'haml'
99
+
100
+ def init_source
101
+ @source = Amiba::Source::Page.new(name, format)
102
+ end
103
+
104
+ def page
105
+ if ask("Are you sure you want to delete #{@source.filename}?" +
106
+ " This is irreversible (y/n): ")
107
+ remove_file(@source.filename)
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+ # Lists all pages currently managed by this Amiba project
114
+ class List < Thor::Group
115
+ include Amiba::Generator
116
+
117
+ namespace :"page:list"
118
+
119
+ def list
120
+ Dir.glob("pages/*").each {|p| say File.basename(p)}
121
+ end
122
+ end
123
+
124
+
125
+ # Hate this - will deprecate as soon as I think of a more elegant solution
126
+ class Templates
127
+ class << self
128
+ def haml
129
+ "%h1 Title\n%p Body\n"
130
+ end
131
+
132
+ def markdown
133
+ "# Title #\nBody\n"
134
+ end
135
+ end
136
+ end
137
+
138
+ end
139
+ end
data/lib/amiba/repo.rb ADDED
@@ -0,0 +1,28 @@
1
+ module Amiba
2
+ module Repo
3
+
4
+ def init(dir)
5
+ Grit::Repo.init(dir)
6
+ end
7
+
8
+ def repo
9
+ Grit::Repo.new(Dir.pwd)
10
+ rescue
11
+ raise "No repo exists at #{Dir.pwd}"
12
+ end
13
+
14
+ def add_and_commit(filename, msg=nil)
15
+ repo.add(filename)
16
+ repo.commit_index(msg || "Added a new entry at #{filename}")
17
+ end
18
+
19
+ def last_commit_date(filename)
20
+ repo.log(filename).first.committed_date
21
+ end
22
+
23
+ def last_commit_dates(*filenames)
24
+ filenames.map {|fn| last_commit_date(fn)}
25
+ end
26
+
27
+ end
28
+ end