vlaah 0.9.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/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
+