io_tools 0.2.1

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