vlaah 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/vlaah/base.rb ADDED
@@ -0,0 +1,133 @@
1
+ require 'vlaah/version'
2
+ require 'net/http'
3
+ require 'uri'
4
+ require 'rexml/document'
5
+
6
+ # = VLAAH API Client Library For Ruby
7
+ #
8
+ # This library provides model classes about VLAAH API.
9
+ #
10
+ # == Installation
11
+ #
12
+ # You can install this library with RubyGems.
13
+ #
14
+ # gem install vlaah
15
+ #
16
+ # Or checkout it from Subversion repository.
17
+ #
18
+ # svn co http://vlaah.rubyforge.org/svn/trunk vlaah-ruby
19
+ #
20
+ # == Applying A New Application And Setting Application Key
21
+ #
22
+ # You must apply the application in the VLAAH API page before you use this API.
23
+ # And you can get an application key of 64 characters.
24
+ #
25
+ # VLAAH::Base.application_key = "<your application key goes here.>";
26
+ #
27
+ # To apply a new application, go http://api.vlaah.com.
28
+ #
29
+ # See VLAAH::Topic, VLAAH::Person and VLAAH::Comment for more manuals and
30
+ # examples.
31
+ #
32
+ # == Author
33
+ #
34
+ # This library was written by Hong, MinHee. <dahlia@lunant.net> And, thanks for
35
+ # our Lunant friends.
36
+ #
37
+ # == License
38
+ #
39
+ # It is provided with MIT License.
40
+ # See more details on http://www.opensource.org/licenses/mit-license.php
41
+ module VLAAH
42
+ Copyright = "Copyright (c) 2008 Lunant <http://lunant.net/>"
43
+ Author = "Hong, MinHee <dahlia@lunant.net>"
44
+ Protocol = "VLAAH/0.9"
45
+
46
+ HTTP = ::Net::HTTP unless defined? HTTP
47
+ Host = "vlaah.com" unless defined? Host
48
+ Path = "/" unless defined? Path
49
+
50
+ # :stopdoc:
51
+ class Error < StandardError; end
52
+ class ApplicationKeyError < Error; end
53
+ # :startdoc:
54
+
55
+ # It is an internal abstract class to inherit.
56
+ class Base
57
+ @@application_key = nil
58
+ @@connection = nil
59
+ @@cache = {}
60
+
61
+ # The application key. Default value is +nil+.
62
+ #
63
+ # VLAAH::Base.application_key #=> nil
64
+ # VLAAH::Base.application_key = '6cec5bcd27b9.........35af1e8ea0b'
65
+ # VLAAH::Base.application_key #=> '6cec5bcd27b9.........35af1e8ea0b'
66
+ def self.application_key
67
+ @@application_key
68
+ end
69
+
70
+ # Your application key goes here.
71
+ #
72
+ # VLAAH::Base.application_key = '6cec5bcd27b9.........35af1e8ea0b'
73
+ def self.application_key=(appkey)
74
+ @@application_key = appkey
75
+ end
76
+
77
+ protected
78
+
79
+ def self.connect_http
80
+ if @@connection
81
+ @@connection
82
+ else
83
+ @@connection = HTTP.new Host
84
+ end
85
+ end
86
+
87
+ def self.raw_data(*resource)
88
+ page = resource.pop if resource.last.is_a? ::Integer
89
+
90
+ unsafe = /[^-_.~a-zA-Z\d;:@&,]/n
91
+
92
+ path = resource.flatten \
93
+ .reject { |x| x.nil? } \
94
+ .map { |s| URI.escape(s.to_s, unsafe) } \
95
+ .join("/")
96
+
97
+ path += "?#{page}" if page
98
+
99
+ return @@cache[path] if @@cache.include? path
100
+
101
+ unless appkey = application_key
102
+ raise ApplicationKeyError,
103
+ "Missing VLAAH::Base.application_key",
104
+ caller[1..-1]
105
+ end
106
+
107
+ headers = {
108
+ "Accept" => "text/xml",
109
+ "User-Agent" => "VLAAH-Ruby/#{Version} (appkey/#{appkey})",
110
+ "X-Accept-Protocol" => Protocol
111
+ }
112
+
113
+ response = connect_http.get(Path + path, headers)
114
+ accepted = response["X-Accept-Protocol"]
115
+
116
+ if accepted =~ /(^|,)\s*#{Protocol}\s*;\s*expires\s*=\s*(.+?)(,|$)/
117
+ warn "#{caller[0]}: warning:" +
118
+ " This version of VLAAH protocol is deprecated;" +
119
+ " #{Protocol} will be expired on #{$2.strip}"
120
+ end
121
+
122
+ xml = ::REXML::Document.new(response.body).root
123
+
124
+ if xml.name == "error" and xml.attributes["type"] =~ /application-key/
125
+ raise ApplicationKeyError,
126
+ "Invalid VLAAH::Base.application_key",
127
+ caller[1..-1]
128
+ end
129
+
130
+ return xml
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,156 @@
1
+ require 'vlaah/topic'
2
+ require 'date'
3
+
4
+ module VLAAH
5
+ # = Comment
6
+ #
7
+ # It represents comments of the topic. Comments are also kind of topic in
8
+ # VLAAH. So it inherits VLAAH::Topic.
9
+ #
10
+ # VLAAH::Comment.superclass #=> VLAAH::Topic
11
+ # VLAAH::Topic.find("?1234567").class #=> VLAAH::Comment
12
+ #
13
+ # == Getting Comment Body
14
+ #
15
+ # To get body of the comment, call +body+ method. You can customize the name
16
+ # and the attributes of the wrapping paragraph element with arguments.
17
+ #
18
+ # comment.body
19
+ # #=> '<p>Hello, <a href="http://vlaah.com/VLAAH" class="topic">VLAAH'
20
+ # '</a> <em>world</em>!</p>'
21
+ # comment.body("blockquote")
22
+ # #=> '<blockquote>Hello, <a href="http://vlaah.com/VLAAH"'
23
+ # ' class="topic">VLAAH</a> <em>world</em>!</blockquote>'
24
+ # comment.body("q", :class => "vlaah-comment")
25
+ # #=> '<q class="vlaah-comment">Hello, <a href="http://vlaah.com/VLAAH"'
26
+ # ' class="topic">VLAAH</a> <em>world</em>!</q>'
27
+ #
28
+ # If you want just a plain text, pass +nil+.
29
+ #
30
+ # comment.body(nil) #=> "Hello, VLAAH world!"
31
+ #
32
+ # It may be +nil+.
33
+ #
34
+ # comment_without_body.body #=> nil
35
+ #
36
+ # == Getting Author
37
+ #
38
+ # comment.author #=> <VLAAH::Person name="~dahlia" ...>
39
+ # comment.author.name #=> "~dahlia"
40
+ # comment.author.nick #=> "Hong, MinHee"
41
+ #
42
+ # +author+ may be +nil+.
43
+ #
44
+ # anonymous_comment.author #=> nil
45
+ class Comment < Topic
46
+ include ::Comparable
47
+
48
+ NamePattern = /^\?[1-9]\d*$/
49
+
50
+ # Returns the comment named +name+. May be +nil+.
51
+ def self.find(name)
52
+ return unless NamePattern =~ name
53
+
54
+ xml = raw_data(name).root
55
+ new(xml) if xml.attributes.include? 'type'
56
+ end
57
+
58
+ # :plus or :minus.
59
+ #
60
+ # plus_comment.type #=> :plus
61
+ # minus_comment.type #=> :minus
62
+ def type
63
+ raw_data("@type").first.value.to_sym
64
+ end
65
+
66
+ # Returns +true+ if its type is plus.
67
+ #
68
+ # plus_comment.plus? #=> true
69
+ # minus_comment.plus? #=> false
70
+ def plus?
71
+ type == :plus
72
+ end
73
+
74
+ # Returns +true+ if its type is minus.
75
+ #
76
+ # plus_comment.minus? #=> false
77
+ # minus_comment.minus? #=> true
78
+ def minus?
79
+ type == :minus
80
+ end
81
+
82
+ # The created time. +DateTime+ instance.
83
+ #
84
+ # comment.created_at #=> #<DateTime: 70694857241/28800,0,2299161>
85
+ # comment.created_at.to_s #=> "2008-08-04T01:02:03+00:00"
86
+ def created_at
87
+ DateTime.parse raw_data("@created-at").first.value
88
+ end
89
+
90
+ # The author. +Person+ instance.
91
+ #
92
+ # comment.author #=> <VLAAH::Person name="~dahlia" nick="Hong">
93
+ # comment.author.name #=> "~dahlia"
94
+ def author
95
+ result = raw_data("author")
96
+ Person.new(result.first) unless result.empty?
97
+ end
98
+
99
+ # Returns the body string. The link to topic includes class="topic".
100
+ # The paragraph is wrapped into +tag+ element with +attrs+.
101
+ #
102
+ # comment.body
103
+ # #=> '<p>Hello, <a href="http://vlaah.com/VLAAH" class="topic">'
104
+ # 'VLAAH</a> <em>world</em>!</p>'
105
+ # comment.body("blockquote")
106
+ # #=> '<blockquote>Hello, <a href="http://vlaah.com/VLAAH"'
107
+ # ' class="topic">VLAAH</a> <em>world</em>!</blockquote>'
108
+ # comment.body("q", :class => "comment")
109
+ # #=> '<q class="comment">Hello, <a href="http://vlaah.com/VLAAH"'
110
+ # ' class="topic">VLAAH</a> <em>world</em>!</q>'
111
+ #
112
+ # If you want just a plain text, pass +nil+.
113
+ #
114
+ # comment.body(nil) #=> "Hello, VLAAH world!"
115
+ #
116
+ # It may be +nil+.
117
+ #
118
+ # comment_without_body.body #=> nil
119
+ def body(tag = "p", attrs = {})
120
+ result = raw_data("body/*")
121
+ return nil if result.empty?
122
+
123
+ if tag.nil?
124
+ raw_data("body//text()").map { |n| n.value }.join(" ").gsub(/\s+/, ' ')
125
+ else
126
+ element = result.first
127
+ element.name = tag
128
+
129
+ for key in element.attributes.keys
130
+ element.delete_attribute(key)
131
+ end
132
+
133
+ for key, value in attrs
134
+ element.add_attribute(key.to_s, value.to_s)
135
+ end
136
+
137
+ element.to_s
138
+ end
139
+ end
140
+
141
+ # Compares with the another comment by +created_at+ time.
142
+ def <=>(operand)
143
+ name[1..-1].to_i <=> operand.name[1..-1].to_i
144
+ end
145
+
146
+ def inspect
147
+ if self.body
148
+ "<#{self.class.name} name=#{name.inspect}" +
149
+ " type=#{type} body=#{body.inspect}>"
150
+ else
151
+ super
152
+ end
153
+ end
154
+ end
155
+ end
156
+
@@ -0,0 +1,148 @@
1
+ require 'vlaah/base'
2
+
3
+ module VLAAH
4
+ # This container class is used for various lists of comments.
5
+ # e.g. VLAAH::Topic#comments, VLAAH::Person#feedback
6
+ #
7
+ # It accepts only instances of VLAAH::Comment. It is immutable.
8
+ class CommentList < Base
9
+ include ::Enumerable
10
+
11
+ def initialize(*resource)
12
+ @resource = resource.flatten
13
+ end
14
+
15
+ # The number of comments. May be zero.
16
+ def length
17
+ raw_data("@cardinality", nil, :first).value.to_i
18
+ end
19
+
20
+ alias_method :size, :length
21
+
22
+ # Returns +true+ when +self+ contains no comments.
23
+ def empty?
24
+ length.zero?
25
+ end
26
+
27
+ # Returns the comment at index.
28
+ # A negative index counts from the end of self.
29
+ # Returns +nil+ if the index is out of range.
30
+ # See also VLAAH::Comment#[].
31
+ def at(index)
32
+ return nil if empty?
33
+ return nil unless page = index_to_page(index)
34
+ page, rest = page
35
+ Comment.new raw_data("//comment", page).at(rest)
36
+ end
37
+
38
+ # Returns the ccomment at index, or returns a subarray starting at index and
39
+ # continuing for length comments, or returns a subarray specified by index
40
+ # +Range+ instance.
41
+ # Negative indices count backward from the end of the list (-1 is the last
42
+ # comment).
43
+ def slice(index, length = nil)
44
+ unless length.nil?
45
+ to = index + length
46
+ index = if index < 0 and to >= 0
47
+ index..-1
48
+ else
49
+ index...to
50
+ end
51
+ end
52
+
53
+ if index.is_a? ::Range
54
+ return [] if empty?
55
+ begin_page, begin_offset = index_to_page(index.begin)
56
+ end_page, end_offset = index_to_page(index.end)
57
+
58
+ return [] if begin_page.nil? or end_page.nil?
59
+
60
+ comments = []
61
+
62
+ for page in begin_page..end_page
63
+ range = if page == begin_page and begin_page == end_page
64
+ ::Range.new(begin_offset, end_offset, index.exclude_end?)
65
+ elsif page == begin_page
66
+ begin_offset..-1
67
+ elsif page == end_page
68
+ ::Range.new(0, end_offset, index.exclude_end?)
69
+ else
70
+ 0..-1
71
+ end
72
+
73
+ for comment in raw_data("//comment", page)[range]
74
+ comments << Comment.new(comment)
75
+ end
76
+ end
77
+
78
+ comments
79
+ else
80
+ at(index)
81
+ end
82
+ end
83
+
84
+ alias_method :[], :slice
85
+
86
+ # Returns the first comment. If the list is empty, returns +nil+.
87
+ def first
88
+ self[0]
89
+ end
90
+
91
+ # Returns the last comment. If the list is empty, returns +nil+.
92
+ def last
93
+ self[-1]
94
+ end
95
+
96
+ # Calls block once for each element in self,
97
+ # passing that comment as a parameter.
98
+ def each # :yields: comment
99
+ length = raw_data("@total-pages", nil, :first).value.to_i
100
+
101
+ for page in (1..length)
102
+ for comment in raw_data("//comment", page)
103
+ yield Comment.new(comment)
104
+ end
105
+ end
106
+ end
107
+
108
+ # Returns a new array containing comments of the list in reverse order.
109
+ def reverse
110
+ to_a.reverse
111
+ end
112
+
113
+ def inspect
114
+ len = length
115
+
116
+ list = raw_data("//comment/@name").map do |c|
117
+ "<VLAAH::Comment name=#{c.value.inspect}>"
118
+ end
119
+
120
+ if list.length == len
121
+ "[#{list.join(', ')}]"
122
+ else
123
+ "[#{list.join(', ')} ... (#{length} comments)]"
124
+ end
125
+ end
126
+
127
+ def limit_per_page
128
+ if defined? @limit
129
+ @limit
130
+ else
131
+ @limit = raw_data("//comment").length
132
+ end
133
+ end
134
+
135
+ def index_to_page(index)
136
+ return nil if index >= length
137
+ index = length + index if index < 0
138
+ return nil if index < 0
139
+ [index / limit_per_page + 1, index % limit_per_page]
140
+ end
141
+
142
+ def raw_data(xpath, page = nil, method = :match)
143
+ page = nil if page.is_a? Integer and page < 2
144
+ data = self.class.raw_data(@resource, page)
145
+ return ::REXML::XPath.__send__(method, data, xpath)
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,32 @@
1
+
2
+ module VLAAH
3
+ class Locale
4
+ # Creates a new Locale instance.
5
+ #
6
+ # Locale.new("ko_kr") #=> ko_KR
7
+ def initialize(locale)
8
+ @language, @country = locale.split(/[^[:alpha:]]+/)
9
+ end
10
+
11
+ # The language.
12
+ #
13
+ # Locale.new("ko_kr").language #=> "ko"
14
+ def language
15
+ @language.downcase
16
+ end
17
+
18
+ # The country
19
+ #
20
+ # Locale.new("en_us").country #=> "US"
21
+ def country
22
+ @country.upcase
23
+ end
24
+
25
+ # Convert to a string.
26
+ #
27
+ # Locale.new("ja_jp").to_s #=> "ja_JP"
28
+ def to_s
29
+ "#{language}_#{country}"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,157 @@
1
+ require 'vlaah/topic'
2
+ require 'vlaah/locale'
3
+ require 'date'
4
+ require 'rexml/xpath'
5
+
6
+ module VLAAH
7
+ # = Person: VLAAH User
8
+ #
9
+ # It represents the user of VLAAH. Users are kind of topic in VLAAH.
10
+ # So it inherits VLAAH::Topic.
11
+ #
12
+ # VLAAH::Person.superclass #=> VLAAH::Topic
13
+ # VLAAH::Topic.find("~dahlia").class #=> VLAAH::Person
14
+ #
15
+ # == Getting Favorites
16
+ #
17
+ # To get a set of one’s favorites, call +favorites+ method. It returns an
18
+ # instance of VLAAH::CommentList.
19
+ #
20
+ # person.favorites #=> [<VLAAH::Comment name="?12345">,
21
+ # <VLAAH::Comment name="?6789"> ... (531 comments)]
22
+ #
23
+ # If you want to find topics instead of comments, +map+ it.
24
+ #
25
+ # person.favorites[0..5].map { |comment| comment.topic.name }
26
+ # #=> ["Smalltalk", "Scheme", "Perl", "C++", "VLAAH", "Jamiroquai"]
27
+ #
28
+ # == Getting Friends
29
+ #
30
+ # There is no “friends”-like feature in VLAAH exactly. But VLAAH defines
31
+ # _friend_ as relation that give a plus or a minus each other.
32
+ #
33
+ # person.friends #=> [<VLAAH::Person name="~dahlia">, ...]
34
+ #
35
+ # Person#friends returns an array of people. It may be empty. If you want
36
+ # friends’ comments, you can use +comments+ singleton method of the array.
37
+ #
38
+ # person.friends.comments #=> [<VLAAH::Comment name="~54321">,
39
+ # <VLAAH::Comment name="~4321">
40
+ # ... (14092 comments)]
41
+ #
42
+ # == Getting Feedback
43
+ #
44
+ # If someone comments to your comment, it is a _recursive comment_.
45
+ # VLAAH defines _feedback_ as recursive comments about you.
46
+ #
47
+ # To find feedback, call +feedback+ method. It returns VLAAH::CommentList.
48
+ #
49
+ # person.feedback #=> [<VLAAH::Comment name="?9123"> ... (8192 comments)]
50
+ class Person < Topic
51
+ NamePattern = /^~[-_.a-z0-9]{3,32}$/
52
+
53
+ # Returns the person named name. May be +nil+.
54
+ def self.find(name)
55
+ return unless NamePattern =~ name
56
+
57
+ xml = raw_data(name).root
58
+ new(xml) if xml.attributes.include? 'nick'
59
+ end
60
+
61
+ # Returns the nick name.
62
+ def nick
63
+ raw_data("@nick").first.value
64
+ end
65
+
66
+ # The URL string of its picture.
67
+ #
68
+ # person.picture_url #=> "http://static.vlaah.com/.../default.128.gif"
69
+ def picture_url
70
+ raw_data("@picture-url").first.value
71
+ end
72
+
73
+ # Returns :male or :female. May be +nil+.
74
+ #
75
+ # boy.gender #=> :male
76
+ # girl.gender #=> :female
77
+ # unidentified.gender #=> nil
78
+ def gender
79
+ result = raw_data("@gender")
80
+ result.first.value.to_sym unless result.empty?
81
+ end
82
+
83
+ # Returns +true+ if gender is male.
84
+ # May be +nil+ when it does not have +gender+ field.
85
+ #
86
+ # boy.male? #=> true
87
+ # girl.male? #=> false
88
+ # unidentified.male? #=> nil
89
+ def male?
90
+ gender == :male if gender
91
+ end
92
+
93
+ # Returns +true+ if gender is female.
94
+ # May be +nil+ when it does not have +gender+ field.
95
+ #
96
+ # boy.female? #=> false
97
+ # girl.female? #=> true
98
+ # unidentified.female? #=> nil
99
+ def female?
100
+ gender == :female if gender
101
+ end
102
+
103
+ # Returns its birthday. May be +nil+.
104
+ def birthday
105
+ result = raw_data("@birthday")
106
+ Date.strptime(result.first.value) unless result.empty?
107
+ end
108
+
109
+ # Returns its locale. (An instance of VLAAH::Locale.)
110
+ #
111
+ # korean.locale #=> ko_KR
112
+ # japanese.locale #=> ja_JP
113
+ def locale
114
+ result = raw_data("@locale")
115
+ Locale.new(result.first.value) unless result.empty?
116
+ end
117
+
118
+ # Returns an array of the friends.
119
+ # You can find also comments that writed the friends with +comments+ method.
120
+ #
121
+ # person.friends #=> [<VLAAH::Person name="~dahlia" ...>,
122
+ # <VLAAH::Person name="~shinvee" ...> ...]
123
+ # person.friends.comments #=> [<VLAAH::Comment name="?1234">,
124
+ # <VLAAH::Comment name="?567">
125
+ # ... (1234 comments)]
126
+ def friends
127
+ xml = self.class.raw_data(normalized_name, "friends")
128
+ friends = ::REXML::XPath.match(xml, "//person").map(&Person.method(:new))
129
+ friends.instance_variable_set(:@__vlaah_person_name, name)
130
+
131
+ def friends.comments
132
+ CommentList.new @__vlaah_person_name, "friends"
133
+ end
134
+
135
+ return friends
136
+ end
137
+
138
+ # Returns a list of comments that written by the person.
139
+ # (VLAAH::CommentList)
140
+ def favorites
141
+ CommentList.new name, "favorites"
142
+ end
143
+
144
+ # Returns a list of comments that appended in favorites of the person.
145
+ # (VLAAH::CommentList)
146
+ def feedback
147
+ CommentList.new name, "feedback"
148
+ end
149
+
150
+ alias_method :feedbacks, :feedback
151
+
152
+ def inspect
153
+ "<#{self.class.name} name=#{name.inspect} nick=#{nick.inspect}>"
154
+ end
155
+ end
156
+ end
157
+