fakeetc 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/NEWS +13 -0
- data/README.md +40 -4
- data/lib/fakeetc/base.rb +24 -2
- data/lib/fakeetc/groups.rb +73 -36
- data/lib/fakeetc/system.rb +3 -1
- data/lib/fakeetc/users.rb +89 -9
- data/lib/fakeetc/version.rb +3 -2
- data/lib/fakeetc.rb +2 -0
- data/spec/fakeetc_spec.rb +99 -2
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 478ba20702c29d670a5e38f12c8417528c8c04f8
|
4
|
+
data.tar.gz: d8fe0b47e4bccb6208bdb87bbae7f08fdcb28f12
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2e2517977a497aab29a9a948df92ec1b016f0c0eea4ce97662ec902064c5704840468fd594f57cea4836d1edb5c183467db7a3f815c5a68394d46b386eb680c
|
7
|
+
data.tar.gz: 3515da34591c0e5de8dc75ad5afacac713122b160a5a8d0234993e68889c9bd8704fae1da8e32846e2dd3ca3b309366b77e5f04f70013ee3e4c6a1ef3f7d2716
|
data/NEWS
ADDED
data/README.md
CHANGED
@@ -18,13 +18,49 @@ Usage
|
|
18
18
|
require 'fakeetc'
|
19
19
|
|
20
20
|
FakeEtc.add_groups({
|
21
|
-
'
|
22
|
-
'
|
21
|
+
'empty' => { gid: 42, mem: [] },
|
22
|
+
'anonymous' => { gid: 43, mem: ['johndoe'] }
|
23
23
|
})
|
24
|
+
FakeEtc.add_users({
|
25
|
+
'janedoe' => { uid: 10,
|
26
|
+
gid: 20,
|
27
|
+
gecos: 'Jane Doe',
|
28
|
+
dir: '/home/janedoe',
|
29
|
+
shell: '/bin/zsh' },
|
30
|
+
'jackdoe' => { uid: 50,
|
31
|
+
gid: 60,
|
32
|
+
gecos: 'Jack Doe',
|
33
|
+
dir: '/home/jackdoe',
|
34
|
+
shell: '/bin/bash' },
|
35
|
+
})
|
36
|
+
|
37
|
+
anonymous = nil
|
38
|
+
jack = nil
|
39
|
+
|
24
40
|
FakeEtc do
|
25
|
-
Etc.getgrnam('
|
41
|
+
anonymous = Etc.getgrnam('anonymous')
|
42
|
+
jack = Etc.getpwuid(50)
|
26
43
|
end
|
27
|
-
|
44
|
+
|
45
|
+
anonymous
|
46
|
+
# => #<struct Struct::Group
|
47
|
+
# name="anonymous",
|
48
|
+
# passwd="x",
|
49
|
+
# gid=43,
|
50
|
+
# mem=["johndoe"]>
|
51
|
+
|
52
|
+
jack
|
53
|
+
# => #<struct Struct::Passwd
|
54
|
+
# name="jackdoe",
|
55
|
+
# passwd="x",
|
56
|
+
# uid=50,
|
57
|
+
# gid=60,
|
58
|
+
# gecos="Jack Doe",
|
59
|
+
# dir="/home/jackdoe",
|
60
|
+
# shell="/bin/bash",
|
61
|
+
# change=nil,
|
62
|
+
# uclass=nil,
|
63
|
+
# expire=nil>
|
28
64
|
```
|
29
65
|
|
30
66
|
Copyright
|
data/lib/fakeetc/base.rb
CHANGED
@@ -26,13 +26,19 @@
|
|
26
26
|
|
27
27
|
RealEtc = Etc
|
28
28
|
|
29
|
-
module FakeEtc
|
29
|
+
module FakeEtc # rubocop:disable Documentation
|
30
30
|
@groups = {}
|
31
|
+
@users = {}
|
31
32
|
|
33
|
+
# Checks whether FakeEtc is currently activated.
|
34
|
+
# @return [Bool] true if FakeEtc is currently activated, false if it
|
35
|
+
# is not
|
32
36
|
def self.activated?
|
33
37
|
@activated
|
34
38
|
end
|
35
39
|
|
40
|
+
# Activates FakeEtc.
|
41
|
+
# @return [void]
|
36
42
|
def self.activate
|
37
43
|
@activated = true
|
38
44
|
Object.class_eval do
|
@@ -41,6 +47,8 @@ module FakeEtc
|
|
41
47
|
end
|
42
48
|
end
|
43
49
|
|
50
|
+
# Deactivates FakeEtc.
|
51
|
+
# @return [void]
|
44
52
|
def self.deactivate
|
45
53
|
Object.class_eval do
|
46
54
|
remove_const :Etc
|
@@ -49,6 +57,8 @@ module FakeEtc
|
|
49
57
|
@activated = false
|
50
58
|
end
|
51
59
|
|
60
|
+
# Runs a code block with FakeEtc.
|
61
|
+
# @return [Object] the block's return value
|
52
62
|
def self.with
|
53
63
|
if activated?
|
54
64
|
yield
|
@@ -62,6 +72,8 @@ module FakeEtc
|
|
62
72
|
end
|
63
73
|
end
|
64
74
|
|
75
|
+
# Runs a code block without FakeEtc.
|
76
|
+
# @return [Object] the block's return value
|
65
77
|
def self.without
|
66
78
|
if !activated?
|
67
79
|
yield
|
@@ -76,7 +88,17 @@ module FakeEtc
|
|
76
88
|
end
|
77
89
|
end
|
78
90
|
|
79
|
-
|
91
|
+
# Runs a code block with FakeEtc.
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# FakeEtc.add_groups('foo' => { gid: 42, mem: %w(bar baz) })
|
95
|
+
# FakeEtc do
|
96
|
+
# Etc.getgrnam('foo').gid
|
97
|
+
# end
|
98
|
+
# # => 42
|
99
|
+
#
|
100
|
+
# @return [Object] the block's return value
|
101
|
+
def FakeEtc(&block) # rubocop:disable Style/MethodName
|
80
102
|
return ::FakeEtc unless block
|
81
103
|
::FakeEtc.with(&block)
|
82
104
|
end
|
data/lib/fakeetc/groups.rb
CHANGED
@@ -1,46 +1,83 @@
|
|
1
|
-
module FakeEtc
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
1
|
+
module FakeEtc # rubocop:disable Documentation
|
2
|
+
class << self
|
3
|
+
# Adds groups to the FakeEtc group list.
|
4
|
+
#
|
5
|
+
# @param group_hash [Hash{String=>Hash{Symbol=>Integer,String}}]
|
6
|
+
# the list of groups that should be added
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# FakeEtc.add_groups({
|
10
|
+
# 'empty' => { gid: 42, mem: [] },
|
11
|
+
# 'anonymous' => { gid: 43, mem: ['johndoe'] }
|
12
|
+
# })
|
13
|
+
#
|
14
|
+
# @return [void]
|
15
|
+
def add_groups(group_hash)
|
16
|
+
passwd = 'x'
|
17
|
+
group_hash.each do |group_name, group_info|
|
18
|
+
group = Struct::Group.new(group_name,
|
19
|
+
passwd,
|
20
|
+
group_info[:gid],
|
21
|
+
group_info[:mem])
|
22
|
+
@groups[group_name] = group
|
23
|
+
end
|
9
24
|
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.clear_groups
|
13
|
-
@groups = {}
|
14
|
-
end
|
15
25
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
26
|
+
# Clears the group list.
|
27
|
+
# @return [void]
|
28
|
+
def clear_groups
|
29
|
+
@groups = {}
|
30
|
+
end
|
21
31
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
group
|
26
|
-
|
32
|
+
# Finds a group by its group name.
|
33
|
+
# @param group_name [String] the group's name
|
34
|
+
# @return [Struct::Group] the group
|
35
|
+
# @raise [ArgumentError] if no group with the given name can be
|
36
|
+
# found
|
37
|
+
def getgrnam(group_name)
|
38
|
+
group = @groups[group_name]
|
39
|
+
fail ArgumentError, "can't find group for #{group_name}" if group.nil?
|
40
|
+
group
|
41
|
+
end
|
27
42
|
|
28
|
-
|
29
|
-
@
|
30
|
-
@
|
31
|
-
|
43
|
+
# Finds a group by its gid.
|
44
|
+
# @param gid [Integer] the group's gid
|
45
|
+
# @return [Struct::Group] the group
|
46
|
+
# @raise [ArgumentError] if no group with the given gid can be
|
47
|
+
# found
|
48
|
+
def getgrgid(gid)
|
49
|
+
group = @groups.values.find { |g| g.gid == gid }
|
50
|
+
fail ArgumentError, "can't find group for #{gid}" if group.nil?
|
51
|
+
group
|
52
|
+
end
|
32
53
|
|
33
|
-
|
34
|
-
|
35
|
-
|
54
|
+
# Returns an entry from the group list. Each successive call
|
55
|
+
# returns the next entry or `nil` if the end of the list has been
|
56
|
+
# reached.
|
57
|
+
#
|
58
|
+
# To reset scanning the group list, use {endgrent}.
|
59
|
+
#
|
60
|
+
# @return [Struct::Group] the next entry in the group list
|
61
|
+
def getgrent
|
62
|
+
@grents ||= @groups.values
|
63
|
+
@grents.shift
|
64
|
+
end
|
36
65
|
|
37
|
-
|
38
|
-
return
|
66
|
+
# Ends the process of scanning through the group list.
|
67
|
+
# @return [void]
|
68
|
+
def endgrent
|
69
|
+
@grents = nil
|
70
|
+
end
|
71
|
+
alias_method :setgrent, :endgrent
|
39
72
|
|
40
|
-
|
41
|
-
|
73
|
+
# Executes a block for each group entry.
|
74
|
+
# @yield [Struct::Group] the group entry
|
75
|
+
# @return [void]
|
76
|
+
def group
|
77
|
+
return getgrent unless block_given?
|
78
|
+
@groups.values.each { |g| yield g }
|
79
|
+
endgrent
|
80
|
+
nil
|
42
81
|
end
|
43
|
-
endgrent
|
44
|
-
nil
|
45
82
|
end
|
46
83
|
end
|
data/lib/fakeetc/system.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
module FakeEtc
|
1
|
+
module FakeEtc # rubocop:disable Documentation
|
2
|
+
# @return [String] the system's configuration directory
|
2
3
|
def self.sysconfdir
|
3
4
|
RealEtc.sysconfdir
|
4
5
|
end
|
5
6
|
|
7
|
+
# @return [String] the system's temp directory
|
6
8
|
def self.systmpdir
|
7
9
|
RealEtc.systmpdir
|
8
10
|
end
|
data/lib/fakeetc/users.rb
CHANGED
@@ -1,14 +1,94 @@
|
|
1
|
-
module FakeEtc
|
1
|
+
module FakeEtc # rubocop:disable Documentation
|
2
2
|
class << self
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
# Adds users to the FakeEtc user list.
|
4
|
+
#
|
5
|
+
# @param user_hash [Hash{String=>Hash{Symbol=>Integer,String}}]
|
6
|
+
# the list of users that should be added
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# FakeEtc.add_users({
|
10
|
+
# 'janedoe' => { uid: 10,
|
11
|
+
# gid: 20,
|
12
|
+
# gecos: 'Jane Doe',
|
13
|
+
# dir: '/home/janedoe',
|
14
|
+
# shell: '/bin/zsh' },
|
15
|
+
# 'jackdoe' => { uid: 50,
|
16
|
+
# gid: 60,
|
17
|
+
# gecos: 'Jack Doe',
|
18
|
+
# dir: '/home/jackdoe',
|
19
|
+
# shell: '/bin/bash' },
|
20
|
+
# })
|
21
|
+
#
|
22
|
+
# @return [void]
|
23
|
+
def add_users(user_hash)
|
24
|
+
passwd = 'x'
|
25
|
+
|
26
|
+
user_hash.each do |user_name, user_info|
|
27
|
+
user = Struct::Passwd.new(user_name,
|
28
|
+
passwd,
|
29
|
+
user_info[:uid],
|
30
|
+
user_info[:gid],
|
31
|
+
user_info[:gecos],
|
32
|
+
user_info[:dir],
|
33
|
+
user_info[:shell])
|
34
|
+
@users[user_name] = user
|
11
35
|
end
|
12
36
|
end
|
37
|
+
|
38
|
+
# Clears the user list.
|
39
|
+
# @return [void]
|
40
|
+
def clear_users
|
41
|
+
@users = {}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Finds a user by their user name.
|
45
|
+
# @param user_name [String] the user's name
|
46
|
+
# @return [Struct::Passwd] the user
|
47
|
+
# @raise [ArgumentError] if no user with the given name can be
|
48
|
+
# found
|
49
|
+
def getpwnam(user_name)
|
50
|
+
user = @users[user_name]
|
51
|
+
fail ArgumentError, "can't find user for #{user_name}" if user.nil?
|
52
|
+
user
|
53
|
+
end
|
54
|
+
|
55
|
+
# Finds a user by their user id.
|
56
|
+
# @param uid [Integer] the user's id
|
57
|
+
# @return [Struct::Passwd] the user
|
58
|
+
# @raise [ArgumentError] if no user with the given id can be found
|
59
|
+
def getpwuid(uid)
|
60
|
+
user = @users.values.find { |u| u.uid == uid }
|
61
|
+
fail ArgumentError, "can't find user for #{uid}" if user.nil?
|
62
|
+
user
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns an entry from the user list. Each successive call
|
66
|
+
# returns the next entry or `nil` if the end of the list has been
|
67
|
+
# reached.
|
68
|
+
#
|
69
|
+
# To reset scanning the user list, use {endpwent}.
|
70
|
+
#
|
71
|
+
# @return [Struct::Passwd] the next entry in the user list
|
72
|
+
def getpwent
|
73
|
+
@pwents ||= @users.values
|
74
|
+
@pwents.shift
|
75
|
+
end
|
76
|
+
|
77
|
+
# Ends the process of scanning through the user list.
|
78
|
+
# @return [void]
|
79
|
+
def endpwent
|
80
|
+
@pwents = nil
|
81
|
+
end
|
82
|
+
alias_method :setpwent, :endpwent
|
83
|
+
|
84
|
+
# Executes a block for each user entry.
|
85
|
+
# @yield [Struct::Passwd] the user entry
|
86
|
+
# @return [void]
|
87
|
+
def passwd
|
88
|
+
return getpwent unless block_given?
|
89
|
+
@users.values.each { |u| yield u }
|
90
|
+
endpwent
|
91
|
+
nil
|
92
|
+
end
|
13
93
|
end
|
14
94
|
end
|
data/lib/fakeetc/version.rb
CHANGED
data/lib/fakeetc.rb
CHANGED
data/spec/fakeetc_spec.rb
CHANGED
@@ -5,13 +5,28 @@ require 'fakeetc'
|
|
5
5
|
describe FakeEtc do
|
6
6
|
before(:each) do
|
7
7
|
@groups = {
|
8
|
-
'cheeses' => { gid: 10, mem:
|
9
|
-
'parrots' => { gid: 20, mem:
|
8
|
+
'cheeses' => { gid: 10, mem: %w(red_leicester tilsit) },
|
9
|
+
'parrots' => { gid: 20, mem: %w(norwegian_blue macaw) }
|
10
|
+
}
|
11
|
+
@users = {
|
12
|
+
'norwegian_blue' => { uid: 10,
|
13
|
+
gid: 20,
|
14
|
+
gecos: 'Remarkable Bird',
|
15
|
+
dir: '/home/parrot',
|
16
|
+
shell: '/bin/zsh' },
|
17
|
+
'red_leicester' => { uid: 20,
|
18
|
+
gid: 10,
|
19
|
+
gecos: 'a little Red Leicester',
|
20
|
+
dir: '/home/red_leicester',
|
21
|
+
shell: '/bin/bash' }
|
10
22
|
}
|
11
23
|
FakeEtc.add_groups(@groups)
|
24
|
+
FakeEtc.add_users(@users)
|
12
25
|
end
|
13
26
|
|
14
27
|
after(:each) do
|
28
|
+
FakeEtc.endpwent
|
29
|
+
FakeEtc.clear_users
|
15
30
|
FakeEtc.endgrent
|
16
31
|
FakeEtc.clear_groups
|
17
32
|
end
|
@@ -137,6 +152,88 @@ describe FakeEtc do
|
|
137
152
|
FakeEtc.systmpdir.must_equal RealEtc.systmpdir
|
138
153
|
end
|
139
154
|
end
|
155
|
+
|
156
|
+
describe 'getpwnam' do
|
157
|
+
it 'should find users by name' do
|
158
|
+
red_leicester = FakeEtc.getpwnam('red_leicester')
|
159
|
+
red_leicester.must_be_instance_of Struct::Passwd
|
160
|
+
red_leicester.uid.must_equal @users['red_leicester'][:uid]
|
161
|
+
red_leicester.gid.must_equal @users['red_leicester'][:gid]
|
162
|
+
red_leicester.gecos.must_equal @users['red_leicester'][:gecos]
|
163
|
+
red_leicester.dir.must_equal @users['red_leicester'][:dir]
|
164
|
+
red_leicester.shell.must_equal @users['red_leicester'][:shell]
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'should raise exceptions for non-existent users' do
|
168
|
+
user_name = 'not-a-user'
|
169
|
+
err = -> { FakeEtc.getpwnam(user_name) }.must_raise ArgumentError
|
170
|
+
err.message.must_match "can't find user for #{user_name}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
describe 'getpwuid' do
|
175
|
+
it 'should find users by uid' do
|
176
|
+
norwegian_blue = FakeEtc.getpwuid(@users['norwegian_blue'][:uid])
|
177
|
+
norwegian_blue.must_be_instance_of Struct::Passwd
|
178
|
+
norwegian_blue.uid.must_equal @users['norwegian_blue'][:uid]
|
179
|
+
norwegian_blue.gid.must_equal @users['norwegian_blue'][:gid]
|
180
|
+
norwegian_blue.gecos.must_equal @users['norwegian_blue'][:gecos]
|
181
|
+
norwegian_blue.dir.must_equal @users['norwegian_blue'][:dir]
|
182
|
+
norwegian_blue.shell.must_equal @users['norwegian_blue'][:shell]
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should raise exceptions for non-existent groups' do
|
186
|
+
uid = 247
|
187
|
+
err = -> { FakeEtc.getpwuid(uid) }.must_raise ArgumentError
|
188
|
+
err.message.must_match "can't find user for #{uid}"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe 'getpwent' do
|
193
|
+
it 'should return all user entries in order' do
|
194
|
+
norwegian_blue = FakeEtc.getpwent
|
195
|
+
red_leicester = FakeEtc.getpwent
|
196
|
+
nil_user = FakeEtc.getpwent
|
197
|
+
|
198
|
+
norwegian_blue.name.must_equal 'norwegian_blue'
|
199
|
+
red_leicester.name.must_equal 'red_leicester'
|
200
|
+
nil_user.must_be_nil
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe 'endpwent' do
|
205
|
+
it 'should reset user traversal' do
|
206
|
+
norwegian_blue_1 = FakeEtc.getpwent
|
207
|
+
FakeEtc.endpwent
|
208
|
+
norwegian_blue_2 = FakeEtc.getpwent
|
209
|
+
norwegian_blue_1.must_equal norwegian_blue_2
|
210
|
+
norwegian_blue_1.name.must_equal 'norwegian_blue'
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
describe 'passwd' do
|
215
|
+
it 'should execute a given block for each group entry' do
|
216
|
+
users = {}
|
217
|
+
FakeEtc.passwd do |u|
|
218
|
+
users[u.name] = { uid: u.uid,
|
219
|
+
gid: u.gid,
|
220
|
+
gecos: u.gecos,
|
221
|
+
dir: u.dir,
|
222
|
+
shell: u.shell }
|
223
|
+
end
|
224
|
+
users.must_equal @users
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'should behave like getpwent if there was no block given' do
|
228
|
+
norwegian_blue = FakeEtc.passwd
|
229
|
+
red_leicester = FakeEtc.passwd
|
230
|
+
nil_user = FakeEtc.passwd
|
231
|
+
|
232
|
+
norwegian_blue.name.must_equal 'norwegian_blue'
|
233
|
+
red_leicester.name.must_equal 'red_leicester'
|
234
|
+
nil_user.must_be_nil
|
235
|
+
end
|
236
|
+
end
|
140
237
|
end
|
141
238
|
|
142
239
|
describe 'FakeEtc' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fakeetc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastian Boehm
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ~>
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 5.5.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: yard
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.8.7.6
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.8.7.6
|
41
55
|
description: |
|
42
56
|
FakeEtc is a fake Etc module for your tests.
|
43
57
|
email:
|
@@ -49,6 +63,7 @@ files:
|
|
49
63
|
- Rakefile
|
50
64
|
- README.md
|
51
65
|
- LICENSE
|
66
|
+
- NEWS
|
52
67
|
- lib/fakeetc/base.rb
|
53
68
|
- lib/fakeetc/groups.rb
|
54
69
|
- lib/fakeetc/system.rb
|
@@ -84,3 +99,4 @@ summary: A fake Etc module for your tests
|
|
84
99
|
test_files:
|
85
100
|
- spec/fakeetc_spec.rb
|
86
101
|
- spec/spec_helper.rb
|
102
|
+
has_rdoc:
|