friend-feed 0.1.0

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,5 @@
1
+ == 0.1.0 / 2008-03-26
2
+
3
+ * 1 major enhancement
4
+ * Created!
5
+ * Almost direct port from Python client
data/Manifest.txt ADDED
@@ -0,0 +1,18 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/friend-feed/feed.rb
6
+ lib/friend-feed.rb
7
+ tasks/ann.rake
8
+ tasks/annotations.rake
9
+ tasks/bones.rake
10
+ tasks/doc.rake
11
+ tasks/gem.rake
12
+ tasks/manifest.rake
13
+ tasks/post_load.rake
14
+ tasks/rubyforge.rake
15
+ tasks/setup.rb
16
+ tasks/svn.rake
17
+ tasks/test.rake
18
+ test/test_friend-feed.rb
data/README.txt ADDED
@@ -0,0 +1,48 @@
1
+ friend-feed
2
+ by Clinton R. Nixon <crnixon@gmail.com>
3
+ http://friend-feed.rubyforge.org
4
+
5
+ == DESCRIPTION:
6
+
7
+ Ruby client for the FriendFeed API.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * FIXME (list of features or problems)
12
+
13
+ == SYNOPSIS:
14
+
15
+ FIXME (code sample of usage)
16
+
17
+ == REQUIREMENTS:
18
+
19
+ * json gem
20
+
21
+ == INSTALL:
22
+
23
+ * sudo gem install friend-feed
24
+
25
+ == LICENSE:
26
+
27
+ (The MIT License)
28
+
29
+ Copyright (c) 2008 Clinton R. Nixon
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining
32
+ a copy of this software and associated documentation files (the
33
+ 'Software'), to deal in the Software without restriction, including
34
+ without limitation the rights to use, copy, modify, merge, publish,
35
+ distribute, sublicense, and/or sell copies of the Software, and to
36
+ permit persons to whom the Software is furnished to do so, subject to
37
+ the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be
40
+ included in all copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
43
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
44
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
45
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
46
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
47
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
48
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ load 'tasks/setup.rb'
6
+
7
+ ensure_in_path 'lib'
8
+ require 'friend-feed'
9
+
10
+ task :default => 'test'
11
+
12
+ PROJ.name = 'friend-feed'
13
+ PROJ.authors = 'Clinton R. Nixon'
14
+ PROJ.email = 'crnixon@gmail.com'
15
+ PROJ.url = 'friend-feed.rubyforge.org'
16
+ PROJ.rubyforge_name = 'friend-feed'
17
+ PROJ.dependencies = ['json']
18
+ PROJ.version = FriendFeed::VERSION
19
+
20
+ # EOF
@@ -0,0 +1,211 @@
1
+ require 'rubygems'
2
+ require 'json'
3
+ require 'cgi'
4
+ require 'net/http'
5
+
6
+ module FriendFeed
7
+ class Feed
8
+
9
+ def initialize(auth_nickname = nil, auth_key = nil)
10
+ @auth_nickname = auth_nickname
11
+ @auth_key = auth_key
12
+ end
13
+
14
+ # Returns the public feed with everyone's public entries.
15
+ # Authentication is not required.
16
+ def fetch_public_feed(url_args = {})
17
+ fetch_feed('/api/feed/public', url_args)
18
+ end
19
+
20
+ # Returns the entries shared by the user with the given nickname.
21
+ # Authentication is required if the user's feed is not public.
22
+ def fetch_user_feed(nickname, url_args = {})
23
+ fetch_feed("/api/feed/user/#{e nickname}", url_args)
24
+ end
25
+
26
+ # Returns the entries the given user has commented on.
27
+ def fetch_user_comments_feed(nickname, url_args = {})
28
+ fetch_feed("/api/feed/user/#{e nickname}/comments", url_args)
29
+ end
30
+
31
+ # Returns the entries the given user has "liked".
32
+ def fetch_user_likes_feed(nickname, url_args = {})
33
+ fetch_feed("/api/feed/user/#{e nickname}/likes", url_args)
34
+ end
35
+
36
+ # Returns the entries the given user has commented on or "liked".
37
+ def fetch_user_discussion_feed(nickname, url_args = {})
38
+ fetch_feed("/api/feed/user/#{e nickname}/discussion", url_args)
39
+ end
40
+
41
+ # Returns a merged feed with all of the given users' entries.
42
+ # Authentication is required if any one of the users' feeds is not
43
+ # public.
44
+ #
45
+ # TODO Fix this - some weird authentication thing.
46
+ def fetch_multi_user_feed(nicknames, url_args = {})
47
+ fetch_feed("/api/feed/user", {:nickname => nicknames.join(',')}.merge(url_args))
48
+ end
49
+
50
+ # Returns the entries the authenticated user sees on their home page.
51
+ # Authentication is always required.
52
+ def fetch_home_feed(url_args = {})
53
+ fetch_feed("/api/feed/home", url_args)
54
+ end
55
+
56
+ # Searches over entries in FriendFeed.
57
+ #
58
+ # If the request is authenticated, the default scope is over all of the
59
+ # entries in the authenticated user's Friends Feed. If the request is
60
+ # not authenticated, the default scope is over all public entries.
61
+ #
62
+ # The query syntax is the same syntax as
63
+ # http://friendfeed.com/advancedsearch
64
+ def search(q, url_args = {})
65
+ fetch_feed("/api/feed/search", url_args.merge(:q => q))
66
+ end
67
+
68
+ # Publishes the given link/title to the authenticated user's feed.
69
+ #
70
+ # Authentication is always required.
71
+ #
72
+ # image_urls is a list of URLs that will be downloaded and included as
73
+ # thumbnails beneath the link. The thumbnails will all link to the
74
+ # destination link. If you would prefer that the images link somewhere
75
+ # else, you can specify images[] instead, which should be a list of
76
+ # dicts of the form {"url": ..., "link": ...}. The thumbnail with the
77
+ # given url will link to the specified link.
78
+ #
79
+ # We return the parsed/published entry as returned from the server, which
80
+ # includes the final thumbnail URLs as well as the ID for the new entry.
81
+ #
82
+ # Example:
83
+ #
84
+ # session = friendfeed.FriendFeed(nickname, remote_key)
85
+ # entry = session.publish_link(
86
+ # title="Testing the FriendFeed API",
87
+ # link="http://friendfeed.com/",
88
+ # image_urls=[
89
+ # "http://friendfeed.com/static/images/jim-superman.jpg",
90
+ # "http://friendfeed.com/static/images/logo.png",
91
+ # ],
92
+ # )
93
+ # print "Posted images at http://friendfeed.com/e/%s" % entry["id"]
94
+ #
95
+ def publish_link(title, link = nil, comment = nil, image_urls = [], images = [])
96
+ post_args = {:title => title}
97
+
98
+ post_args['link'] = link unless link.nil?
99
+ post_args['comment'] = comment unless comment.nil?
100
+
101
+ image_urls.each do |image_url|
102
+ images.push({:url => image_url})
103
+ end
104
+
105
+ images.each_with_index do |image, i|
106
+ post_args["image#{i}_url"] = image[:url]
107
+ if image[:link]
108
+ post_args["image#{i}_link"] = image[:link]
109
+ end
110
+ end
111
+
112
+ feed = fetch_feed("/api/share", {}, post_args)
113
+
114
+ feed['entries'][0]
115
+ end
116
+
117
+ # Publishes the given message to the authenticated user's feed.
118
+ # See publish_link for additional options.
119
+ def publish_message(*args)
120
+ publish_link(*args)
121
+ end
122
+
123
+ # Adds the given comment to the entry with the given ID.
124
+ #
125
+ # We return the ID of the new comment, which can be used to edit or
126
+ # delete the comment.
127
+ def add_comment(entry_id, body)
128
+ result = fetch("/api/comment", {}, {:entry => entry_id, :body => body})
129
+ result["id"]
130
+ end
131
+
132
+ # Updates the comment with the given ID.
133
+ def edit_comment(entry_id, comment_id, body)
134
+ fetch("/api/comment", {}, { :entry => entry_id, :comment => comment_id, :body => body })
135
+ end
136
+
137
+ # Deletes the comment with the given ID.
138
+ def delete_comment(entry_id, comment_id)
139
+ fetch("/api/comment/delete", {}, { :entry => entry_id, :comment => comment_id })
140
+ end
141
+
142
+ # Un-deletes the comment with the given ID.
143
+ def undelete_comment(entry_id, comment_id)
144
+ fetch("/api/comment/delete", {}, { :entry => entry_id, :comment => comment_id, :undelete => 1 })
145
+ end
146
+
147
+ # 'Likes' the entry with the given ID.
148
+ def add_like(entry_id)
149
+ fetch("/api/like", {}, { :entry => entry_id })
150
+ end
151
+
152
+ # Deletes the 'Like' for the entry with the given ID (if any).
153
+ def delete_like(entry_id)
154
+ fetch("/api/like/delete", {}, { :entry => entry_id })
155
+ end
156
+
157
+ private
158
+
159
+ def has_auth?
160
+ @auth_nickname and @auth_key
161
+ end
162
+
163
+ def fetch_feed(uri, url_args = {}, post_args = {})
164
+ result = fetch(uri, url_args, post_args)
165
+ result['entries'].each do |entry|
166
+ entry['updated'] = Time.parse(entry['updated'])
167
+ entry['published'] = Time.parse(entry['published'])
168
+ if entry['comments']
169
+ entry['comments'].each do |comment|
170
+ comment['date'] = Time.parse(comment['date'])
171
+ end
172
+ end
173
+ if entry['like']
174
+ entry['like'].each do |like|
175
+ like['date'] = Time.parse(like['date'])
176
+ end
177
+ end
178
+ end
179
+ result
180
+ end
181
+
182
+ def fetch(path, url_args = {}, post_args = {})
183
+ url_args[:format] = :json
184
+ url = "http://friendfeed.com" + path + "?" + url_encode(url_args)
185
+ uri = URI.parse(url)
186
+
187
+ if post_args.empty?
188
+ req = Net::HTTP::Get.new("#{uri.path}?#{uri.query}")
189
+ else
190
+ req = Net::HTTP::Post.new("#{uri.path}?#{uri.query}")
191
+ req.set_form_data post_args
192
+ end
193
+
194
+ if has_auth?
195
+ req.basic_auth @auth_nickname, @auth_key
196
+ end
197
+
198
+ res = Net::HTTP.start(uri.host, uri.port) { |http| http.request(req) }
199
+
200
+ JSON.parse(res.body)
201
+ end
202
+
203
+ def url_encode(args_hash)
204
+ args_hash.collect {|key, value| CGI.escape(key.to_s) + '=' + CGI.escape(value.to_s)}.join('&')
205
+ end
206
+
207
+ def e(s)
208
+ CGI.escape(s)
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,54 @@
1
+ # Equivalent to a header guard in C/C++
2
+ # Used to prevent the class/module from being loaded more than once
3
+ unless defined? FriendFeed
4
+
5
+ module FriendFeed
6
+
7
+ # :stopdoc:
8
+ VERSION = '0.1.0'
9
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
10
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
11
+ # :startdoc:
12
+
13
+ # Returns the version string for the library.
14
+ #
15
+ def self.version
16
+ VERSION
17
+ end
18
+
19
+ # Returns the library path for the module. If any arguments are given,
20
+ # they will be joined to the end of the libray path using
21
+ # <tt>File.join</tt>.
22
+ #
23
+ def self.libpath( *args )
24
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, *args)
25
+ end
26
+
27
+ # Returns the lpath for the module. If any arguments are given,
28
+ # they will be joined to the end of the path using
29
+ # <tt>File.join</tt>.
30
+ #
31
+ def self.path( *args )
32
+ args.empty? ? PATH : ::File.join(PATH, *args)
33
+ end
34
+
35
+ # Utility method used to rquire all files ending in .rb that lie in the
36
+ # directory below this file that has the same name as the filename passed
37
+ # in. Optionally, a specific _directory_ name can be passed in such that
38
+ # the _filename_ does not have to be equivalent to the directory.
39
+ #
40
+ def self.require_all_libs_relative_to( fname, dir = nil )
41
+ dir ||= ::File.basename(fname, '.*')
42
+ search_me = ::File.expand_path(
43
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
44
+
45
+ Dir.glob(search_me).sort.each {|rb| require rb}
46
+ end
47
+
48
+ end # module FriendFeed
49
+
50
+ FriendFeed.require_all_libs_relative_to __FILE__
51
+
52
+ end # unless defined?
53
+
54
+ # EOF
data/tasks/ann.rake ADDED
@@ -0,0 +1,76 @@
1
+ # $Id$
2
+
3
+ begin
4
+ require 'bones/smtp_tls'
5
+ rescue LoadError
6
+ require 'net/smtp'
7
+ end
8
+ require 'time'
9
+
10
+ namespace :ann do
11
+
12
+ file PROJ.ann_file do
13
+ puts "Generating #{PROJ.ann_file}"
14
+ File.open(PROJ.ann_file,'w') do |fd|
15
+ fd.puts("#{PROJ.name} version #{PROJ.version}")
16
+ fd.puts(" by #{Array(PROJ.authors).first}") if PROJ.authors
17
+ fd.puts(" #{PROJ.url}") if PROJ.url
18
+ fd.puts(" (the \"#{PROJ.release_name}\" release)") if PROJ.release_name
19
+ fd.puts
20
+ fd.puts("== DESCRIPTION")
21
+ fd.puts
22
+ fd.puts(PROJ.description)
23
+ fd.puts
24
+ fd.puts(PROJ.changes.sub(%r/^.*$/, '== CHANGES'))
25
+ fd.puts
26
+ PROJ.ann_paragraphs.each do |p|
27
+ fd.puts "== #{p.upcase}"
28
+ fd.puts
29
+ fd.puts paragraphs_of(PROJ.readme_file, p).join("\n\n")
30
+ fd.puts
31
+ end
32
+ fd.puts PROJ.ann_text if PROJ.ann_text
33
+ end
34
+ end
35
+
36
+ desc "Create an announcement file"
37
+ task :announcement => PROJ.ann_file
38
+
39
+ desc "Send an email announcement"
40
+ task :email => PROJ.ann_file do
41
+ from = PROJ.ann_email[:from] || PROJ.email
42
+ to = Array(PROJ.ann_email[:to])
43
+
44
+ ### build a mail header for RFC 822
45
+ rfc822msg = "From: #{from}\n"
46
+ rfc822msg << "To: #{to.join(',')}\n"
47
+ rfc822msg << "Subject: [ANN] #{PROJ.name} #{PROJ.version}"
48
+ rfc822msg << " (#{PROJ.release_name})" if PROJ.release_name
49
+ rfc822msg << "\n"
50
+ rfc822msg << "Date: #{Time.new.rfc822}\n"
51
+ rfc822msg << "Message-Id: "
52
+ rfc822msg << "<#{"%.8f" % Time.now.to_f}@#{PROJ.ann_email[:domain]}>\n\n"
53
+ rfc822msg << File.read(PROJ.ann_file)
54
+
55
+ params = [:server, :port, :domain, :acct, :passwd, :authtype].map do |key|
56
+ PROJ.ann_email[key]
57
+ end
58
+
59
+ params[3] = PROJ.email if params[3].nil?
60
+
61
+ if params[4].nil?
62
+ STDOUT.write "Please enter your e-mail password (#{params[3]}): "
63
+ params[4] = STDIN.gets.chomp
64
+ end
65
+
66
+ ### send email
67
+ Net::SMTP.start(*params) {|smtp| smtp.sendmail(rfc822msg, from, to)}
68
+ end
69
+ end # namespace :ann
70
+
71
+ desc 'Alias to ann:announcement'
72
+ task :ann => 'ann:announcement'
73
+
74
+ CLOBBER << PROJ.ann_file
75
+
76
+ # EOF
@@ -0,0 +1,22 @@
1
+ # $Id$
2
+
3
+ if HAVE_BONES
4
+
5
+ desc "Enumerate all annotations"
6
+ task :notes do
7
+ Bones::AnnotationExtractor.enumerate(
8
+ PROJ, PROJ.annotation_tags.join('|'), :tag => true)
9
+ end
10
+
11
+ namespace :notes do
12
+ PROJ.annotation_tags.each do |tag|
13
+ desc "Enumerate all #{tag} annotations"
14
+ task tag.downcase.to_sym do
15
+ Bones::AnnotationExtractor.enumerate(PROJ, tag)
16
+ end
17
+ end
18
+ end
19
+
20
+ end # if HAVE_BONES
21
+
22
+ # EOF
data/tasks/bones.rake ADDED
@@ -0,0 +1,40 @@
1
+ # $Id$
2
+
3
+ require 'pp'
4
+ require 'stringio'
5
+
6
+ namespace :bones do
7
+
8
+ desc 'Show the PROJ open struct'
9
+ task :debug do |t|
10
+ atr = if ARGV.length == 2
11
+ t.application.top_level_tasks.pop
12
+ end
13
+ sio = StringIO.new
14
+ sep = "\n" + ' '*27
15
+ fmt = "%23s => %s"
16
+
17
+ if atr
18
+ PP.pp(PROJ.send(atr.to_sym), sio, 49)
19
+ sio.seek 0
20
+ val = sio.read
21
+ val = val.split("\n").join(sep)
22
+
23
+ puts fmt % [atr, val]
24
+ else
25
+ h = PROJ.instance_variable_get(:@table)
26
+ h.keys.map {|k| k.to_s}.sort.each do |k|
27
+ sio.truncate 0
28
+ PP.pp(h[k.to_sym], sio, 49)
29
+ sio.seek 0
30
+ val = sio.read
31
+ val = val.split("\n").join(sep)
32
+
33
+ puts fmt % [k, val]
34
+ end
35
+ end
36
+ end
37
+
38
+ end # namespace :bones
39
+
40
+ # EOF
data/tasks/doc.rake ADDED
@@ -0,0 +1,48 @@
1
+ # $Id$
2
+
3
+ require 'rake/rdoctask'
4
+
5
+ namespace :doc do
6
+
7
+ desc 'Generate RDoc documentation'
8
+ Rake::RDocTask.new do |rd|
9
+ rd.main = PROJ.rdoc_main
10
+ rd.rdoc_dir = PROJ.rdoc_dir
11
+
12
+ incl = Regexp.new(PROJ.rdoc_include.join('|'))
13
+ excl = Regexp.new(PROJ.rdoc_exclude.join('|'))
14
+ files = PROJ.files.find_all do |fn|
15
+ case fn
16
+ when excl; false
17
+ when incl; true
18
+ else false end
19
+ end
20
+ rd.rdoc_files.push(*files)
21
+
22
+ title = "#{PROJ.name}-#{PROJ.version} Documentation"
23
+ title = "#{PROJ.rubyforge_name}'s " + title if PROJ.rubyforge_name != title
24
+
25
+ rd.options << "-t #{title}"
26
+ rd.options.concat(PROJ.rdoc_opts)
27
+ end
28
+
29
+ desc 'Generate ri locally for testing'
30
+ task :ri => :clobber_ri do
31
+ sh "#{RDOC} --ri -o ri ."
32
+ end
33
+
34
+ task :clobber_ri do
35
+ rm_r 'ri' rescue nil
36
+ end
37
+
38
+ end # namespace :doc
39
+
40
+ desc 'Alias to doc:rdoc'
41
+ task :doc => 'doc:rdoc'
42
+
43
+ desc 'Remove all build products'
44
+ task :clobber => %w(doc:clobber_rdoc doc:clobber_ri)
45
+
46
+ remove_desc_for_task %w(doc:clobber_rdoc)
47
+
48
+ # EOF
data/tasks/gem.rake ADDED
@@ -0,0 +1,116 @@
1
+ # $Id$
2
+
3
+ require 'rake/gempackagetask'
4
+
5
+ namespace :gem do
6
+
7
+ PROJ.spec = Gem::Specification.new do |s|
8
+ s.name = PROJ.name
9
+ s.version = PROJ.version
10
+ s.summary = PROJ.summary
11
+ s.authors = Array(PROJ.authors)
12
+ s.email = PROJ.email
13
+ s.homepage = Array(PROJ.url).first
14
+ s.rubyforge_project = PROJ.rubyforge_name
15
+ s.post_install_message = PROJ.post_install_message
16
+
17
+ s.description = PROJ.description
18
+
19
+ PROJ.dependencies.each do |dep|
20
+ s.add_dependency(*dep)
21
+ end
22
+
23
+ s.files = PROJ.files
24
+ s.executables = PROJ.executables.map {|fn| File.basename(fn)}
25
+ s.extensions = PROJ.files.grep %r/extconf\.rb$/
26
+
27
+ s.bindir = 'bin'
28
+ dirs = Dir["{#{PROJ.libs.join(',')}}"]
29
+ s.require_paths = dirs unless dirs.empty?
30
+
31
+ incl = Regexp.new(PROJ.rdoc_include.join('|'))
32
+ excl = PROJ.rdoc_exclude.dup.concat %w[\.rb$ ^(\.\/|\/)?ext]
33
+ excl = Regexp.new(excl.join('|'))
34
+ rdoc_files = PROJ.files.find_all do |fn|
35
+ case fn
36
+ when excl; false
37
+ when incl; true
38
+ else false end
39
+ end
40
+ s.rdoc_options = PROJ.rdoc_opts + ['--main', PROJ.rdoc_main]
41
+ s.extra_rdoc_files = rdoc_files
42
+ s.has_rdoc = true
43
+
44
+ if test ?f, PROJ.test_file
45
+ s.test_file = PROJ.test_file
46
+ else
47
+ s.test_files = PROJ.tests.to_a
48
+ end
49
+
50
+ # Do any extra stuff the user wants
51
+ # spec_extras.each do |msg, val|
52
+ # case val
53
+ # when Proc
54
+ # val.call(s.send(msg))
55
+ # else
56
+ # s.send "#{msg}=", val
57
+ # end
58
+ # end
59
+ end
60
+
61
+ desc 'Show information about the gem'
62
+ task :debug do
63
+ puts PROJ.spec.to_ruby
64
+ end
65
+
66
+ pkg = Rake::PackageTask.new(PROJ.name, PROJ.version) do |pkg|
67
+ pkg.need_tar = PROJ.need_tar
68
+ pkg.need_zip = PROJ.need_zip
69
+ pkg.package_files += PROJ.spec.files
70
+ end
71
+ Rake::Task['gem:package'].instance_variable_set(:@full_comment, nil)
72
+
73
+ gem_file = if PROJ.spec.platform == Gem::Platform::RUBY
74
+ "#{pkg.package_name}.gem"
75
+ else
76
+ "#{pkg.package_name}-#{PROJ.spec.platform}.gem"
77
+ end
78
+
79
+ desc "Build the gem file #{gem_file}"
80
+ task :package => "#{pkg.package_dir}/#{gem_file}"
81
+
82
+ file "#{pkg.package_dir}/#{gem_file}" => [pkg.package_dir] + PROJ.spec.files do
83
+ when_writing("Creating GEM") {
84
+ Gem::Builder.new(PROJ.spec).build
85
+ verbose(true) {
86
+ mv gem_file, "#{pkg.package_dir}/#{gem_file}"
87
+ }
88
+ }
89
+ end
90
+
91
+ desc 'Install the gem'
92
+ task :install => [:clobber, :package] do
93
+ sh "#{SUDO} #{GEM} install pkg/#{PROJ.spec.full_name}"
94
+ end
95
+
96
+ desc 'Uninstall the gem'
97
+ task :uninstall do
98
+ installed_list = Gem.source_index.find_name(PROJ.name)
99
+ if installed_list and installed_list.collect { |s| s.version.to_s}.include?(PROJ.version) then
100
+ sh "#{SUDO} #{GEM} uninstall -v '#{PROJ.version}' -i -x #{PROJ.name}"
101
+ end
102
+ end
103
+
104
+ desc 'Reinstall the gem'
105
+ task :reinstall => [:uninstall, :install]
106
+
107
+ end # namespace :gem
108
+
109
+ desc 'Alias to gem:package'
110
+ task :gem => 'gem:package'
111
+
112
+ task :clobber => 'gem:clobber_package'
113
+
114
+ remove_desc_for_task %w(gem:clobber_package)
115
+
116
+ # EOF
@@ -0,0 +1,49 @@
1
+ # $Id$
2
+
3
+ require 'find'
4
+
5
+ namespace :manifest do
6
+
7
+ desc 'Verify the manifest'
8
+ task :check do
9
+ fn = PROJ.manifest_file + '.tmp'
10
+ files = manifest_files
11
+
12
+ File.open(fn, 'w') {|fp| fp.puts files}
13
+ lines = %x(#{DIFF} -du #{PROJ.manifest_file} #{fn}).split("\n")
14
+ if HAVE_FACETS_ANSICODE and ENV.has_key?('TERM')
15
+ lines.map! do |line|
16
+ case line
17
+ when %r/^(-{3}|\+{3})/; nil
18
+ when %r/^@/; Console::ANSICode.blue line
19
+ when %r/^\+/; Console::ANSICode.green line
20
+ when %r/^\-/; Console::ANSICode.red line
21
+ else line end
22
+ end
23
+ end
24
+ puts lines.compact
25
+ rm fn rescue nil
26
+ end
27
+
28
+ desc 'Create a new manifest'
29
+ task :create do
30
+ files = manifest_files
31
+ unless test(?f, PROJ.manifest_file)
32
+ files << PROJ.manifest_file
33
+ files.sort!
34
+ end
35
+ File.open(PROJ.manifest_file, 'w') {|fp| fp.puts files}
36
+ end
37
+
38
+ task :assert do
39
+ files = manifest_files
40
+ manifest = File.read(PROJ.manifest_file).split($/)
41
+ raise "ERROR: #{PROJ.manifest_file} is out of date" unless files == manifest
42
+ end
43
+
44
+ end # namespace :manifest
45
+
46
+ desc 'Alias to manifest:check'
47
+ task :manifest => 'manifest:check'
48
+
49
+ # EOF
@@ -0,0 +1,32 @@
1
+ # $Id$
2
+
3
+ # This file does not define any rake tasks. It is used to load some project
4
+ # settings if they are not defined by the user.
5
+
6
+ PROJ.rdoc_exclude << "^#{Regexp.escape(PROJ.manifest_file)}$"
7
+ PROJ.exclude << "^#{Regexp.escape(PROJ.ann_file)}$"
8
+
9
+ PROJ.instance_variable_get(:@table).each do |key,val|
10
+ next unless val.instance_of? Array
11
+ next if key == :dependencies
12
+ val.flatten!
13
+ end
14
+
15
+ PROJ.changes ||= paragraphs_of(PROJ.history_file, 0..1).join("\n\n")
16
+
17
+ PROJ.description ||= paragraphs_of(PROJ.readme_file, 'description').join("\n\n")
18
+
19
+ PROJ.summary ||= PROJ.description.split('.').first
20
+
21
+ PROJ.files ||=
22
+ if test(?f, PROJ.manifest_file)
23
+ files = File.readlines(PROJ.manifest_file).map {|fn| fn.chomp.strip}
24
+ files.delete ''
25
+ files
26
+ else [] end
27
+
28
+ PROJ.executables ||= PROJ.files.find_all {|fn| fn =~ %r/^bin/}
29
+
30
+ PROJ.rdoc_main ||= PROJ.readme_file
31
+
32
+ # EOF
@@ -0,0 +1,57 @@
1
+ # $Id$
2
+
3
+ if PROJ.rubyforge_name && HAVE_RUBYFORGE
4
+
5
+ require 'rubyforge'
6
+ require 'rake/contrib/sshpublisher'
7
+
8
+ namespace :gem do
9
+ desc 'Package and upload to RubyForge'
10
+ task :release => [:clobber, :package] do |t|
11
+ v = ENV['VERSION'] or abort 'Must supply VERSION=x.y.z'
12
+ abort "Versions don't match #{v} vs #{PROJ.version}" if v != PROJ.version
13
+ pkg = "pkg/#{PROJ.spec.full_name}"
14
+
15
+ if $DEBUG then
16
+ puts "release_id = rf.add_release #{PROJ.rubyforge_name.inspect}, #{PROJ.name.inspect}, #{PROJ.version.inspect}, \"#{pkg}.tgz\""
17
+ puts "rf.add_file #{PROJ.rubyforge_name.inspect}, #{PROJ.name.inspect}, release_id, \"#{pkg}.gem\""
18
+ end
19
+
20
+ rf = RubyForge.new
21
+ puts 'Logging in'
22
+ rf.login
23
+
24
+ c = rf.userconfig
25
+ c['release_notes'] = PROJ.description if PROJ.description
26
+ c['release_changes'] = PROJ.changes if PROJ.changes
27
+ c['preformatted'] = true
28
+
29
+ files = [(PROJ.need_tar ? "#{pkg}.tgz" : nil),
30
+ (PROJ.need_zip ? "#{pkg}.zip" : nil),
31
+ "#{pkg}.gem"].compact
32
+
33
+ puts "Releasing #{PROJ.name} v. #{PROJ.version}"
34
+ rf.add_release PROJ.rubyforge_name, PROJ.name, PROJ.version, *files
35
+ end
36
+ end # namespace :gem
37
+
38
+
39
+ namespace :doc do
40
+ desc "Publish RDoc to RubyForge"
41
+ task :release => %w(doc:clobber_rdoc doc:rdoc) do
42
+ config = YAML.load(
43
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
44
+ )
45
+
46
+ host = "#{config['username']}@rubyforge.org"
47
+ remote_dir = "/var/www/gforge-projects/#{PROJ.rubyforge_name}/"
48
+ remote_dir << PROJ.rdoc_remote_dir if PROJ.rdoc_remote_dir
49
+ local_dir = PROJ.rdoc_dir
50
+
51
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
52
+ end
53
+ end # namespace :doc
54
+
55
+ end # if HAVE_RUBYFORGE
56
+
57
+ # EOF
data/tasks/setup.rb ADDED
@@ -0,0 +1,227 @@
1
+ # $Id$
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+ require 'rake/clean'
6
+ require 'fileutils'
7
+ require 'ostruct'
8
+
9
+ PROJ = OpenStruct.new
10
+
11
+ PROJ.name = nil
12
+ PROJ.summary = nil
13
+ PROJ.description = nil
14
+ PROJ.changes = nil
15
+ PROJ.authors = nil
16
+ PROJ.email = nil
17
+ PROJ.url = nil
18
+ PROJ.version = ENV['VERSION'] || '0.0.0'
19
+ PROJ.rubyforge_name = nil
20
+ PROJ.exclude = %w(tmp$ bak$ ~$ CVS .svn/ ^pkg/ ^doc/)
21
+ PROJ.release_name = ENV['RELEASE']
22
+ PROJ.history_file = 'History.txt'
23
+ PROJ.manifest_file = 'Manifest.txt'
24
+ PROJ.readme_file = 'README.txt'
25
+
26
+ # Rspec
27
+ PROJ.specs = FileList['spec/**/*_spec.rb']
28
+ PROJ.spec_opts = []
29
+
30
+ # Test::Unit
31
+ PROJ.tests = FileList['test/**/test_*.rb']
32
+ PROJ.test_file = 'test/all.rb'
33
+ PROJ.test_opts = []
34
+
35
+ # Rcov
36
+ PROJ.rcov_dir = 'coverage'
37
+ PROJ.rcov_opts = %w[--sort coverage -T]
38
+ PROJ.rcov_threshold = 90.0
39
+ PROJ.rcov_threshold_exact = false
40
+
41
+ # Rdoc
42
+ PROJ.rdoc_opts = []
43
+ PROJ.rdoc_include = %w(^lib/ ^bin/ ^ext/ .txt$)
44
+ PROJ.rdoc_exclude = %w(extconf.rb$)
45
+ PROJ.rdoc_main = nil
46
+ PROJ.rdoc_dir = 'doc'
47
+ PROJ.rdoc_remote_dir = nil
48
+
49
+ # Extensions
50
+ PROJ.extensions = FileList['ext/**/extconf.rb']
51
+ PROJ.ruby_opts = %w(-w)
52
+ PROJ.libs = []
53
+ %w(lib ext).each {|dir| PROJ.libs << dir if test ?d, dir}
54
+
55
+ # Gem Packaging
56
+ PROJ.files = nil
57
+ PROJ.executables = nil
58
+ PROJ.dependencies = []
59
+ PROJ.need_tar = true
60
+ PROJ.need_zip = false
61
+ PROJ.post_install_message = nil
62
+
63
+ # File Annotations
64
+ PROJ.annotation_exclude = %w(^tasks/setup.rb$)
65
+ PROJ.annotation_extensions = %w(.txt .rb .erb) << ''
66
+ PROJ.annotation_tags = %w(FIXME OPTIMIZE TODO)
67
+
68
+ # Subversion Repository
69
+ PROJ.svn = false
70
+ PROJ.svn_root = nil
71
+ PROJ.svn_trunk = 'trunk'
72
+ PROJ.svn_tags = 'tags'
73
+ PROJ.svn_branches = 'branches'
74
+
75
+ # Announce
76
+ PROJ.ann_file = 'announcement.txt'
77
+ PROJ.ann_text = nil
78
+ PROJ.ann_paragraphs = []
79
+ PROJ.ann_email = {
80
+ :from => nil,
81
+ :to => %w(ruby-talk@ruby-lang.org),
82
+ :server => 'localhost',
83
+ :port => 25,
84
+ :domain => ENV['HOSTNAME'],
85
+ :acct => nil,
86
+ :passwd => nil,
87
+ :authtype => :plain
88
+ }
89
+
90
+ # Load the other rake files in the tasks folder
91
+ rakefiles = Dir.glob('tasks/*.rake').sort
92
+ rakefiles.unshift(rakefiles.delete('tasks/post_load.rake')).compact!
93
+ import(*rakefiles)
94
+
95
+ # Setup some constants
96
+ WIN32 = %r/djgpp|(cyg|ms|bcc)win|mingw/ =~ RUBY_PLATFORM unless defined? WIN32
97
+
98
+ DEV_NULL = WIN32 ? 'NUL:' : '/dev/null'
99
+
100
+ def quiet( &block )
101
+ io = [STDOUT.dup, STDERR.dup]
102
+ STDOUT.reopen DEV_NULL
103
+ STDERR.reopen DEV_NULL
104
+ block.call
105
+ ensure
106
+ STDOUT.reopen io.first
107
+ STDERR.reopen io.last
108
+ end
109
+
110
+ DIFF = if WIN32 then 'diff.exe'
111
+ else
112
+ if quiet {system "gdiff", __FILE__, __FILE__} then 'gdiff'
113
+ else 'diff' end
114
+ end unless defined? DIFF
115
+
116
+ SUDO = if WIN32 then ''
117
+ else
118
+ if quiet {system 'which sudo'} then 'sudo'
119
+ else '' end
120
+ end
121
+
122
+ RCOV = WIN32 ? 'rcov.bat' : 'rcov'
123
+ GEM = WIN32 ? 'gem.bat' : 'gem'
124
+
125
+ %w(rcov spec/rake/spectask rubyforge bones facets/ansicode).each do |lib|
126
+ begin
127
+ require lib
128
+ Object.instance_eval {const_set "HAVE_#{lib.tr('/','_').upcase}", true}
129
+ rescue LoadError
130
+ Object.instance_eval {const_set "HAVE_#{lib.tr('/','_').upcase}", false}
131
+ end
132
+ end
133
+
134
+ # Reads a file at +path+ and spits out an array of the +paragraphs+
135
+ # specified.
136
+ #
137
+ # changes = paragraphs_of('History.txt', 0..1).join("\n\n")
138
+ # summary, *description = paragraphs_of('README.txt', 3, 3..8)
139
+ #
140
+ def paragraphs_of( path, *paragraphs )
141
+ title = String === paragraphs.first ? paragraphs.shift : nil
142
+ ary = File.read(path).delete("\r").split(/\n\n+/)
143
+
144
+ result = if title
145
+ tmp, matching = [], false
146
+ rgxp = %r/^=+\s*#{Regexp.escape(title)}/i
147
+ paragraphs << (0..-1) if paragraphs.empty?
148
+
149
+ ary.each do |val|
150
+ if val =~ rgxp
151
+ break if matching
152
+ matching = true
153
+ rgxp = %r/^=+/i
154
+ elsif matching
155
+ tmp << val
156
+ end
157
+ end
158
+ tmp
159
+ else ary end
160
+
161
+ result.values_at(*paragraphs)
162
+ end
163
+
164
+ # Adds the given gem _name_ to the current project's dependency list. An
165
+ # optional gem _version_ can be given. If omitted, the newest gem version
166
+ # will be used.
167
+ #
168
+ def depend_on( name, version = nil )
169
+ spec = Gem.source_index.find_name(name).last
170
+ version = spec.version.to_s if version.nil? and !spec.nil?
171
+
172
+ PROJ.dependencies << case version
173
+ when nil; [name]
174
+ when %r/^\d/; [name, ">= #{version}"]
175
+ else [name, version] end
176
+ end
177
+
178
+ # Adds the given arguments to the include path if they are not already there
179
+ #
180
+ def ensure_in_path( *args )
181
+ args.each do |path|
182
+ path = File.expand_path(path)
183
+ $:.unshift(path) if test(?d, path) and not $:.include?(path)
184
+ end
185
+ end
186
+
187
+ # Find a rake task using the task name and remove any description text. This
188
+ # will prevent the task from being displayed in the list of available tasks.
189
+ #
190
+ def remove_desc_for_task( names )
191
+ Array(names).each do |task_name|
192
+ task = Rake.application.tasks.find {|t| t.name == task_name}
193
+ next if task.nil?
194
+ task.instance_variable_set :@comment, nil
195
+ end
196
+ end
197
+
198
+ # Change working directories to _dir_, call the _block_ of code, and then
199
+ # change back to the original working directory (the current directory when
200
+ # this method was called).
201
+ #
202
+ def in_directory( dir, &block )
203
+ curdir = pwd
204
+ begin
205
+ cd dir
206
+ return block.call
207
+ ensure
208
+ cd curdir
209
+ end
210
+ end
211
+
212
+ # Scans the current working directory and creates a list of files that are
213
+ # candidates to be in the manifest.
214
+ #
215
+ def manifest_files
216
+ files = []
217
+ exclude = Regexp.new(PROJ.exclude.join('|'))
218
+ Find.find '.' do |path|
219
+ path.sub! %r/^(\.\/|\/)/o, ''
220
+ next unless test ?f, path
221
+ next if path =~ exclude
222
+ files << path
223
+ end
224
+ files.sort!
225
+ end
226
+
227
+ # EOF
data/tasks/svn.rake ADDED
@@ -0,0 +1,44 @@
1
+ # $Id$
2
+
3
+
4
+ if PROJ.svn and system("svn --version 2>&1 > #{DEV_NULL}")
5
+
6
+ unless PROJ.svn_root
7
+ info = %x/svn info ./
8
+ m = %r/^Repository Root:\s+(.*)$/.match(info)
9
+ PROJ.svn_root = (m.nil? ? '' : m[1])
10
+ end
11
+ PROJ.svn_root = File.join(PROJ.svn_root, PROJ.svn) if String === PROJ.svn
12
+
13
+ namespace :svn do
14
+
15
+ desc 'Show tags from the SVN repository'
16
+ task :show_tags do |t|
17
+ tags = %x/svn list #{File.join(PROJ.svn_root, PROJ.svn_tags)}/
18
+ tags.gsub!(%r/\/$/, '')
19
+ puts tags
20
+ end
21
+
22
+ desc 'Create a new tag in the SVN repository'
23
+ task :create_tag do |t|
24
+ v = ENV['VERSION'] or abort 'Must supply VERSION=x.y.z'
25
+ abort "Versions don't match #{v} vs #{PROJ.version}" if v != PROJ.version
26
+
27
+ trunk = File.join(PROJ.svn_root, PROJ.svn_trunk)
28
+ tag = "%s-%s" % [PROJ.name, PROJ.version]
29
+ tag = File.join(PROJ.svn_root, PROJ.svn_tags, tag)
30
+ msg = "Creating tag for #{PROJ.name} version #{PROJ.version}"
31
+
32
+ puts "Creating SVN tag '#{tag}'"
33
+ unless system "svn cp -m '#{msg}' #{trunk} #{tag}"
34
+ abort "Tag creation failed"
35
+ end
36
+ end
37
+
38
+ end # namespace :svn
39
+
40
+ task 'gem:release' => 'svn:create_tag'
41
+
42
+ end # if PROJ.svn
43
+
44
+ # EOF
data/tasks/test.rake ADDED
@@ -0,0 +1,38 @@
1
+ # $Id$
2
+
3
+ require 'rake/testtask'
4
+
5
+ namespace :test do
6
+
7
+ Rake::TestTask.new(:run) do |t|
8
+ t.libs = PROJ.libs
9
+ t.test_files = if test(?f, PROJ.test_file) then [PROJ.test_file]
10
+ else PROJ.tests end
11
+ t.ruby_opts += PROJ.ruby_opts
12
+ t.ruby_opts += PROJ.test_opts
13
+ end
14
+
15
+ if HAVE_RCOV
16
+ desc 'Run rcov on the unit tests'
17
+ task :rcov => :clobber_rcov do
18
+ opts = PROJ.rcov_opts.dup << '-o' << PROJ.rcov_dir
19
+ opts = opts.join(' ')
20
+ files = if test(?f, PROJ.test_file) then [PROJ.test_file]
21
+ else PROJ.tests end
22
+ files = files.join(' ')
23
+ sh "#{RCOV} #{files} #{opts}"
24
+ end
25
+
26
+ task :clobber_rcov do
27
+ rm_r 'coverage' rescue nil
28
+ end
29
+ end
30
+
31
+ end # namespace :test
32
+
33
+ desc 'Alias to test:run'
34
+ task :test => 'test:run'
35
+
36
+ task :clobber => 'test:clobber_rcov' if HAVE_RCOV
37
+
38
+ # EOF
File without changes
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: friend-feed
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Clinton R. Nixon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-03-27 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ description: Ruby client for the FriendFeed API.
25
+ email: crnixon@gmail.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - History.txt
32
+ - README.txt
33
+ files:
34
+ - History.txt
35
+ - Manifest.txt
36
+ - README.txt
37
+ - Rakefile
38
+ - lib/friend-feed/feed.rb
39
+ - lib/friend-feed.rb
40
+ - tasks/ann.rake
41
+ - tasks/annotations.rake
42
+ - tasks/bones.rake
43
+ - tasks/doc.rake
44
+ - tasks/gem.rake
45
+ - tasks/manifest.rake
46
+ - tasks/post_load.rake
47
+ - tasks/rubyforge.rake
48
+ - tasks/setup.rb
49
+ - tasks/svn.rake
50
+ - tasks/test.rake
51
+ - test/test_friend-feed.rb
52
+ has_rdoc: true
53
+ homepage: friend-feed.rubyforge.org
54
+ post_install_message:
55
+ rdoc_options:
56
+ - --main
57
+ - README.txt
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: "0"
71
+ version:
72
+ requirements: []
73
+
74
+ rubyforge_project: friend-feed
75
+ rubygems_version: 1.0.1
76
+ signing_key:
77
+ specification_version: 2
78
+ summary: Ruby client for the FriendFeed API
79
+ test_files:
80
+ - test/test_friend-feed.rb