io_tools 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Christopher Burnett
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.mdown ADDED
@@ -0,0 +1,72 @@
1
+ ## io_tools ##
2
+
3
+
4
+
5
+ Extracted modules from the Posterous Blog Importer. Used for conforming RSS Feeds or results from one API to another.
6
+
7
+ ## Installation ##
8
+
9
+ gem install io_tools
10
+
11
+ ## Usage ##
12
+
13
+ Include the Importer.
14
+
15
+ class PosterousImporter
16
+ include IoTools::Importer
17
+ end
18
+
19
+ The Importer gives you a way to structure your import code. Moving data from one API to another consists of two *basic* steps, Collecting and Conforming. The rest of the work usually ends up being specific to the API that you are dealing with. So look in the `lib/server/importers` directory for how to deal with more complicated APIs that require helper methods.
20
+
21
+ The `collect` method.
22
+
23
+ class PosterousImporter
24
+ include IoTools::Importer
25
+
26
+ collect :feed_items
27
+
28
+ def feed_items
29
+ [{:title => "Fancy Post", :body => "Is fancy."}]
30
+ end
31
+
32
+ end
33
+
34
+ The `collect` method accepts a method name as a `symbol` or a block that will provide an `Array` of results from the API or feed. This method does not have to do all the work, all it needs to do is return an `Array`.
35
+
36
+ Hey wait, what format do the results have to be in?
37
+
38
+ It doesn't matter. This is where our friend the `conform` method comes in.
39
+
40
+ class PosterousImporter
41
+ include IoTools::Importer
42
+
43
+ collect :feed_items
44
+
45
+ conform do |incoming, conformed|
46
+ conformed.title = incoming[:title]
47
+ conformed.body = incoming[:body]
48
+ conformed
49
+ end
50
+
51
+ def feed_items
52
+ [{:title => "Fancy Post", :body => "Is fancy."}]
53
+ end
54
+
55
+ end
56
+
57
+ The conform method is called for every entry in the `Array` that is created by the `collect` method. This is where you turn crazy camelcase field names from weird APIs into nice clean attributes that suit your systems needs. `conform` is passed the incoming item from the `Array` produced by the `collect` method. Along with an empty `Openstruct` to hold whatever values your system needs. These in turn will populate the final `conformed_collection`.
58
+
59
+
60
+ ## Note on Patches/Pull Requests
61
+
62
+ * Fork the project.
63
+ * Make your feature addition or bug fix.
64
+ * Add tests for it. This is important so I don't break it in a
65
+ future version unintentionally.
66
+ * Commit, do not mess with rakefile, version, or history.
67
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
68
+ * Send me a pull request. Bonus points for topic branches.
69
+
70
+ ## Copyright
71
+
72
+ Copyright (c) 2010 Posterous Inc. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "io_tools"
8
+ gem.summary = %Q{Import framework for Blogs and Feeds}
9
+ gem.description = %Q{Extracted modules from the Posterous Blog Importer. Used for conforming RSS Feeds or results from one API to another.}
10
+ gem.email = "signalstatic@gmail.com"
11
+ gem.homepage = "http://github.com/posterous/io_tools"
12
+ gem.authors = ["Christopher Burnett"]
13
+ gem.executables = "io-server"
14
+ gem.add_development_dependency "rspec", ">= 1.2.9"
15
+ gem.add_development_dependency "mocha"
16
+
17
+ gem.add_dependency "nokogiri", ">= 1.4.2"
18
+ gem.add_dependency "feedtools", ">= 0.2.29"
19
+ gem.add_dependency "vegas", "~> 0.1.2"
20
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
21
+ end
22
+ Jeweler::GemcutterTasks.new
23
+ rescue LoadError
24
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
25
+ end
26
+
27
+ require 'spec/rake/spectask'
28
+ Spec::Rake::SpecTask.new(:spec) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.spec_files = FileList['spec/**/*_spec.rb']
31
+ end
32
+
33
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
34
+ spec.libs << 'lib' << 'spec'
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :spec => :check_dependencies
40
+
41
+ task :default => :spec
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "io_tools #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.1
data/bin/io-server ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ require 'server/server'
5
+
6
+ begin
7
+ require 'vegas'
8
+ rescue LoadError
9
+ require 'rubygems'
10
+ require 'vegas'
11
+ end
12
+
13
+ Vegas::Runner.new(IoTools::Server, 'io_server')
14
+
data/io_tools.gemspec ADDED
@@ -0,0 +1,90 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{io_tools}
8
+ s.version = "0.2.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Christopher Burnett"]
12
+ s.date = %q{2010-08-18}
13
+ s.default_executable = %q{io-server}
14
+ s.description = %q{Extracted modules from the Posterous Blog Importer. Used for conforming RSS Feeds or results from one API to another.}
15
+ s.email = %q{signalstatic@gmail.com}
16
+ s.executables = ["io-server"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.mdown"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".gitignore",
24
+ "LICENSE",
25
+ "README.mdown",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "bin/io-server",
29
+ "io_tools.gemspec",
30
+ "lib/io_tools.rb",
31
+ "lib/io_tools/helpers.rb",
32
+ "lib/io_tools/helpers/rss_feed.rb",
33
+ "lib/io_tools/helpers/xml_rpc.rb",
34
+ "lib/io_tools/importer.rb",
35
+ "lib/server/config.ru",
36
+ "lib/server/importers/posterous_api_importer.rb",
37
+ "lib/server/importers/posterous_feed_importer.rb",
38
+ "lib/server/importers/word_press_importer.rb",
39
+ "lib/server/public/images/bg.png",
40
+ "lib/server/public/javascripts/app.js",
41
+ "lib/server/public/stylesheets/app.css",
42
+ "lib/server/server.rb",
43
+ "lib/server/views/import.haml",
44
+ "lib/server/views/index.haml",
45
+ "spec/io_tools/helpers/rss_feed.rb",
46
+ "spec/io_tools/helpers/xml_rpc.rb",
47
+ "spec/io_tools/importer_spec.rb",
48
+ "spec/io_tools_spec.rb",
49
+ "spec/spec.opts",
50
+ "spec/spec_helper.rb"
51
+ ]
52
+ s.homepage = %q{http://github.com/posterous/io_tools}
53
+ s.rdoc_options = ["--charset=UTF-8"]
54
+ s.require_paths = ["lib"]
55
+ s.rubygems_version = %q{1.3.7}
56
+ s.summary = %q{Import framework for Blogs and Feeds}
57
+ s.test_files = [
58
+ "spec/io_tools/helpers/rss_feed.rb",
59
+ "spec/io_tools/helpers/xml_rpc.rb",
60
+ "spec/io_tools/importer_spec.rb",
61
+ "spec/io_tools_spec.rb",
62
+ "spec/spec_helper.rb"
63
+ ]
64
+
65
+ if s.respond_to? :specification_version then
66
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
67
+ s.specification_version = 3
68
+
69
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
70
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
71
+ s.add_development_dependency(%q<mocha>, [">= 0"])
72
+ s.add_runtime_dependency(%q<nokogiri>, [">= 1.4.2"])
73
+ s.add_runtime_dependency(%q<feedtools>, [">= 0.2.29"])
74
+ s.add_runtime_dependency(%q<vegas>, ["~> 0.1.2"])
75
+ else
76
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
77
+ s.add_dependency(%q<mocha>, [">= 0"])
78
+ s.add_dependency(%q<nokogiri>, [">= 1.4.2"])
79
+ s.add_dependency(%q<feedtools>, [">= 0.2.29"])
80
+ s.add_dependency(%q<vegas>, ["~> 0.1.2"])
81
+ end
82
+ else
83
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
84
+ s.add_dependency(%q<mocha>, [">= 0"])
85
+ s.add_dependency(%q<nokogiri>, [">= 1.4.2"])
86
+ s.add_dependency(%q<feedtools>, [">= 0.2.29"])
87
+ s.add_dependency(%q<vegas>, ["~> 0.1.2"])
88
+ end
89
+ end
90
+
@@ -0,0 +1,11 @@
1
+ module IoTools
2
+ module Helpers
3
+ module RssFeed
4
+
5
+ def feed_items_from url
6
+ FeedTools::Feed.open(url).items
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ module IoTools
2
+ module Helpers
3
+ module XmlRpc
4
+
5
+ def server
6
+ @server ||= XMLRPC::Client.new2(api_url, nil, 300)
7
+ end
8
+
9
+ def rpc method, *args
10
+ server.call("metaWeblog.#{method.to_s}", self.metaweblog_id, self.credentials[:username], self.credentials[:password], *args)
11
+ end
12
+
13
+ def recent_posts num
14
+ rpc :getRecentPosts, num
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ module IoTools
2
+ module Helpers
3
+
4
+ end
5
+ end
@@ -0,0 +1,45 @@
1
+ module IoTools
2
+ module Importer
3
+
4
+ def self.included klass
5
+ klass.class_eval do
6
+ attr_reader :params
7
+
8
+ extend ClassMethods
9
+ include InstanceMethods
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ def collect *args, &block
16
+ define_method :collection do
17
+ @collection ||= block_given? ? instance_eval(&block) : send(args.first)
18
+ end
19
+ end
20
+
21
+ def conform &block
22
+ define_method :conform do |post|
23
+ block.call(post, ItemStruct.new)
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ module InstanceMethods
30
+
31
+ def initialize opts={}
32
+ @params = opts
33
+ end
34
+
35
+ def conformed_collection
36
+ @conformed_collection ||= collection.collect{ |post| conform(post) }
37
+ end
38
+
39
+ def import!
40
+ conformed_collection
41
+ end
42
+
43
+ end
44
+ end
45
+ end
data/lib/io_tools.rb ADDED
@@ -0,0 +1,15 @@
1
+ %w{rubygems ostruct xmlrpc/client nokogiri open-uri feed_tools}.each { |f| require f }
2
+
3
+
4
+ require "io_tools/importer"
5
+ require "io_tools/helpers/rss_feed"
6
+ require "io_tools/helpers/xml_rpc"
7
+
8
+ module IoTools
9
+ VERSION = '0.0.1'
10
+ class ItemStruct < OpenStruct
11
+ def to_json *_
12
+ self.table.inject({}){|h,f| h[f.first] = f.last; h }.to_json
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ require 'sinatra'
2
+ require 'server'
3
+ run IoTools::Server
@@ -0,0 +1,74 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'io_tools'
5
+ require 'xmlsimple'
6
+
7
+ class PosterousApiImporter
8
+ include IoTools::Importer
9
+
10
+ PER_PAGE_LIMIT = 50
11
+
12
+ collect :paginated_results
13
+
14
+ conform do |incoming, conformed|
15
+ conformed.title = incoming['title']
16
+ conformed.body = incoming['body']
17
+ conformed.display_date = incoming['display_date']
18
+ conformed.author = incoming['author']
19
+ conformed.comments = self.extract_comments(incoming['comment'])
20
+ conformed
21
+ end
22
+
23
+ def self.extract_comments comments
24
+ return [] if comments.nil?
25
+ if comments.is_a? Array
26
+ comments.inject(IoTools::ItemStruct.new) do |comment,cmt|
27
+ comment.body = cmt['body']
28
+ comment.author = cmt['author']
29
+ comment.date = cmt['date']
30
+ comment
31
+ end
32
+ else
33
+ []
34
+ end
35
+ end
36
+
37
+ def paginated_results
38
+ items = []
39
+ while current_items = items_for_current_page
40
+ items += current_items
41
+ self.current_page += 1
42
+ end
43
+ items
44
+ end
45
+
46
+ def url
47
+ @url ||= URI.parse('http://posterous.com/api/readposts')
48
+ end
49
+
50
+ def items_for_current_page
51
+ XmlSimple.xml_in( self.response_for_current_page, 'ForceArray' => false )['post']
52
+ end
53
+
54
+ def response_for_current_page
55
+ Net::HTTP.new(url.host, url.port).start { |http| http.request(request) }.body
56
+ end
57
+
58
+ def request
59
+ @username, @password = self.params.delete('username'), self.params.delete('password') unless @username.present?
60
+ req = Net::HTTP::Get.new(url.path)
61
+ req.basic_auth @username, @password
62
+ req.set_form_data self.params.merge('num_posts' => PER_PAGE_LIMIT, 'page' => current_page)
63
+ req
64
+ end
65
+
66
+ def current_page
67
+ @current_page ||= 1
68
+ end
69
+
70
+ def current_page=(num)
71
+ @current_page = num
72
+ end
73
+
74
+ end
@@ -0,0 +1,42 @@
1
+
2
+ class PosterousFeedImporter
3
+ include IoTools::Importer
4
+ include IoTools::Helpers::RssFeed
5
+
6
+ MAX_PAGES = 2
7
+ FEED_SELECTOR = 'link[type="application/rss+xml"]'
8
+
9
+ collect :paginated_feed_items
10
+
11
+ conform do |incoming, conformed|
12
+ conformed.title = incoming.title
13
+ conformed.body = incoming.description
14
+ conformed.display_date = incoming.published
15
+ conformed
16
+ end
17
+
18
+ def paginated_feed_items
19
+ items = []
20
+ 1.upto(MAX_PAGES) do |num|
21
+ items += items_from_page(num)
22
+ end
23
+ items
24
+ end
25
+
26
+ def feed_url
27
+ @feed_url ||= Nokogiri::HTML(open(params[:feed_url])).css(FEED_SELECTOR).first['href']
28
+ end
29
+
30
+ def url_for_page num
31
+ puts "#{feed_url}?page=#{num}"
32
+ "#{feed_url}?page=#{num}"
33
+ end
34
+
35
+ def items_from_page num
36
+ feed_items_from(url_for_page(num))
37
+ end
38
+
39
+ end
40
+
41
+ # wp = PosterousFeedImporter.new(:feed_url => 'http://twoism.posterous.com')
42
+ # p wp.import!
@@ -0,0 +1,64 @@
1
+ %w{open-uri nokogiri xmlrpc/client}.each { |f| require f }
2
+
3
+ class WordPressImporter
4
+ include IoTools::Importer
5
+ include IoTools::Helpers::XmlRpc
6
+
7
+ RSD_SELECTOR = 'link[rel=EditURI]'
8
+ METAWEB_SELECTOR = 'api[name=MetaWeblog]'
9
+ MAX_POSTS_TO_REQUEST = 20
10
+
11
+ collect do
12
+ recent_posts MAX_POSTS_TO_REQUEST
13
+ end
14
+
15
+ conform do
16
+ set :title, entry['title']
17
+ set :body, entry['description']
18
+ set :display_date, entry['dateCreated'].to_time.utc
19
+ set :tags, entry['mt_keywords'].split(",")
20
+ end
21
+
22
+ # Methods below are helpers for detecting RSD for the
23
+ # XML-RPC protocol.
24
+ def scan_for_url
25
+ rsd_doc.css( METAWEB_SELECTOR ).first['apilink']
26
+ end
27
+
28
+ def site_doc
29
+ @site_doc ||= Nokogiri::HTML(html)
30
+ end
31
+
32
+ def html
33
+ @html ||= open self.import_url
34
+ end
35
+
36
+ def rsd_url
37
+ @rsd_url ||= scan_for_rsd
38
+ end
39
+
40
+ def scan_for_rsd
41
+ site_doc.css( RSD_SELECTOR ).first['href']
42
+ end
43
+
44
+ def scan_for_id
45
+ rsd_doc.css( METAWEB_SELECTOR ).first['blogid']
46
+ end
47
+
48
+ def rsd_doc
49
+ @rsd_doc ||= Nokogiri::HTML(rsd_html)
50
+ end
51
+
52
+ def rsd_html
53
+ @rsd_html ||= open(rsd_url)
54
+ end
55
+
56
+ def metaweblog_id
57
+ @metaweblog_id ||= scan_for_id
58
+ end
59
+
60
+ def api_url
61
+ @api_url ||= scan_for_url
62
+ end
63
+
64
+ end
Binary file
@@ -0,0 +1,23 @@
1
+ $(function(){
2
+
3
+ var service = null;
4
+
5
+ $('#service').change(function(){
6
+ service = $(this).val();
7
+ if (service != '') {
8
+ $('.login_info').slideUp('medium',function(){
9
+ $('#'+service).slideDown('medium');
10
+ });
11
+ }
12
+ });
13
+ $('.login_info').submit(function(e){
14
+ e.preventDefault();
15
+ $('#loading').show();
16
+ $('#posts').html("");
17
+ var params = $('#'+service).serialize();
18
+ $.post('/import', params ,function(data){
19
+ $('#posts').html(data);
20
+ $('#loading').hide();
21
+ });
22
+ });
23
+ });
@@ -0,0 +1,164 @@
1
+ body {font-size: 16px; line-height: 1.25em; margin: 0; padding: 0; }
2
+
3
+ html, body {background: #eee url(/images/bg.png); font-family: "helvetica neue", arial, sans-serif}
4
+
5
+ h1 {margin: 0; font-size: 1.5em; line-height: 0.833em; margin-bottom: 0.833em}
6
+ h2 {margin: 0; font-size: 1.375em; line-height: 0.909em; margin-bottom: 0.909em}
7
+ h3 {margin: 0; font-size: 1.25em; line-height: 1em; margin-bottom: 1em}
8
+ h4 {margin: 0; font-size: 1.125em; line-height: 1.111em; margin-bottom: 1.111em}
9
+
10
+ p, ul, blockquote, pre, td, th, label {margin: 0; font-size: 1em; line-height: 1.25em; margin-bottom: 1.25em}
11
+
12
+ .panel {border-radius: 8px; -moz-border-radius: 8px; -webkit-border-radius: 8px; -khtml-border-radius: 8px; behavior: url(stylesheets/border-radius.htc); box-shadow: 0 0 2px #eeeeee; -moz-box-shadow: 0 0 2px #eeeeee; -webkit-box-shadow: 0 0 2px #eeeeee; -khtml-box-shadow: 0 0 2px #eeeeee; border: 1px solid #ccc; background-color: #fff; margin-bottom: 1.25em}
13
+ .panel .panel_title {-moz-border-radius-topleft: 8px; border-top-left-radius: 8px; -webkit-border-top-left-radius: 8px; -khtml-border-top-left-radius: 8px; -moz-border-radius-topright: 8px; border-top-right-radius: 8px; -webkit-border-top-right-radius: 8px; -khtml-border-top-right-radius: 8px; background: white url(/images/panel_title_bg.gif) 0 bottom repeat-x; line-height: 47px; font-size: 1.125em; color: #4b4f5d; font-weight: bold; padding: 0 24px; border-bottom: 1px solid #ccc; box-shadow: 0 0 2px #eeeeee; -moz-box-shadow: 0 0 2px #eeeeee; -webkit-box-shadow: 0 0 2px #eeeeee; -khtml-box-shadow: 0 0 2px #eeeeee; text-shadow: #fff 0px 1px 0px;}
14
+ .panel .panel_title .octocat {vertical-align: middle; margin-right: 5px}
15
+ .panel .panel_title a.add-new {float: right; font-size: 0.667em; border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; -khtml-border-radius: 3px; behavior: url(stylesheets/border-radius.htc); border: 1px solid #ccc; line-height: 1.667em; padding: 5px 7px; margin: 8px 0 0 5px; background: #d4d4d4 url(/images/gray-gradient-pagination-bg.png) 0 0 repeat-x; color: #4B4F5D}
16
+ .panel .panel_title a.add-new:hover {box-shadow: 0 1px 3px #666666; -moz-box-shadow: 0 1px 3px #666666; -webkit-box-shadow: 0 1px 3px #666666; -khtml-box-shadow: 0 1px 3px #666666; border-color: #ccc; background-image: none; background-color: #fff}
17
+ .panel .panel_content {padding: 1em}
18
+
19
+ #container {
20
+ width:450px;
21
+ margin:auto;
22
+ border-bottom:1px #ccc solid;
23
+ clear:right;
24
+ }
25
+
26
+
27
+ #header {
28
+ border-bottom:1px solid #ccc;
29
+ height:25px;
30
+ padding:1em;
31
+ }
32
+
33
+ #header h2{
34
+ text-shadow: #fff 0px 1px 0px;
35
+ color:#4b4f5d;
36
+ }
37
+
38
+ select#service{
39
+
40
+ }
41
+
42
+ #left_nav {
43
+ padding:1em;
44
+ width:200px;
45
+ float:left;
46
+ }
47
+
48
+ #left_nav ul {
49
+ list-style:none;
50
+ padding:0;
51
+ }
52
+ #main {
53
+ padding:1em;
54
+ float:left;
55
+
56
+ }
57
+
58
+ .login_info{
59
+
60
+ float:left;
61
+ }
62
+
63
+ .login_info p{
64
+ height:30px;
65
+ width:100%;
66
+ float:left;
67
+ padding:0px 0px 3px 0px;
68
+ }
69
+
70
+
71
+
72
+ .login_info label{
73
+ position:relative;
74
+ font-weight:bold;
75
+ float:left;
76
+ color:#4B4F5D;
77
+ text-shadow:0 1px 0 #FFFFFF;
78
+ clear:left;
79
+ font-size:14px;
80
+ letter-spacing:.02em;
81
+ bottom:-8px;
82
+ }
83
+
84
+ .login_info input{
85
+ border:1px solid #DEDEDE;
86
+ float:right;
87
+ background-color:#F6F6F6;
88
+ padding:10px;
89
+
90
+ -webkit-border-top-left-radius: 4px;
91
+ -webkit-border-top-right-radius: 4px;
92
+ -webkit-border-bottom-right-radius: 4px;
93
+ -webkit-border-bottom-left-radius: 4px;
94
+ -moz-border-radius-topleft: 4px;
95
+ -moz-border-radius-topright: 4px;
96
+ -moz-border-radius-bottomright: 4px;
97
+ -moz-border-radius-bottomleft: 4px;
98
+ border-top-left-radius: 4px;
99
+ border-top-right-radius: 4px;
100
+ border-bottom-right-radius: 4px;
101
+ border-bottom-left-radius: 4px;
102
+
103
+ }
104
+
105
+ #chooser{margin:auto;}
106
+
107
+ input.text_field{
108
+ width:315px;
109
+ }
110
+
111
+
112
+
113
+ .login_info p.last{
114
+ padding-bottom:0px;
115
+ margin:0px;
116
+
117
+ }
118
+
119
+ .login_info p input[type="submit"]{
120
+ background-color:#F6F6F6;
121
+ text-shadow:0 1px 0 #DEDEDE;
122
+ font-weight:bold;
123
+ width:75px;
124
+ height:30px;
125
+ padding:0px 0px 2px 0px;
126
+ }
127
+
128
+ .login_info p input[type="submit"]:hover{
129
+ background-color:#DEDEDE;
130
+ }
131
+
132
+ .post{
133
+ background:none repeat scroll 0 0 #FFFFFF;
134
+ border:1px solid #CCCCCC;
135
+ margin-bottom:1em;
136
+ padding:1em;
137
+ }
138
+
139
+ .post_title{
140
+ background:none repeat scroll 0 0 #EEEEEE;
141
+ margin-bottom:10px;
142
+ padding:0.5em;
143
+ }
144
+
145
+ .post_body{
146
+ background:none repeat scroll 0 0 #EEEEEE;
147
+ margin-bottom:10px;
148
+ width:386px;
149
+ overflow:auto;
150
+ padding:0.5em;
151
+ }
152
+
153
+ .post_tags{
154
+ background:none repeat scroll 0 0 #EEEEEE;
155
+ margin-bottom:10px;
156
+ padding:0.5em;
157
+ }
158
+
159
+ textarea{
160
+ border:1px solid #CCCCCC;
161
+ margin:10px 0;
162
+ width:430px;
163
+ height:200px;
164
+ }
@@ -0,0 +1,36 @@
1
+ %w{rubygems sinatra haml}.each { |f| require f }
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '../..', 'lib'))
5
+
6
+ require 'io_tools'
7
+ dir = File.expand_path(File.dirname(__FILE__))
8
+
9
+ require dir+'/importers/posterous_feed_importer'
10
+ require dir+'/importers/posterous_api_importer'
11
+
12
+
13
+
14
+ module IoTools
15
+
16
+
17
+ class Server < Sinatra::Base
18
+ dir = File.dirname(File.expand_path(__FILE__))
19
+
20
+ set :views, "#{dir}/views"
21
+ set :public, "#{dir}/public"
22
+
23
+ get "/" do
24
+ haml :index
25
+ end
26
+
27
+ post '/import' do
28
+ klass = params.delete('service').camelize.constantize
29
+ @import = klass.new(params)
30
+ @posts = @import.import!
31
+ haml :import
32
+ end
33
+
34
+ end
35
+ end
36
+
@@ -0,0 +1,18 @@
1
+ %h4 Exported JSON
2
+ %textarea{:id => "xml_export"}
3
+ = @posts.to_json
4
+
5
+ %h4 Posts
6
+ - @posts.each do |post|
7
+ .post
8
+ .post_title
9
+ %h4 Title
10
+ %p
11
+ = "#{post.title} - #{post.display_date}"
12
+ .post_body
13
+ %h4 Body
14
+ = post.body
15
+ .post_tags
16
+ %h4 Tags
17
+ %p
18
+ = post.tags.join(",") unless post.tags.nil?
@@ -0,0 +1,71 @@
1
+ !!!
2
+ %html
3
+ %head
4
+ %title
5
+ Importer
6
+ %script{:src => 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js', :type => 'text/javascript'}
7
+ %script{:src => '/javascripts/app.js', :type => 'text/javascript'}
8
+ %link{:type => 'text/css', :rel=>"stylesheet", :href => '/stylesheets/app.css' }
9
+
10
+ %body
11
+ #container
12
+ #header
13
+ %h2 Export Tools
14
+ #main
15
+
16
+ #chooser
17
+ %p
18
+ %select{:id => 'service'}
19
+ %option{ :value => '' }
20
+ Choose Blog Service:
21
+ %option{ :value => 'posterous_api_importer' }
22
+ Posterous API
23
+ %option{ :value => 'posterous_feed_importer' }
24
+ Posterous Feed
25
+
26
+ %form.login_info{:style => 'display:none', :id => 'posterous_api_importer', :method => "POST"}
27
+ %p
28
+ %label
29
+ Email:
30
+ %input{:name => "username", :type => 'text', :value => '',:class=>"text_field"}
31
+ %br{:clear => "all"}
32
+ %p
33
+ %label
34
+ Password:
35
+ %input{:name => "password", :type => 'password', :value => '',:class=>"text_field"}
36
+ %p
37
+ %label
38
+ Hostname:
39
+ %input{:name => "hostname", :type => 'text', :value => '',:class=>"text_field"}
40
+ %input{:name => "service", :type => 'hidden', :value => 'posterous_api_importer',:class=>"text_field"}
41
+
42
+
43
+ %p
44
+ %input{:id => "submit_btn",:value => 'Export', :type => "submit"}
45
+
46
+ %br{:clear => "all"}
47
+
48
+ %form.login_info{:style => 'display:none', :id => 'posterous_feed_importer', :method => "POST"}
49
+ %p
50
+ %label
51
+ Site URL:
52
+ %input{:name => "feed_url", :type => 'text', :value => '',:class=>"text_field"}
53
+ %input{:name => "service", :type => 'hidden', :value => 'posterous_feed_importer',:class=>"text_field"}
54
+
55
+
56
+ %p
57
+ %input{:id => "submit_btn",:value => 'Export', :type => "submit"}
58
+
59
+ %br{:clear => "all"}
60
+
61
+
62
+
63
+ #loading{:style => 'display:none'}
64
+ %p Loading...
65
+
66
+
67
+ %br{:clear => "all"}
68
+ #posts
69
+
70
+ %br{:clear => "all"}
71
+
@@ -0,0 +1,5 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe IoTools::Helpers::RssFeed do
4
+ it "pending"
5
+ end
@@ -0,0 +1,5 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe IoTools::Helpers::XmlRpc do
4
+ it "pending"
5
+ end
@@ -0,0 +1,62 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe IoTools::Importer do
4
+ describe "#new" do
5
+
6
+ describe "with an import url" do
7
+ before(:each) do
8
+ @import = Import.new(:import_url => 'http://sweetness.com')
9
+ end
10
+ it "should set the :import_url" do
11
+ @import.params[:import_url].should == 'http://sweetness.com'
12
+ end
13
+ end
14
+ end
15
+
16
+ describe "#conform" do
17
+ before(:each) do
18
+ @import = Import.new(:import_url => 'http://sweetness.com')
19
+ @hash = ENTRY_HASH
20
+ @conformed = @import.conform(@hash)
21
+ end
22
+
23
+ describe "conform hash keys" do
24
+ ENTRY_HASH.each do |k,v|
25
+ it "should set :#{k} to #{v}" do
26
+ @conformed.send(k).should == v
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ describe "#collection" do
33
+ before(:each) do
34
+ @import = Import.new(:import_url => 'http://sweetness.com')
35
+ end
36
+
37
+ it "should be an Array" do
38
+ @import.collection.should be_an Array
39
+ end
40
+
41
+ it "should contain the hashes" do
42
+ @import.collection.each{|e| e.should be_a Hash }
43
+ end
44
+
45
+ end
46
+
47
+ describe "#conformed_collection" do
48
+ before(:each) do
49
+ @import = Import.new(:import_url => 'http://sweetness.com')
50
+ end
51
+
52
+ it "should be an Array" do
53
+ @import.conformed_collection.should be_an Array
54
+ end
55
+
56
+ it "should contain the conformed structs" do
57
+ @import.conformed_collection.each{|e| e.should be_a IoTools::ItemStruct }
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,5 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "IoTools" do
4
+
5
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format nested
3
+ --loadby mtime
4
+ --backtrace
@@ -0,0 +1,28 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'io_tools'
5
+ require 'spec'
6
+ require 'spec/autorun'
7
+ require 'mocha'
8
+
9
+ Spec::Runner.configure do |config|
10
+ config.mock_with :mocha
11
+ end
12
+
13
+ ENTRY_HASH = {:title => "Sweet", :body => "An awesome post!"}
14
+
15
+ class Import
16
+ include IoTools::Importer
17
+
18
+ conform do |incoming,conformed|
19
+ conformed.title = incoming[:title]
20
+ conformed.body = incoming[:body]
21
+ conformed
22
+ end
23
+
24
+ collect do
25
+ [ENTRY_HASH,ENTRY_HASH]
26
+ end
27
+
28
+ end
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: io_tools
3
+ version: !ruby/object:Gem::Version
4
+ hash: 21
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 1
10
+ version: 0.2.1
11
+ platform: ruby
12
+ authors:
13
+ - Christopher Burnett
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-08-18 00:00:00 -07:00
19
+ default_executable: io-server
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 13
30
+ segments:
31
+ - 1
32
+ - 2
33
+ - 9
34
+ version: 1.2.9
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: mocha
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ type: :development
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: nokogiri
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 1
62
+ - 4
63
+ - 2
64
+ version: 1.4.2
65
+ type: :runtime
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: feedtools
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 45
76
+ segments:
77
+ - 0
78
+ - 2
79
+ - 29
80
+ version: 0.2.29
81
+ type: :runtime
82
+ version_requirements: *id004
83
+ - !ruby/object:Gem::Dependency
84
+ name: vegas
85
+ prerelease: false
86
+ requirement: &id005 !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ~>
90
+ - !ruby/object:Gem::Version
91
+ hash: 31
92
+ segments:
93
+ - 0
94
+ - 1
95
+ - 2
96
+ version: 0.1.2
97
+ type: :runtime
98
+ version_requirements: *id005
99
+ description: Extracted modules from the Posterous Blog Importer. Used for conforming RSS Feeds or results from one API to another.
100
+ email: signalstatic@gmail.com
101
+ executables:
102
+ - io-server
103
+ extensions: []
104
+
105
+ extra_rdoc_files:
106
+ - LICENSE
107
+ - README.mdown
108
+ files:
109
+ - .document
110
+ - .gitignore
111
+ - LICENSE
112
+ - README.mdown
113
+ - Rakefile
114
+ - VERSION
115
+ - bin/io-server
116
+ - io_tools.gemspec
117
+ - lib/io_tools.rb
118
+ - lib/io_tools/helpers.rb
119
+ - lib/io_tools/helpers/rss_feed.rb
120
+ - lib/io_tools/helpers/xml_rpc.rb
121
+ - lib/io_tools/importer.rb
122
+ - lib/server/config.ru
123
+ - lib/server/importers/posterous_api_importer.rb
124
+ - lib/server/importers/posterous_feed_importer.rb
125
+ - lib/server/importers/word_press_importer.rb
126
+ - lib/server/public/images/bg.png
127
+ - lib/server/public/javascripts/app.js
128
+ - lib/server/public/stylesheets/app.css
129
+ - lib/server/server.rb
130
+ - lib/server/views/import.haml
131
+ - lib/server/views/index.haml
132
+ - spec/io_tools/helpers/rss_feed.rb
133
+ - spec/io_tools/helpers/xml_rpc.rb
134
+ - spec/io_tools/importer_spec.rb
135
+ - spec/io_tools_spec.rb
136
+ - spec/spec.opts
137
+ - spec/spec_helper.rb
138
+ has_rdoc: true
139
+ homepage: http://github.com/posterous/io_tools
140
+ licenses: []
141
+
142
+ post_install_message:
143
+ rdoc_options:
144
+ - --charset=UTF-8
145
+ require_paths:
146
+ - lib
147
+ required_ruby_version: !ruby/object:Gem::Requirement
148
+ none: false
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ hash: 3
153
+ segments:
154
+ - 0
155
+ version: "0"
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ none: false
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ hash: 3
162
+ segments:
163
+ - 0
164
+ version: "0"
165
+ requirements: []
166
+
167
+ rubyforge_project:
168
+ rubygems_version: 1.3.7
169
+ signing_key:
170
+ specification_version: 3
171
+ summary: Import framework for Blogs and Feeds
172
+ test_files:
173
+ - spec/io_tools/helpers/rss_feed.rb
174
+ - spec/io_tools/helpers/xml_rpc.rb
175
+ - spec/io_tools/importer_spec.rb
176
+ - spec/io_tools_spec.rb
177
+ - spec/spec_helper.rb