ytag 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/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
|