ruby-tumblr 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2007-08-20
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/License.txt ADDED
@@ -0,0 +1,56 @@
1
+ Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
2
+ You can redistribute it and/or modify it under either the terms of the GPL
3
+ (see the file GPL), or the conditions below:
4
+
5
+ 1. You may make and give away verbatim copies of the source form of the
6
+ software without restriction, provided that you duplicate all of the
7
+ original copyright notices and associated disclaimers.
8
+
9
+ 2. You may modify your copy of the software in any way, provided that
10
+ you do at least ONE of the following:
11
+
12
+ a) place your modifications in the Public Domain or otherwise
13
+ make them Freely Available, such as by posting said
14
+ modifications to Usenet or an equivalent medium, or by allowing
15
+ the author to include your modifications in the software.
16
+
17
+ b) use the modified software only within your corporation or
18
+ organization.
19
+
20
+ c) give non-standard binaries non-standard names, with
21
+ instructions on where to get the original software distribution.
22
+
23
+ d) make other distribution arrangements with the author.
24
+
25
+ 3. You may distribute the software in object code or binary form,
26
+ provided that you do at least ONE of the following:
27
+
28
+ a) distribute the binaries and library files of the software,
29
+ together with instructions (in the manual page or equivalent)
30
+ on where to get the original distribution.
31
+
32
+ b) accompany the distribution with the machine-readable source of
33
+ the software.
34
+
35
+ c) give non-standard binaries non-standard names, with
36
+ instructions on where to get the original software distribution.
37
+
38
+ d) make other distribution arrangements with the author.
39
+
40
+ 4. You may modify and include the part of the software into any other
41
+ software (possibly commercial). But some files in the distribution
42
+ are not written by the author, so that they are not under these terms.
43
+
44
+ For the list of those files and their copying conditions, see the
45
+ file LEGAL.
46
+
47
+ 5. The scripts and library files supplied as input to or produced as
48
+ output from the software do not automatically fall under the
49
+ copyright of the software, but belong to whomever generated them,
50
+ and may be sold commercially, and may be aggregated with this
51
+ software.
52
+
53
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
54
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
55
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
56
+ PURPOSE.
data/Manifest.txt ADDED
@@ -0,0 +1,16 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ lib/tumblr.rb
7
+ lib/tumblr/version.rb
8
+ scripts/txt2html
9
+ setup.rb
10
+ test/test_helper.rb
11
+ test/test_ruby-tumblr.rb
12
+ website/index.html
13
+ website/index.txt
14
+ website/javascripts/rounded_corners_lite.inc.js
15
+ website/stylesheets/screen.css
16
+ website/template.rhtml
data/README.txt ADDED
@@ -0,0 +1,6 @@
1
+ README for ruby-tumblr
2
+ ======================
3
+ ruby-tumblr is a library for tumblr API.
4
+
5
+ Copyright (c) 2007 Keita Yamaguchi
6
+
data/Rakefile ADDED
@@ -0,0 +1,123 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'fileutils'
10
+ require 'hoe'
11
+
12
+ include FileUtils
13
+ require File.join(File.dirname(__FILE__), 'lib', 'tumblr', 'version')
14
+
15
+ AUTHOR = 'Keita Yamaguchi' # can also be an array of Authors
16
+ EMAIL = "keita.yamaguchi@gmail.com"
17
+ DESCRIPTION = "ruby-tumblr is a library for tumblr API."
18
+ GEM_NAME = 'ruby-tumblr' # what ppl will type to install your gem
19
+
20
+ @config_file = "~/.rubyforge/user-config.yml"
21
+ @config = nil
22
+ def rubyforge_username
23
+ unless @config
24
+ begin
25
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
26
+ rescue
27
+ puts <<-EOS
28
+ ERROR: No rubyforge config file found: #{@config_file}"
29
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
30
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
31
+ EOS
32
+ exit
33
+ end
34
+ end
35
+ @rubyforge_username ||= @config["username"]
36
+ end
37
+
38
+ RUBYFORGE_PROJECT = 'ruby-tumblr' # The unix name for your project
39
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
40
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
41
+
42
+ NAME = "ruby-tumblr"
43
+ REV = nil
44
+ # UNCOMMENT IF REQUIRED:
45
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
46
+ VERS = Tumblr::VERSION::STRING + (REV ? ".#{REV}" : "")
47
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store']
48
+ RDOC_OPTS = ['--quiet', '--title', 'ruby-tumblr documentation',
49
+ "--opname", "index.html",
50
+ "--line-numbers",
51
+ "--main", "README",
52
+ "--inline-source"]
53
+
54
+ class Hoe
55
+ def extra_deps
56
+ @extra_deps.reject { |x| Array(x).first == 'hoe' }
57
+ end
58
+ end
59
+
60
+ # Generate all the Rake tasks
61
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
62
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
63
+ p.author = AUTHOR
64
+ p.description = DESCRIPTION
65
+ p.email = EMAIL
66
+ p.summary = DESCRIPTION
67
+ p.url = HOMEPATH
68
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
69
+ p.test_globs = ["test/**/test_*.rb"]
70
+ p.clean_globs |= CLEAN #An array of file patterns to delete on clean.
71
+
72
+ # == Optional
73
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
74
+ p.extra_deps = [['tzinfo', '>= 0.3.3']]
75
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
76
+ end
77
+
78
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\n\n")
79
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
80
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
81
+
82
+ desc 'Generate website files'
83
+ task :website_generate do
84
+ Dir['website/**/*.txt'].each do |txt|
85
+ sh %{ ruby scripts/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} }
86
+ end
87
+ end
88
+
89
+ desc 'Upload website files to rubyforge'
90
+ task :website_upload do
91
+ host = "#{rubyforge_username}@rubyforge.org"
92
+ remote_dir = "/var/www/gforge-projects/#{PATH}/"
93
+ local_dir = 'website'
94
+ sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}}
95
+ end
96
+
97
+ desc 'Generate and upload website files'
98
+ task :website => [:website_generate, :website_upload, :publish_docs]
99
+
100
+ desc 'Release the website and new gem version'
101
+ task :deploy => [:check_version, :website, :release] do
102
+ puts "Remember to create SVN tag:"
103
+ puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " +
104
+ "svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} "
105
+ puts "Suggested comment:"
106
+ puts "Tagging release #{CHANGES}"
107
+ end
108
+
109
+ desc 'Runs tasks website_generate and install_gem as a local deployment of the gem'
110
+ task :local_deploy => [:website_generate, :install_gem]
111
+
112
+ task :check_version do
113
+ unless ENV['VERSION']
114
+ puts 'Must pass a VERSION=x.y.z release version'
115
+ exit
116
+ end
117
+ unless ENV['VERSION'] == VERS
118
+ puts "Please update your version.rb to match the release version, currently #{VERS}"
119
+ exit
120
+ end
121
+ end
122
+
123
+
@@ -0,0 +1,9 @@
1
+ module Tumblr #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/lib/tumblr.rb ADDED
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "rexml/document"
6
+ require "tzinfo"
7
+ require "time"
8
+
9
+ class Tumblr
10
+ class Data
11
+ attr_accessor :tumblelog, :posts
12
+
13
+ def initialize(doc = nil)
14
+ if doc
15
+ @tumblelog = Tumblelog.new(REXML::XPath.first(doc, "//tumblelog"))
16
+ @posts = Posts.new(REXML::XPath.first(doc, "//posts"), @tumblelog.timezone)
17
+ end
18
+ end
19
+
20
+ def self.load(path)
21
+ xml.each do |post|
22
+ case post.attributes["type"]
23
+ when "regular"
24
+ Regular.new()
25
+ when "quote"
26
+ Quote.new(post)
27
+ end
28
+ end
29
+ end
30
+
31
+ def save(path)
32
+ File.open(path, "w") do |file|
33
+ doc = REXML::Document.new
34
+ root = doc.add_element "ruby-tumblr", {"version" => "0.1"}
35
+ root.elements << @tumblelog.to_xml if @tumblelog
36
+ root.elements << @posts.to_xml if @posts
37
+ doc.write(file)
38
+ end
39
+ end
40
+
41
+ def <<(other)
42
+ @tumblelog = other.tumblelog unless @tumblelog
43
+ @posts ? @posts << other.posts : @posts = other.posts
44
+ end
45
+
46
+ class Tumblelog
47
+ attr_accessor :name, :timezone, :cname, :title, :description
48
+
49
+ def initialize(elt)
50
+ @name = elt.attributes["name"]
51
+ @timezone = TZInfo::Timezone.get(elt.attributes["timezone"])
52
+ @cname = elt.attributes["cname"]
53
+ @title = elt.attributes["title"]
54
+ @description = elt.attributes["description"]
55
+ end
56
+
57
+ def to_xml
58
+ elt = REXML::Element.new("tumblelog")
59
+ elt.attributes["name"] = @name
60
+ elt.attributes["timezone"] = @timezone.name
61
+ elt.attributes["cname"] = @cname
62
+ elt.attributes["title"] = @title
63
+ elt.text = description
64
+ return elt
65
+ end
66
+ end
67
+
68
+ class Posts < Array
69
+ attr_accessor :total, :start, :type
70
+
71
+ def initialize(elt, tz)
72
+ @total = elt.attributes["total"].to_i
73
+ @start = elt.attributes["start"].to_i if elt.attributes.has_key? "start"
74
+ @type = elt.attributes["type"]
75
+
76
+ elt.elements.each("post") do |e|
77
+ push((case e.attributes["type"]
78
+ when "regular"; Regular
79
+ when "quote"; Quote
80
+ when "photo"; Photo
81
+ when "link"; Link
82
+ when "video"; Video
83
+ when "conversation"; Conversation
84
+ end).new(e, tz))
85
+ end
86
+ end
87
+
88
+ def to_xml
89
+ elt = REXML::Element.new("posts")
90
+ elt.attributes["total"] = @total
91
+ elt.attributes["type"] = @type
92
+ each do |post|
93
+ elt.elements << post.to_xml
94
+ end
95
+ return elt
96
+ end
97
+ end
98
+
99
+ class Post
100
+ attr_reader :postid, :url, :date, :bookmarklet
101
+
102
+ def initialize(elt, tz)
103
+ @postid = elt.attributes["id"]
104
+ @url = elt.attributes["url"]
105
+ @date = Time.parse(elt.attributes["date"] + tz.strftime("%Z"))
106
+ @bookmarklet = (elt.attributes["bookmarklet"] == "true")
107
+ @timezone = tz
108
+ end
109
+
110
+ def to_xml
111
+ elt = REXML::Element.new("post")
112
+ elt.attributes["postid"] = @postid
113
+ elt.attributes["date"] = @date.strftime("%a, %d %b %Y %X")
114
+ elt.attributes["bookmarklet"] = "true" if @bookmark
115
+ elt.attributes["url"] = @url
116
+ return elt
117
+ end
118
+ end
119
+
120
+ class Regular < Post
121
+ attr_accessor :title, :body
122
+
123
+ def initialize(elt, tz)
124
+ super
125
+ @title = elt.elements["regular-title"].text if elt.elements["regular-title"]
126
+ @body = elt.elements["regular-body"].text if elt.elements["regular-body"]
127
+ end
128
+
129
+ def to_xml
130
+ elt = super
131
+ title = elt.add_element("regular-title")
132
+ title.text = @title
133
+ body = elt.add_element("regular-body")
134
+ body.text = @body
135
+ return elt
136
+ end
137
+ end
138
+
139
+ class Quote < Post
140
+ attr_accessor :text, :source
141
+
142
+ def initialize(elt, tz)
143
+ super
144
+ @text = elt.elements["quote-text"].text
145
+ @source = elt.elements["quote-source"].text
146
+ end
147
+
148
+ def to_xml
149
+ elt = super
150
+ et = elt.add_element("quote-text")
151
+ et.text = @text
152
+ es = elt.add_element("quote-source")
153
+ es.text = @source
154
+ return elt
155
+ end
156
+ end
157
+
158
+ class Photo < Post
159
+ attr_accessor :caption, :urls
160
+
161
+ def initialize(elt, tz)
162
+ super
163
+ @caption = elt.elements["photo-caption"].text
164
+ @urls = Hash.new
165
+ elt.elements.each("photo-url") do |url|
166
+ @urls[url.attributes["max-width"].to_i] = url.text
167
+ end
168
+ end
169
+
170
+ def to_xml
171
+ elt = super
172
+ caption = elt.add_element "photo-caption"
173
+ caption.text = @caption
174
+ @urls.each do |width, url|
175
+ e = elt.add_element "photo-url", {"max-width" => width}
176
+ e.text = url
177
+ end
178
+ return elt
179
+ end
180
+ end
181
+
182
+ class Link < Post
183
+ attr_accessor :name, :url, :description
184
+
185
+ def initialize(elt, tz)
186
+ super
187
+ @text = elt.elements["link-text"].text if elt.elements["link-text"]
188
+ @url = elt.elements["link-url"].text
189
+ @description = elt.elements["link-description"].text if elt.elements["link-description"]
190
+ end
191
+
192
+ def to_xml
193
+ elt = super
194
+ name = elt.add_element "link-text"
195
+ name.text = @text
196
+ url = elt.add_element "link-url"
197
+ url.text = @url
198
+ description = elt.add_element "link-description"
199
+ description.text = @description
200
+ return elt
201
+ end
202
+ end
203
+
204
+ class Conversation < Post
205
+ attr_accessor :title, :lines
206
+
207
+ def initialize(elt, tz)
208
+ super
209
+ @title = elt.elements["conversation-title"].text
210
+ @text = elt.elements["conversation-text"].text
211
+ @lines = []
212
+ elt.each("conversation-line") do |line|
213
+ name = line.attributes["name"]
214
+ label = line.attributes["label"]
215
+ @lines << [name, label, line.text]
216
+ end
217
+ end
218
+
219
+ def to_xml
220
+ elt = super
221
+ title = elt.add_element "conversation-title"
222
+ title.text = @title
223
+ text = elt.add_element "conversation-text"
224
+ text.text = @text
225
+ @lines.each do |line|
226
+ e = elt.add_element "conversation-line", {"name" => line[0], "label" => line[1]}
227
+ e.text = line[2]
228
+ end
229
+ return elt
230
+ end
231
+ end
232
+
233
+ class Video < Post
234
+ def initialize(elt, tz)
235
+ super
236
+ @caption = elt.elements["video-caption"].text
237
+ @source = elt.elements["video-source"].text
238
+ @player = elt.attributes["video-player"]
239
+ end
240
+
241
+ def to_xml
242
+ elt = super
243
+ caption = elt.add_element "video-caption"
244
+ caption.text = @caption
245
+ player = elt.add_element "video-player"
246
+ player.text = @player
247
+ source = elt.add_element "video-source"
248
+ source.text = @source
249
+ return elt
250
+ end
251
+ end
252
+ end
253
+
254
+ module API
255
+ class ResponseError < StandardError
256
+ attr_reader :response
257
+ def initialize(response)
258
+ @response = response
259
+ end
260
+ end
261
+
262
+ class AuthError < StandardError; end
263
+
264
+ class BadRequestError < StandardError
265
+ attr_reader :message
266
+ def initialize(message)
267
+ @message = message
268
+ end
269
+ end
270
+
271
+ class Reader
272
+ attr_accessor :http, :start, :num, :type
273
+
274
+ def initialize(http, num=20, type=nil)
275
+ @http = http
276
+ @num = num
277
+ @type = type
278
+ @total = request(0, 0).posts.total
279
+ end
280
+
281
+ def last_page
282
+ ((@total - 1) / @num) + 1
283
+ end
284
+
285
+ def page(pos)
286
+ request(pos*@num, @num)
287
+ end
288
+
289
+ private
290
+
291
+ def request(start, num)
292
+ req = Net::HTTP::Post.new "/api/read"
293
+ data = {"start" => start, "num" => num}
294
+ data["type"] = @type if @type
295
+ req.set_form_data data
296
+ res = http.request(req)
297
+ if res.kind_of?(Net::HTTPSuccess)
298
+ return Tumblr::Data.new(REXML::Document.new(res.body))
299
+ else
300
+ raise ResponseError.new(res)
301
+ end
302
+ end
303
+ end
304
+
305
+ def self.read(hostname, num=20, type=nil, &b)
306
+ Net::HTTP.start(hostname) do |http|
307
+ reader = Reader.new(http, num, type)
308
+ reader.instance_eval &b
309
+ end
310
+ end
311
+
312
+ class Writer
313
+ attr_accessor :http, :email, :password, :generator
314
+
315
+ def initialize(http, email, password, generator)
316
+ @http = http
317
+ @email = email
318
+ @password = password
319
+ @generator = generator
320
+ end
321
+
322
+ def regular(body, title=nil)
323
+ post("type" => "regular", "title" => title, "body" => body)
324
+ end
325
+
326
+ def quote(text, source=nil)
327
+ post("type" => "quote", "quote" => text, "source" => source)
328
+ end
329
+
330
+ def photo(source, caption=nil)
331
+ post("type" => "photo", "caption" => caption, "source" => source)
332
+ end
333
+
334
+ def link(url, name=nil, description=nil)
335
+ post("type" => "link", "name" => name, "url" => url, "description" => description)
336
+ end
337
+
338
+ def conversation(conversation, title=nil)
339
+ post("type" => "conversation", "title" => title, "conversation" => conversation)
340
+ end
341
+
342
+ def video(embed, caption=nil)
343
+ post("type" => "video", "embed" => embed, "caption" => caption)
344
+ end
345
+
346
+ def post(data)
347
+ req = Net::HTTP::Post.new "/api/write"
348
+ req.set_form_data({
349
+ "email" => @email,
350
+ "password" => @password,
351
+ "generator" => @generator
352
+ }.merge(data))
353
+ res = @http.request req
354
+ case res.code
355
+ when '201'
356
+ return res.body.chomp
357
+ when '403'
358
+ raise AuthError.new
359
+ when '400'
360
+ raise BadRequestError.new(res.body)
361
+ else
362
+ raise ResponseError.new(res)
363
+ end
364
+ end
365
+ end
366
+
367
+ def self.write(email, password, generator="ruby-tumblr", &b)
368
+ Net::HTTP.start("www.tumblr.com") do |http|
369
+ writer = Writer.new(http, email, password, generator)
370
+ writer.instance_eval &b
371
+ end
372
+ end
373
+
374
+ end
375
+ end
data/scripts/txt2html ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'redcloth'
5
+ require 'syntax/convertors/html'
6
+ require 'erb'
7
+ require File.dirname(__FILE__) + '/../lib/tumblr/version.rb'
8
+
9
+ version = Tumblr::VERSION::STRING
10
+ download = 'http://rubyforge.org/projects/ruby-tumblr'
11
+
12
+ class Fixnum
13
+ def ordinal
14
+ # teens
15
+ return 'th' if (10..19).include?(self % 100)
16
+ # others
17
+ case self % 10
18
+ when 1: return 'st'
19
+ when 2: return 'nd'
20
+ when 3: return 'rd'
21
+ else return 'th'
22
+ end
23
+ end
24
+ end
25
+
26
+ class Time
27
+ def pretty
28
+ return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
29
+ end
30
+ end
31
+
32
+ def convert_syntax(syntax, source)
33
+ return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
34
+ end
35
+
36
+ if ARGV.length >= 1
37
+ src, template = ARGV
38
+ template ||= File.dirname(__FILE__) + '/../website/template.rhtml'
39
+
40
+ else
41
+ puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html")
42
+ exit!
43
+ end
44
+
45
+ template = ERB.new(File.open(template).read)
46
+
47
+ title = nil
48
+ body = nil
49
+ File.open(src) do |fsrc|
50
+ title_text = fsrc.readline
51
+ body_text = fsrc.read
52
+ syntax_items = []
53
+ body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</>!m){
54
+ ident = syntax_items.length
55
+ element, syntax, source = $1, $2, $3
56
+ syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
57
+ "syntax-temp-#{ident}"
58
+ }
59
+ title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
60
+ body = RedCloth.new(body_text).to_html
61
+ body.gsub!(%r!(?:<pre><code>)?syntax-temp-(d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
62
+ end
63
+ stat = File.stat(src)
64
+ created = stat.ctime
65
+ modified = stat.mtime
66
+
67
+ $stdout << template.result(binding)