ytag 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +62 -0
- data/lib/ytag.rb +24 -0
- data/lib/ytag/bundle.rb +15 -0
- data/lib/ytag/tag.rb +140 -0
- data/test/tagify.rb +259 -0
- metadata +83 -0
data/README
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
== What
|
2
|
+
Tagging library for Ruby
|
3
|
+
|
4
|
+
Provide a robust tagging API that solves the problems posed by Acidus:
|
5
|
+
|
6
|
+
1. How to handle multiple words
|
7
|
+
2. If/how to allow tag delimiter inside a tag
|
8
|
+
3. Does letter case matter
|
9
|
+
4. Punctuation and symbols
|
10
|
+
5. Handling plural or singular words
|
11
|
+
6. Date formating
|
12
|
+
7. Multiple language support
|
13
|
+
8. Colloquialisms/slang
|
14
|
+
|
15
|
+
YTag handles cases 1, 2, 3, and 4, as these are syntactic issues. Cases 5, 6,
|
16
|
+
7, and 8 are more interesting. I think they should really be left to user
|
17
|
+
creativity. Case 6 is not a problem because YTag maintains independent date and
|
18
|
+
time stamps for tags, so even if I encode it in the tag in a weird manner, any
|
19
|
+
user will be able to get at an ISO-standard date/time stamp.
|
20
|
+
|
21
|
+
At the end of the day, YTag draws a lot of inspiration from Consumating [RIP]
|
22
|
+
and del.icio.us.
|
23
|
+
|
24
|
+
References:
|
25
|
+
http://neosmart.net/blog/2007/the-need-for-creating-tag-standards/
|
26
|
+
http://www.memestreams.net/users/acidus/blogid152074
|
27
|
+
|
28
|
+
== How
|
29
|
+
require 'rubygems'
|
30
|
+
require 'ytag'
|
31
|
+
|
32
|
+
Dependencies:
|
33
|
+
- HTMLEntities gem
|
34
|
+
- REX gem
|
35
|
+
- RCL gem
|
36
|
+
|
37
|
+
What you get:
|
38
|
+
|
39
|
+
YTag::Tag#new(name, user, active=true, hidden=false)
|
40
|
+
YTag::Tag#create
|
41
|
+
YTag::Tag#modify
|
42
|
+
YTag::Tag#access
|
43
|
+
YTag::Tag#print
|
44
|
+
|
45
|
+
YTag::tagify(tag)
|
46
|
+
|
47
|
+
== Install
|
48
|
+
To construct a Ruby gem, just type:
|
49
|
+
|
50
|
+
rake gem
|
51
|
+
|
52
|
+
To install it, type:
|
53
|
+
|
54
|
+
sudo gem install pkg/ytag-0.0.1.gem
|
55
|
+
|
56
|
+
== Notes
|
57
|
+
I haven't tested this code with YARV, so YMMV.
|
58
|
+
|
59
|
+
== Contribute
|
60
|
+
The source is availble at github: git://github.com/yesmar/ytag.git. Feel free
|
61
|
+
to clone it and implement your own awesome ideas. You can also send your
|
62
|
+
patches by email to yesmar[at]speakeasy[dot]net.
|
data/lib/ytag.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# ytag.rb
|
4
|
+
# yesmar@speakeasy.net
|
5
|
+
|
6
|
+
module YTag
|
7
|
+
YTag::NAME = 'ytag'
|
8
|
+
YTag::VERSION = '0.0.1'
|
9
|
+
YTag::COPYRIGHT = 'Copyright (c) 2008 Ramsey Dow'
|
10
|
+
def self.copyright() YTag::COPYRIGHT end
|
11
|
+
def self.version() YTag::VERSION end
|
12
|
+
def self.libdir() File.expand_path(__FILE__).gsub(%r/\.rb$/, '') end
|
13
|
+
end
|
14
|
+
|
15
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__)+'/ytag')
|
16
|
+
|
17
|
+
require 'tag'
|
18
|
+
require 'bundle'
|
19
|
+
|
20
|
+
# TODO add more Tag unit tests
|
21
|
+
# TODO implement bundle
|
22
|
+
# TODO code up some examples
|
23
|
+
|
24
|
+
raise RuntimeError, 'This library is for require only' if $0 == __FILE__
|
data/lib/ytag/bundle.rb
ADDED
data/lib/ytag/tag.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# tag.rb
|
4
|
+
# yesmar@speakeasy.net
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'htmlentities'
|
8
|
+
require 'rex'
|
9
|
+
require 'rcl'
|
10
|
+
|
11
|
+
module YTag
|
12
|
+
class Tag
|
13
|
+
attr_reader :name, :user, :active, :hidden
|
14
|
+
|
15
|
+
MARKUP = {}
|
16
|
+
BOGUS = 'system:undefined'
|
17
|
+
|
18
|
+
def initialize(name, user, active=true, hidden=false)
|
19
|
+
raise ArgumentError, 'nil name' if name.nil?
|
20
|
+
raise ArgumentError, 'invalid name class' if name.class != String
|
21
|
+
raise ArgumentError, 'empty name' if name.empty?
|
22
|
+
raise ArgumentError, 'nil user' if user.nil?
|
23
|
+
raise ArgumentError, 'invalid user class' if user.class != String
|
24
|
+
raise ArgumentError, 'empty user' if user.empty?
|
25
|
+
raise ArgumentError, 'invalid active class' if \
|
26
|
+
active.class != TrueClass && active.class != FalseClass
|
27
|
+
raise ArgumentError, 'invalid hidden class' if \
|
28
|
+
hidden.class != TrueClass && hidden.class != FalseClass
|
29
|
+
|
30
|
+
@name = YTag::tagify(name)
|
31
|
+
@user = user
|
32
|
+
@active = active
|
33
|
+
@hidden = hidden
|
34
|
+
@timestamp = RCL::Timestamp.new
|
35
|
+
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def create
|
40
|
+
@timestamp.create
|
41
|
+
end
|
42
|
+
|
43
|
+
def modify
|
44
|
+
@timestamp.modify
|
45
|
+
end
|
46
|
+
|
47
|
+
def access
|
48
|
+
@timestamp.access
|
49
|
+
end
|
50
|
+
|
51
|
+
def print(stream=STDOUT)
|
52
|
+
raise 'nil stream' if stream.nil?
|
53
|
+
raise 'invalid stream class' if stream.class != IO
|
54
|
+
|
55
|
+
@timestamp.update_access
|
56
|
+
|
57
|
+
stream << "#{@name} ["
|
58
|
+
stream << 'in' if !@active
|
59
|
+
stream << 'active, '
|
60
|
+
if !@hidden
|
61
|
+
stream << 'public'
|
62
|
+
else
|
63
|
+
stream << 'private'
|
64
|
+
end
|
65
|
+
stream << "]\n"
|
66
|
+
|
67
|
+
stream << "+ added by #{@user} on #{@timestamp.create}\n"
|
68
|
+
stream << "+ last accessed on #{@timestamp.access}\n"
|
69
|
+
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
@name
|
76
|
+
@user
|
77
|
+
@active
|
78
|
+
@hidden
|
79
|
+
@timestamp
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.tagify(tag)
|
83
|
+
raise ArgumentError, 'nil tag' if tag.nil?
|
84
|
+
raise ArgumentError, 'invalid tag class' if tag.class != String
|
85
|
+
raise ArgumentError, 'empty tag' if tag.empty?
|
86
|
+
|
87
|
+
# scrub all markup/scripting
|
88
|
+
tag.scrub!(Tag::MARKUP)
|
89
|
+
|
90
|
+
# from here on, tag may become empty and/or nil after any given operation
|
91
|
+
|
92
|
+
# encode HTML entities
|
93
|
+
tag = HTMLEntities.encode_entities(tag, :basic, :named) \
|
94
|
+
if !tag.nil? && !tag.empty?
|
95
|
+
|
96
|
+
# transform & => _amp_
|
97
|
+
tag.gsub!('&', '_amp_') if !tag.nil? && !tag.empty?
|
98
|
+
|
99
|
+
# transform & => _amp_
|
100
|
+
tag.gsub!('&', '_amp_') if !tag.nil? && !tag.empty?
|
101
|
+
|
102
|
+
# transform ? => _qm
|
103
|
+
tag.gsub!('?', '_qm') if !tag.nil? && !tag.empty?
|
104
|
+
|
105
|
+
# transform ! => _xpt
|
106
|
+
tag.gsub!('!', '_xpt') if !tag.nil? && !tag.empty?
|
107
|
+
|
108
|
+
# transform / => _fs_
|
109
|
+
tag.gsub!('/', '_fs_') if !tag.nil? && !tag.empty?
|
110
|
+
|
111
|
+
# transform \ => _bs_
|
112
|
+
tag.gsub!('\\', '_bs_') if !tag.nil? && !tag.empty?
|
113
|
+
|
114
|
+
# compress whitespace
|
115
|
+
tag.squeeze(' \t') if !tag.nil? && !tag.empty?
|
116
|
+
|
117
|
+
# delete undefined characters
|
118
|
+
tag.delete!('=~@#$%^*()+[]{}|;:\'\"<>') if !tag.nil? && !tag.empty?
|
119
|
+
|
120
|
+
# transform illegal separators
|
121
|
+
tag.tr!(' ,.-', '_') if !tag.nil? && !tag.empty?
|
122
|
+
|
123
|
+
# compress underscores
|
124
|
+
tag.squeeze!('_') if !tag.nil? && !tag.empty?
|
125
|
+
|
126
|
+
# remove preceding underscore
|
127
|
+
tag = tag[1..-1] if !tag.nil? && !tag.empty? && tag[0].chr == '_'
|
128
|
+
|
129
|
+
# remove trailing underscore
|
130
|
+
tag = tag[0..tag.length-2] if !tag.nil? && !tag.empty? && tag[-1].chr == '_'
|
131
|
+
|
132
|
+
# downcase
|
133
|
+
tag.downcase! if !tag.nil? && !tag.empty?
|
134
|
+
|
135
|
+
# handle nil or empty tags
|
136
|
+
tag = Tag::BOGUS if tag.nil? || tag.empty?
|
137
|
+
|
138
|
+
tag
|
139
|
+
end
|
140
|
+
end
|
data/test/tagify.rb
ADDED
@@ -0,0 +1,259 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# tagify.rb
|
4
|
+
# YTag::tagify unit tests
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
|
8
|
+
require 'pathname'
|
9
|
+
dir = Pathname.new(File.expand_path(__FILE__)).realpath
|
10
|
+
require File.join(File.dirname(dir.to_s), '../lib/ytag')
|
11
|
+
|
12
|
+
class TagifyTests < Test::Unit::TestCase
|
13
|
+
def test_nil_string
|
14
|
+
expected = nil
|
15
|
+
actual = nil
|
16
|
+
|
17
|
+
assert_raise ArgumentError do
|
18
|
+
actual = YTag::tagify(nil)
|
19
|
+
end
|
20
|
+
|
21
|
+
assert_nil(actual)
|
22
|
+
assert_instance_of(NilClass, actual)
|
23
|
+
assert_equal(expected, actual)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_invalid_class
|
27
|
+
expected = nil
|
28
|
+
actual = nil
|
29
|
+
|
30
|
+
assert_raise ArgumentError do
|
31
|
+
actual = YTag::tagify(TrueClass)
|
32
|
+
end
|
33
|
+
|
34
|
+
assert_nil(actual)
|
35
|
+
assert_instance_of(NilClass, actual)
|
36
|
+
assert_equal(expected, actual)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_empty_string
|
40
|
+
expected = nil
|
41
|
+
actual = nil
|
42
|
+
|
43
|
+
assert_raise ArgumentError do
|
44
|
+
actual = YTag::tagify('')
|
45
|
+
end
|
46
|
+
|
47
|
+
assert_nil(actual)
|
48
|
+
assert_instance_of(NilClass, actual)
|
49
|
+
assert_equal(expected, actual)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_html_entities_1
|
53
|
+
expected = 'amp_amp_lt_amp_gt'
|
54
|
+
actual = nil
|
55
|
+
|
56
|
+
assert_nothing_raised do
|
57
|
+
actual = YTag::tagify('&<>')
|
58
|
+
end
|
59
|
+
|
60
|
+
assert_not_nil(actual)
|
61
|
+
assert_instance_of(String, actual)
|
62
|
+
assert_equal(expected, actual)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_html_entities_2
|
66
|
+
expected = 'abc_amp_auml'
|
67
|
+
actual = nil
|
68
|
+
|
69
|
+
assert_nothing_raised do
|
70
|
+
actual = YTag::tagify("abc\303\244")
|
71
|
+
end
|
72
|
+
|
73
|
+
assert_not_nil(actual)
|
74
|
+
assert_instance_of(String, actual)
|
75
|
+
assert_equal(expected, actual)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_markup
|
79
|
+
expected = YTag::Tag::BOGUS
|
80
|
+
actual = nil
|
81
|
+
|
82
|
+
assert_nothing_raised do
|
83
|
+
actual = YTag::tagify("<img src=\"javascript:alert('xss');\">")
|
84
|
+
end
|
85
|
+
|
86
|
+
assert_not_nil(actual)
|
87
|
+
assert_instance_of(String, actual)
|
88
|
+
assert_equal(expected, actual)
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_script_1
|
92
|
+
expected = 'document_cookie'
|
93
|
+
actual = nil
|
94
|
+
|
95
|
+
assert_nothing_raised do
|
96
|
+
actual = YTag::tagify('<script>document.cookie</script>')
|
97
|
+
end
|
98
|
+
|
99
|
+
assert_not_nil(actual)
|
100
|
+
assert_instance_of(String, actual)
|
101
|
+
assert_equal(expected, actual)
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_script_2
|
105
|
+
expected = YTag::Tag::BOGUS
|
106
|
+
actual = nil
|
107
|
+
|
108
|
+
assert_nothing_raised do
|
109
|
+
actual = YTag::tagify('<script></script>')
|
110
|
+
end
|
111
|
+
|
112
|
+
assert_not_nil(actual)
|
113
|
+
assert_instance_of(String, actual)
|
114
|
+
assert_equal(expected, actual)
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_whitespace_compression
|
118
|
+
expected = 'mac_os_x'
|
119
|
+
actual = nil
|
120
|
+
|
121
|
+
assert_nothing_raised do
|
122
|
+
actual = YTag::tagify(' Mac OS X ')
|
123
|
+
end
|
124
|
+
|
125
|
+
assert_not_nil(actual)
|
126
|
+
assert_instance_of(String, actual)
|
127
|
+
assert_equal(expected, actual)
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_underscore_compression
|
131
|
+
expected = 'foo_bar'
|
132
|
+
actual = nil
|
133
|
+
|
134
|
+
assert_nothing_raised do
|
135
|
+
actual = YTag::tagify('______foo__________bar___________')
|
136
|
+
end
|
137
|
+
|
138
|
+
assert_not_nil(actual)
|
139
|
+
assert_instance_of(String, actual)
|
140
|
+
assert_equal(expected, actual)
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_undefined_characters
|
144
|
+
expected = 'amp_apos_bs_amp_quot_amp_lt_amp_gt'
|
145
|
+
actual = nil
|
146
|
+
|
147
|
+
assert_nothing_raised do
|
148
|
+
actual = YTag::tagify('=~@#$%^*()+[]{}|;:\'\"<>')
|
149
|
+
end
|
150
|
+
|
151
|
+
assert_not_nil(actual)
|
152
|
+
assert_instance_of(String, actual)
|
153
|
+
assert_equal(expected, actual)
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_illegal_separators
|
157
|
+
expected = 'a_b_c_d_e'
|
158
|
+
actual = nil
|
159
|
+
|
160
|
+
assert_nothing_raised do
|
161
|
+
actual = YTag::tagify('a b,c.d-e')
|
162
|
+
end
|
163
|
+
|
164
|
+
assert_not_nil(actual)
|
165
|
+
assert_instance_of(String, actual)
|
166
|
+
assert_equal(expected, actual)
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_preceding_separarator
|
170
|
+
expected = 'tag'
|
171
|
+
actual = nil
|
172
|
+
|
173
|
+
assert_nothing_raised do
|
174
|
+
actual = YTag::tagify('_tag')
|
175
|
+
end
|
176
|
+
|
177
|
+
assert_not_nil(actual)
|
178
|
+
assert_instance_of(String, actual)
|
179
|
+
assert_equal(expected, actual)
|
180
|
+
end
|
181
|
+
|
182
|
+
def test_trailing_separator
|
183
|
+
expected = 'tag'
|
184
|
+
actual = nil
|
185
|
+
|
186
|
+
assert_nothing_raised do
|
187
|
+
actual = YTag::tagify('tag_')
|
188
|
+
end
|
189
|
+
|
190
|
+
assert_not_nil(actual)
|
191
|
+
assert_instance_of(String, actual)
|
192
|
+
assert_equal(expected, actual)
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_ampersand
|
196
|
+
expected = 'a_amp_b'
|
197
|
+
actual = nil
|
198
|
+
|
199
|
+
assert_nothing_raised do
|
200
|
+
actual = YTag::tagify('a & b')
|
201
|
+
end
|
202
|
+
|
203
|
+
assert_not_nil(actual)
|
204
|
+
assert_instance_of(String, actual)
|
205
|
+
assert_equal(expected, actual)
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_question_mark
|
209
|
+
expected = 'wtf_qm'
|
210
|
+
actual = nil
|
211
|
+
|
212
|
+
assert_nothing_raised do
|
213
|
+
actual = YTag::tagify('wtf?')
|
214
|
+
end
|
215
|
+
|
216
|
+
assert_not_nil(actual)
|
217
|
+
assert_instance_of(String, actual)
|
218
|
+
assert_equal(expected, actual)
|
219
|
+
end
|
220
|
+
|
221
|
+
def test_exclamation_mark
|
222
|
+
expected = 'ausgezeichnet_xpt'
|
223
|
+
actual = nil
|
224
|
+
|
225
|
+
assert_nothing_raised do
|
226
|
+
actual = YTag::tagify('ausgezeichnet_xpt')
|
227
|
+
end
|
228
|
+
|
229
|
+
assert_not_nil(actual)
|
230
|
+
assert_instance_of(String, actual)
|
231
|
+
assert_equal(expected, actual)
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_forward_slash
|
235
|
+
expected = 'fs_etc_fs_passwd'
|
236
|
+
actual = nil
|
237
|
+
|
238
|
+
assert_nothing_raised do
|
239
|
+
actual = YTag::tagify('/etc/passwd')
|
240
|
+
end
|
241
|
+
|
242
|
+
assert_not_nil(actual)
|
243
|
+
assert_instance_of(String, actual)
|
244
|
+
assert_equal(expected, actual)
|
245
|
+
end
|
246
|
+
|
247
|
+
def test_backward_slash
|
248
|
+
expected = 'c_bs_windows_bs_system32'
|
249
|
+
actual = nil
|
250
|
+
|
251
|
+
assert_nothing_raised do
|
252
|
+
actual = YTag::tagify('C:\\WINDOWS\\system32')
|
253
|
+
end
|
254
|
+
|
255
|
+
assert_not_nil(actual)
|
256
|
+
assert_instance_of(String, actual)
|
257
|
+
assert_equal(expected, actual)
|
258
|
+
end
|
259
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ytag
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ramsey Dow
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-03-01 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: htmlentities
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 4.0.0
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: rex
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 0.0.4
|
32
|
+
version:
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rcl
|
35
|
+
version_requirement:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.0.5
|
41
|
+
version:
|
42
|
+
description:
|
43
|
+
email: yesmar @nospam@ speakeasy.net
|
44
|
+
executables: []
|
45
|
+
|
46
|
+
extensions: []
|
47
|
+
|
48
|
+
extra_rdoc_files:
|
49
|
+
- README
|
50
|
+
files:
|
51
|
+
- lib/ytag.rb
|
52
|
+
- lib/ytag/bundle.rb
|
53
|
+
- lib/ytag/tag.rb
|
54
|
+
- test/tagify.rb
|
55
|
+
- README
|
56
|
+
has_rdoc: true
|
57
|
+
homepage: http://ytag.rubyforge.org/
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
version:
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project: http://rubyforge.org/projects/ytag/
|
78
|
+
rubygems_version: 1.0.1
|
79
|
+
signing_key:
|
80
|
+
specification_version: 2
|
81
|
+
summary: Tagging library for Ruby
|
82
|
+
test_files:
|
83
|
+
- test/tagify.rb
|