dle 0.1.7 → 1.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.
- checksums.yaml +4 -4
- data/README.md +30 -2
- data/VERSION +1 -1
- data/lib/dle.rb +1 -0
- data/lib/dle/application.rb +21 -0
- data/lib/dle/application/dispatch.rb +105 -69
- data/lib/dle/application/filter.rb +36 -0
- data/lib/dle/application/filter.tpl +55 -0
- data/lib/dle/helpers.rb +1 -1
- data/lib/dle/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1222ce5b458c3140edf473d1505c02fc789edaf
|
4
|
+
data.tar.gz: e172707845f3c78cc6d5bcff8a2f4ae84eae6786
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 730402aa582c113b6904b3fd511c396679b4621a8981e1caafc7faa174adcca70be21e92e8993fd632595ec5db49cf8ee19729491cb316df5579b50519003447
|
7
|
+
data.tar.gz: 816a8bb37cde6ecae9b49c40392ea89168222495271794d8a7eb3350236231de006232507f116a9aa055a8cafdb7e774c33ff3eb2fe6367b9d0d1d89ec1d2414
|
data/README.md
CHANGED
@@ -5,11 +5,12 @@ You can copy, move, rename, chmod, chown or remove individual files or directori
|
|
5
5
|
|
6
6
|
**BETA product, use at your own risk, use `--simulate` to be sure, always have a backup!**
|
7
7
|
|
8
|
-
|
8
|
+
[](https://www.youtube.com/watch?v=-xfnx3VQvNQ)
|
9
|
+
**[▶ See it in action](https://www.youtube.com/watch?v=-xfnx3VQvNQ)**
|
9
10
|
|
10
11
|
## Requirements
|
11
12
|
|
12
|
-
You will need a UNIX system with a working Ruby installation, sorry Windozer!
|
13
|
+
You will need a UNIX system with a working Ruby (>= 1.9.3) installation, sorry Windozer!
|
13
14
|
|
14
15
|
## Installation
|
15
16
|
|
@@ -28,16 +29,26 @@ Note that you need a blocking call to the editor (for GUI editors). Sublime and
|
|
28
29
|
To get a list of available options invoke DLE with the `--help` or `-h` option:
|
29
30
|
|
30
31
|
Usage: dle [options] base_directory
|
32
|
+
# Application options
|
31
33
|
-d, --dotfiles Include dotfiles (unix invisible)
|
32
34
|
-r, --skip-review Skip review changes before applying
|
33
35
|
-s, --simulate Don't apply changes, show commands instead
|
34
36
|
-f, --file DLFILE Use input file (be careful)
|
35
37
|
-o, --only pattern files, dirs or regexp (without delimiters)
|
36
38
|
e.g.: dle ~/Movies -o "(mov|mkv|avi)$"
|
39
|
+
-a, --apply NAMED,FILTERS Filter collection with saved filters
|
40
|
+
-q, --query Filter collection with ruby code
|
41
|
+
-e, --edit FILTER Edit/create filter scripts
|
42
|
+
|
43
|
+
# General options
|
37
44
|
-m, --monochrome Don't colorize output
|
38
45
|
-h, --help Shows this help
|
39
46
|
-v, --version Shows version and other info
|
40
47
|
-z Do not check for updates on GitHub (with -v/--version)
|
48
|
+
--console Start console to play around with the collection (requires pry)
|
49
|
+
|
50
|
+
|
51
|
+
|
41
52
|
|
42
53
|
Change into a directory you want to work with and invoke DLE:
|
43
54
|
|
@@ -62,6 +73,23 @@ Your editor will open with a list of your directory structure which you can edit
|
|
62
73
|
* Copy
|
63
74
|
* Delete
|
64
75
|
|
76
|
+
|
77
|
+
### Filters
|
78
|
+
|
79
|
+
You can easily filter your movies with Ruby. It's not hard, just look at these examples.
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
# Filter by name, for regex see http://rubular.com
|
83
|
+
@fs.index.reject! {|inode, node| node.basename =~ /whatever/i }
|
84
|
+
|
85
|
+
# Only big files
|
86
|
+
@fs.index.select! {|inode, node| node.file? && node.size > 1024 * 1024 * 10 }
|
87
|
+
|
88
|
+
# Sort by size
|
89
|
+
@fs.index.replace Hash[@fs.index.sort_by{|inode, node| node.size }.reverse]
|
90
|
+
```
|
91
|
+
|
92
|
+
|
65
93
|
## Caveats
|
66
94
|
|
67
95
|
DLE relies on inode values, do not use with hardlinks! This may lead to unexpected file operations or data loss!
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1
|
1
|
+
1.0.1
|
data/lib/dle.rb
CHANGED
data/lib/dle/application.rb
CHANGED
@@ -8,6 +8,7 @@ module Dle
|
|
8
8
|
class Application
|
9
9
|
attr_reader :opts
|
10
10
|
include Dispatch
|
11
|
+
include Filter
|
11
12
|
include Helpers
|
12
13
|
|
13
14
|
# =========
|
@@ -30,27 +31,47 @@ module Dle
|
|
30
31
|
@editor = which_editor
|
31
32
|
@opts = {
|
32
33
|
dispatch: :index,
|
34
|
+
console: false,
|
33
35
|
dotfiles: false,
|
34
36
|
check_for_updates: true,
|
35
37
|
review: true,
|
38
|
+
select_scripts: false,
|
36
39
|
simulate: false,
|
40
|
+
query: false,
|
37
41
|
}
|
38
42
|
yield(self)
|
39
43
|
end
|
40
44
|
|
45
|
+
def collection_size_changed cold, cnew, reason = nil
|
46
|
+
if cold != cnew
|
47
|
+
log(
|
48
|
+
"We have filtered " << c("#{cnew}", :magenta) <<
|
49
|
+
c(" nodes") << c(" (#{cnew - cold})", :red) <<
|
50
|
+
c(" from originally ") << c("#{cold}", :magenta) <<
|
51
|
+
(reason ? c(" (#{reason})", :blue) : "")
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
41
56
|
def parse_params
|
42
57
|
@optparse = OptionParser.new do |opts|
|
43
58
|
opts.banner = "Usage: dle [options] base_directory"
|
44
59
|
|
60
|
+
opts.separator(c "# Application options", :blue)
|
45
61
|
opts.on("-d", "--dotfiles", "Include dotfiles (unix invisible)") { @opts[:dotfiles] = true }
|
46
62
|
opts.on("-r", "--skip-review", "Skip review changes before applying") { @opts[:review] = false }
|
47
63
|
opts.on("-s", "--simulate", "Don't apply changes, show commands instead") { @opts[:simulate] = true ; @opts[:review] = false }
|
48
64
|
opts.on("-f", "--file DLFILE", "Use input file (be careful)") {|f| @opts[:input_file] = f }
|
49
65
|
opts.on("-o", "--only pattern", c("files", :blue) << c(", ") << c("dirs", :blue) << c(" or regexp (without delimiters)"), " e.g.:" << c(%{ dle ~/Movies -o "(mov|mkv|avi)$"}, :blue)) {|p| @opts[:pattern] = p }
|
66
|
+
opts.on("-a", "--apply NAMED,FILTERS", Array, "Filter collection with saved filters") {|s| @opts[:select_scripts] = s }
|
67
|
+
opts.on("-q", "--query", "Filter collection with ruby code") { @opts[:query] = true }
|
68
|
+
opts.on("-e", "--edit FILTER", "Edit/create filter scripts") {|s| @opts[:dispatch] = :edit_script; @opts[:select_script] = s }
|
69
|
+
opts.separator("\n" << c("# General options", :blue))
|
50
70
|
opts.on("-m", "--monochrome", "Don't colorize output") { logger.colorize = false }
|
51
71
|
opts.on("-h", "--help", "Shows this help") { @opts[:dispatch] = :help }
|
52
72
|
opts.on("-v", "--version", "Shows version and other info") { @opts[:dispatch] = :info }
|
53
73
|
opts.on("-z", "Do not check for updates on GitHub (with -v/--version)") { @opts[:check_for_updates] = false }
|
74
|
+
opts.on("--console", "Start console to play around with the collection (requires pry)") {|f| @opts[:console] = true }
|
54
75
|
end
|
55
76
|
|
56
77
|
begin
|
@@ -14,6 +14,12 @@ module Dle
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
def dispatch_edit_script
|
18
|
+
log("Opening file in editor...")
|
19
|
+
f = record_filter(filter_script(@opts[:select_script]))
|
20
|
+
log("Saved file " << c(f, :magenta))
|
21
|
+
end
|
22
|
+
|
17
23
|
def dispatch_help
|
18
24
|
logger.log_without_timestr do
|
19
25
|
@optparse.to_s.split("\n").each(&method(:log))
|
@@ -96,93 +102,123 @@ module Dle
|
|
96
102
|
abort("Base directory is empty or not readable", 1) if @fs.index.empty?
|
97
103
|
log("indexed #{c "#{human_number @fs.index.count} nodes", :magenta}") if @fs.index.count > 1000
|
98
104
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
105
|
+
if @opts[:console]
|
106
|
+
log "You have access to the collection with " << c("@fs", :magenta)
|
107
|
+
log "Apply existent select script with " << c("apply_filter(@fs, 'filter_name')", :magenta)
|
108
|
+
log "Type " << c("exit", :magenta) << c(" to leave the console.")
|
109
|
+
begin
|
110
|
+
binding.pry(quiet: true)
|
111
|
+
abort c("No changes, nothing to do..."), 0
|
112
|
+
rescue NoMethodError => ex
|
113
|
+
raise ex unless ex.message["undefined method `pry'"]
|
114
|
+
abort c("The pry gem is required to display the console. Please install it: " << c("gem install pry", :blue)), 3
|
115
|
+
end
|
116
|
+
else
|
117
|
+
file = "#{Dir.tmpdir}/#{SecureRandom.urlsafe_base64}"
|
118
|
+
begin
|
119
|
+
# read input file or open editor
|
120
|
+
if @opts[:input_file]
|
121
|
+
ifile = File.expand_path(@opts[:input_file])
|
122
|
+
if FileTest.file?(ifile) && FileTest.readable?(ifile)
|
123
|
+
log "processing file..."
|
124
|
+
@dlfile = DlFile.parse(ifile)
|
125
|
+
else
|
126
|
+
abort "Input file not readable: " << c(ifile, :magenta)
|
127
|
+
end
|
107
128
|
else
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
notifier do
|
114
|
-
sleep 3
|
115
|
-
log "writing result list to file..."
|
116
|
-
end.perform do
|
117
|
-
File.open(file, "w") {|f| f.write @fs.to_dlfile }
|
129
|
+
old_count = @fs.index.count
|
130
|
+
if @opts[:query]
|
131
|
+
apply_filter(@fs, record_filter)
|
132
|
+
collection_size_changed old_count, @fs.index.count, "custom filter"
|
133
|
+
old_count = @fs.index.count
|
118
134
|
end
|
135
|
+
|
136
|
+
# filter
|
137
|
+
(@opts[:select_scripts] || []).each do |filter|
|
138
|
+
apply_filter(@fs, filter_script(filter))
|
139
|
+
collection_size_changed old_count, @fs.index.count, "filter: #{filter}"
|
140
|
+
old_count = @fs.index.count
|
141
|
+
end
|
142
|
+
|
143
|
+
# save file
|
144
|
+
FileUtils.mkdir_p(File.dirname(file)) if !FileTest.exist?(File.dirname(file))
|
145
|
+
if !FileTest.exist?(file) || File.read(file).strip.empty?
|
146
|
+
notifier do
|
147
|
+
sleep 3
|
148
|
+
log "writing result list to file..."
|
149
|
+
end.perform do
|
150
|
+
File.open(file, "w") {|f| f.write @fs.to_dlfile }
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# open editor
|
155
|
+
log "open list for editing..."
|
156
|
+
open_editor(file)
|
157
|
+
log "processing file..."
|
158
|
+
@dlfile = DlFile.parse(file)
|
119
159
|
end
|
120
|
-
log "open list for editing..."
|
121
|
-
open_editor(file)
|
122
|
-
log "processing file..."
|
123
|
-
@dlfile = DlFile.parse(file)
|
124
|
-
end
|
125
160
|
|
126
|
-
|
127
|
-
|
161
|
+
# delta changes
|
162
|
+
@delta = @fs.delta(@dlfile)
|
128
163
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
164
|
+
# no changes
|
165
|
+
if @delta.all?{|_, v| v.empty? }
|
166
|
+
abort c("No changes, nothing to do..."), 0
|
167
|
+
end
|
133
168
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
169
|
+
# review
|
170
|
+
if @opts[:review]
|
171
|
+
@delta.each do |action, snodes|
|
172
|
+
logger.ensure_prefix c("[#{action}]\t", :magenta) do
|
173
|
+
snodes.each do |snode|
|
174
|
+
if [:chown, :chmod].include?(action)
|
175
|
+
log(c("#{snode.node.relative_path} ", :blue) << c(snode.is, :red) << c(" » ") << c(snode.should, :green))
|
176
|
+
elsif [:cp, :mv].include?(action)
|
177
|
+
log(c(snode.is, :red) << c(" » ") << c(snode.should, :green))
|
178
|
+
else
|
179
|
+
log(c(snode.is, :red) << " (#{snode.snode.mode})")
|
180
|
+
end
|
145
181
|
end
|
146
182
|
end
|
147
183
|
end
|
148
|
-
end
|
149
184
|
|
150
|
-
|
151
|
-
|
152
|
-
|
185
|
+
answer = ask("Do you want to apply these changes? [yes/no/edit]")
|
186
|
+
while !["y", "yes", "n", "no", "e", "edit"].include?(answer.downcase)
|
187
|
+
answer = ask("Please be explicit, yes/no/edit:")
|
188
|
+
end
|
189
|
+
raise "retry" if ["e", "edit"].include?(answer.downcase)
|
190
|
+
abort("Aborted, nothing changed", 0) if !["y", "yes"].include?(answer.downcase)
|
153
191
|
end
|
154
|
-
|
155
|
-
|
192
|
+
rescue
|
193
|
+
$!.message == "retry" ? retry : raise
|
156
194
|
end
|
157
|
-
rescue
|
158
|
-
$!.message == "retry" ? retry : raise
|
159
|
-
end
|
160
195
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
196
|
+
# apply changes
|
197
|
+
log "#{@opts[:simulate] ? "Simulating" : "Applying"} changes..."
|
198
|
+
@fs.opts[:verbose] = false
|
199
|
+
total_actions = @delta.map{|_, nodes| nodes.count }.inject(&:+)
|
200
|
+
actions_performed = 0
|
201
|
+
begin
|
202
|
+
notifier do
|
203
|
+
loop do
|
204
|
+
if BASH_ENABLED
|
205
|
+
logger.raw("\033]0;#{@opts[:simulate] ? "Simulated" : "Peformed"} #{human_number actions_performed}/#{human_number total_actions} changes\007", :print)
|
206
|
+
end
|
207
|
+
sleep 1
|
171
208
|
end
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
Filesystem::Destructive.new(self, action, @fs, snode).perform
|
209
|
+
end.perform do
|
210
|
+
@delta.each do |action, snodes|
|
211
|
+
logger.ensure_prefix c("[apply-#{action}]\t", :magenta) do
|
212
|
+
snodes.each do |snode|
|
213
|
+
actions_performed += 1
|
214
|
+
Filesystem::Destructive.new(self, action, @fs, snode).perform
|
215
|
+
end
|
180
216
|
end
|
181
217
|
end
|
182
218
|
end
|
219
|
+
ensure
|
220
|
+
log "#{@opts[:simulate] ? "Simulated" : "Peformed"} #{human_number actions_performed} changes..."
|
183
221
|
end
|
184
|
-
ensure
|
185
|
-
log "#{@opts[:simulate] ? "Simulated" : "Peformed"} #{human_number actions_performed} changes..."
|
186
222
|
end
|
187
223
|
end
|
188
224
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Dle
|
2
|
+
class Application
|
3
|
+
module Filter
|
4
|
+
def filter_script name
|
5
|
+
File.expand_path("~/.dle/#{name}.rb")
|
6
|
+
end
|
7
|
+
|
8
|
+
def apply_filter collection, file
|
9
|
+
app = @app
|
10
|
+
eval File.read(file), binding, file
|
11
|
+
collection
|
12
|
+
end
|
13
|
+
|
14
|
+
def permute_script collection, file = nil
|
15
|
+
file ||= "#{Dir.tmpdir}/#{SecureRandom.urlsafe_base64}"
|
16
|
+
FileUtils.mkdir(File.dirname(file)) if !File.exist?(File.dirname(file))
|
17
|
+
if !File.exist?(file) || File.read(file).strip.empty?
|
18
|
+
File.open(file, "w") {|f| f.puts("# Permute your collection, same as with the selector script filters.") }
|
19
|
+
end
|
20
|
+
system "#{cfg :application, :editor} #{file}"
|
21
|
+
eval File.read(file), binding, file
|
22
|
+
collection
|
23
|
+
end
|
24
|
+
|
25
|
+
def record_filter file = nil
|
26
|
+
file ||= "#{Dir.tmpdir}/#{SecureRandom.urlsafe_base64}.rb"
|
27
|
+
FileUtils.mkdir(File.dirname(file)) if !File.exist?(File.dirname(file))
|
28
|
+
if !File.exist?(file) || File.read(file).strip.empty?
|
29
|
+
FileUtils.cp("#{ROOT}/lib/dle/application/filter.tpl", file)
|
30
|
+
end
|
31
|
+
open_editor(file)
|
32
|
+
file
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# TIP: Using vim and want to get rid of this example shit?
|
2
|
+
# In nav-mode type: 100dd
|
3
|
+
|
4
|
+
# Hey there,
|
5
|
+
# to filter your records you will use Ruby
|
6
|
+
# but don't be afraid, it's fairly simple.
|
7
|
+
# Just look at the examples and referenced links.
|
8
|
+
|
9
|
+
# Use ruby methods to narrow down you result set.
|
10
|
+
# * http://www.ruby-doc.org/core-2.1.1/File.html
|
11
|
+
# * http://www.ruby-doc.org/core-2.1.1/Enumerable.html
|
12
|
+
|
13
|
+
# ====================================================
|
14
|
+
# = Doc (remove, reuse or comment out the examples!) =
|
15
|
+
# ====================================================
|
16
|
+
|
17
|
+
puts "Filtering in #{@fs.base_dir}"
|
18
|
+
@fs.index.select! do |inode, node|
|
19
|
+
# node has the following methods
|
20
|
+
# * dir => source movie directory (e.g. C:/Movies)
|
21
|
+
# * relative_path => relative path to @fs.base_dir
|
22
|
+
# * mode => file mode
|
23
|
+
# * owner => name of the file owner
|
24
|
+
# * group => name of the file group
|
25
|
+
# * owngrp => owner and group combined by a colon
|
26
|
+
# * basename => alias for File#basename
|
27
|
+
# * dirname => alias for File#dirname
|
28
|
+
# * extname => alias for File#extname
|
29
|
+
# * stat => alias for File#stat
|
30
|
+
# * size => alias for File#size
|
31
|
+
# * directory? => alias for FileTest#directory?
|
32
|
+
# * file? => alias for FileTest#file?
|
33
|
+
# * symlink? => alias for FileTest#symlink?
|
34
|
+
|
35
|
+
# The index is NOT NESTED! If you remove a directory node, all sub nodes
|
36
|
+
# will still be in the index!
|
37
|
+
|
38
|
+
# Set break point to interactively call methods from here.
|
39
|
+
# See http://pryrepl.org ory type "help" when you are in the REPL.
|
40
|
+
# Use exit or exit! to break out of REPL.
|
41
|
+
# binding.pry
|
42
|
+
|
43
|
+
# --------------------------------------------------------------
|
44
|
+
|
45
|
+
node.directory? && node.basename =~ /^[a-z0-9]+$/
|
46
|
+
end
|
47
|
+
|
48
|
+
# Filter by name, for regex see http://rubular.com
|
49
|
+
@fs.index.reject! {|inode, node| node.basename =~ /whatever/i }
|
50
|
+
|
51
|
+
# Only big files
|
52
|
+
@fs.index.select! {|inode, node| node.file? && node.size > 1024 * 1024 * 10 }
|
53
|
+
|
54
|
+
# Sort by size
|
55
|
+
@fs.index.replace Hash[@fs.index.sort_by{|inode, node| node.size }.reverse]
|
data/lib/dle/helpers.rb
CHANGED
@@ -35,7 +35,7 @@ module Dle
|
|
35
35
|
[].tap do |r|
|
36
36
|
col_sizes = table.map{|col| col.map(&:to_s).map(&:length).max }
|
37
37
|
headers.map(&:length).each_with_index do |length, header|
|
38
|
-
col_sizes[header] = [col_sizes[header], length].max
|
38
|
+
col_sizes[header] = [col_sizes[header] || 0, length || 0].max
|
39
39
|
end
|
40
40
|
|
41
41
|
# header
|
data/lib/dle/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sven Pachnit
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-04-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|
@@ -77,6 +77,8 @@ files:
|
|
77
77
|
- lib/dle.rb
|
78
78
|
- lib/dle/application.rb
|
79
79
|
- lib/dle/application/dispatch.rb
|
80
|
+
- lib/dle/application/filter.rb
|
81
|
+
- lib/dle/application/filter.tpl
|
80
82
|
- lib/dle/dl_file.rb
|
81
83
|
- lib/dle/filesystem.rb
|
82
84
|
- lib/dle/filesystem/destructive.rb
|
@@ -104,9 +106,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
106
|
version: '0'
|
105
107
|
requirements: []
|
106
108
|
rubyforge_project:
|
107
|
-
rubygems_version: 2.
|
109
|
+
rubygems_version: 2.0.14
|
108
110
|
signing_key:
|
109
111
|
specification_version: 4
|
110
112
|
summary: Directory List Edit – Edit file structures in your favorite editor!
|
111
113
|
test_files: []
|
112
|
-
has_rdoc:
|