ruby-tumblr 0.0.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/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)