mongo3 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -29,11 +29,16 @@
29
29
 
30
30
  == ROAD MAP:
31
31
 
32
- * Provide support for db authentication
33
- * Ability to drilldown in collections and query for content
32
+ * Provide support for db authentication [done]
33
+ * Ability to drilldown in collections and query for content [done]
34
34
  * CRUD on dbs, collections, indexes, users
35
35
  * Support for managing shards and replication
36
36
 
37
+ == DEPENDENCIES
38
+
39
+ * mongo + mongo_ext
40
+ * agnostic-will_paginate
41
+
37
42
  == INSTALL:
38
43
 
39
44
  sudo gem install mongo3
@@ -53,8 +58,10 @@
53
58
  port: 27017
54
59
 
55
60
  beta:
56
- host: beta_host_name
57
- port: 27017
61
+ host: beta_host_name
62
+ port: 27017
63
+ user: bobo
64
+ password: secret
58
65
 
59
66
  === Launch It!
60
67
 
data/Rakefile CHANGED
@@ -29,3 +29,9 @@ PROJ.rcov.opts = ["--sort", "coverage", "-T"]
29
29
  PROJ.ignore_file = "*.log"
30
30
  PROJ.spec.opts << '--color'
31
31
  PROJ.rdoc.include = %w[.rb]
32
+
33
+ # Dependencies
34
+ depend_on "mongo" , ">= 0.18.1"
35
+ depend_on "mongo_ext" , ">= 0.18.1"
36
+ depend_on "agnostic-will_paginate", ">= 3.0.0"
37
+
data/config/mongo3.yml CHANGED
@@ -1,12 +1,12 @@
1
1
  development:
2
- :host: localhost
3
- :port: 27017
2
+ host: localhost
3
+ port: 27017
4
4
 
5
5
  beta:
6
- :host: dev8
7
- :port: 27018
8
- :user: "blee"
9
- :password: "fred"
6
+ host: dev8
7
+ port: 27018
8
+ user: "blee"
9
+ password: "fred"
10
10
 
11
11
  # production:
12
12
  # :host: dev8
@@ -1,7 +1,58 @@
1
+ require 'json'
2
+
1
3
  module Collections
2
4
 
3
- get "/cltns" do
4
- puts "Cltns"
5
+ # ---------------------------------------------------------------------------
6
+ # Paginate on a collection
7
+ get "/cltn/:page" do
8
+ @back_url = "/explore/back"
9
+ @page = params[:page].to_i || 1
10
+ @title = title_for( session[:path_names] )
11
+
12
+ load_cltn( params[:page].to_i )
13
+ erb :cltn_list
14
+ end
15
+
16
+ # ---------------------------------------------------------------------------
17
+ post "/cltn_refresh/:page" do
18
+ selected_cols = params[:cols].keys.sort
19
+ session[:selected_cols] = selected_cols
20
+
21
+ load_cltn( params[:page].to_i )
22
+
23
+ erb :cltn_update_js, :layout => false
5
24
  end
6
25
 
26
+ # ---------------------------------------------------------------------------
27
+ # BOZO !! Validation....
28
+ post "/cltn_search" do
29
+ json = params[:search].gsub( /'/, "\"" )
30
+ if json.empty?
31
+ @query = {}
32
+ @sort = {}
33
+ else
34
+ tokens = json.split( "|" )
35
+ @query = JSON.parse( tokens.shift )
36
+ @sort = tokens.empty? ? [] : JSON.parse( tokens.first )
37
+ end
38
+ session[:query_params] = [@query, @sort]
39
+
40
+ load_cltn
41
+ erb :cltn_update_js, :layout => false
42
+ end
43
+
44
+ # ===========================================================================
45
+ helpers do
46
+ def load_cltn( page=1 )
47
+ query_params = session[:query_params] || [{},[]]
48
+ @query = [query_params.first.to_json, query_params.last.to_json].join(",")
49
+ @page = page
50
+ path_names = session[:path_names]
51
+ path_ids = session[:path_ids]
52
+
53
+ @cltn = options.connection.paginate_cltn( path_names, query_params, @page, 15 )
54
+ @cols = @cltn.first.keys.sort
55
+ @selected_cols = session[:selected_cols] || @cols[0...5]
56
+ end
57
+ end
7
58
  end
@@ -1,7 +1,16 @@
1
1
  module Databases
2
2
 
3
- get "/dbs" do
4
- puts "Dbs"
5
- end
3
+ # ---------------------------------------------------------------------------
4
+ get "/db/:page" do
5
+ page = params[:page].to_i || 1
6
+ path_ids = session[:path_ids]
7
+ path_names = session[:path_names]
8
+
9
+ @cltns = options.connection.paginate_db( path_names, page, 10 )
10
+ @title = title_for( path_names )
11
+ @back_url = "/explore/back"
12
+
13
+ erb :db_list
14
+ end
6
15
 
7
16
  end
@@ -4,27 +4,56 @@ module Explore
4
4
  get '/explore' do
5
5
  reset_crumbs!
6
6
  @root = options.connection.build_tree
7
+ Mongo3::Node.dump( @root )
7
8
  erb :explore
8
9
  end
9
10
 
10
11
  # -----------------------------------------------------------------------------
11
- get '/explore/show/:path/:crumbs' do
12
- path = params[:path]
13
- crumbs = params[:crumbs]
12
+ get '/explore/back' do
13
+ session[:selected_cols] = nil
14
+ session[:query_params] = nil
15
+
16
+ back_paths!
17
+ path_ids = session[:path_ids]
18
+ path_names = session[:path_names]
14
19
 
15
- @info = options.connection.show( path, crumbs )
20
+ reset_crumbs!
21
+ crumbs_from_path( path_ids, path_names )
22
+
23
+ @root = options.connection.build_partial_tree( path_ids, path_names )
24
+ # Mongo3::Node.dump( @root )
25
+
26
+ # need to adjust crumbs in case something got blown...
27
+ @center = path_ids.split( "|" ).last
28
+
29
+ erb :explore
30
+ end
31
+
32
+ # -----------------------------------------------------------------------------
33
+ get '/explore/show/:path_ids/:path_names' do
34
+ path_ids = params[:path_ids]
35
+ path_names = params[:path_names]
36
+
37
+ @info = options.connection.show( path_names )
38
+
39
+ session[:path_ids] = path_ids
40
+ session[:path_names] = path_names
16
41
 
17
42
  partial :info
18
43
  end
19
44
 
20
45
  # -----------------------------------------------------------------------------
21
- get '/explore/more_data/:path/:crumbs/*' do
22
- path = params[:path]
23
- crumbs = params[:crumbs]
46
+ get '/explore/more_data/:path_ids/:path_names/*' do
47
+ path_ids = params[:path_ids]
48
+ path_names = params[:path_names]
49
+
50
+ session[:path_ids] = path_ids
51
+ session[:path_names] = path_names
24
52
 
25
- crumbs_from_path( path, crumbs )
53
+ crumbs_from_path( path_ids, path_names )
26
54
 
27
- @sub_tree = options.connection.build_sub_tree( path, crumbs )
55
+ @sub_tree = options.connection.build_sub_tree( path_ids, path_names )
56
+ # Mongo3::Node.dump_adj( @sub_tree )
28
57
  @node_id = @sub_tree.first[:id]
29
58
 
30
59
  erb :more_data_js, :layout => false
@@ -38,8 +67,10 @@ module Explore
38
67
 
39
68
  # -----------------------------------------------------------------------------
40
69
  get '/explore/center/:node_id' do
41
- @node_id = params[:node_id]
70
+ @node_id = params[:node_id]
71
+
42
72
  pop_crumb!( @node_id )
73
+
43
74
  erb :center_js, :layout => false
44
75
  end
45
76
 
@@ -0,0 +1,19 @@
1
+ require 'will_paginate/view_helpers/base'
2
+ require 'will_paginate/view_helpers/link_renderer'
3
+
4
+ module WillPaginate::ViewHelpers
5
+ class LinkRenderer
6
+ def url( page )
7
+ "#{@options[:params][:url]}/#{page}"
8
+ end
9
+ end
10
+ end
11
+
12
+
13
+ module CollectionHelper
14
+
15
+ helpers do
16
+ include WillPaginate::ViewHelpers::Base
17
+ end
18
+
19
+ end
@@ -1,23 +1,23 @@
1
1
  module CrumbHelper
2
2
 
3
3
  helpers do
4
- def crumbs_from_path( path, crumbs )
5
- crumb_tokens = crumbs.split( "|" )
6
- path_tokens = path.split( "|" )
4
+ def crumbs_from_path( path_ids, path_names )
5
+ path_id_tokens = path_ids.split( "|" )
6
+ path_name_tokens = path_names.split( "|" )
7
7
 
8
8
  @crumbs = []
9
9
  count = 0
10
- crumb_tokens.each do |crumb|
11
- @crumbs << [crumb, "/explore/center/#{path_tokens[count]}"]
10
+ path_name_tokens.each do |crumb|
11
+ @crumbs << [crumb, "/explore/center/#{path_id_tokens[count]}"]
12
12
  count += 1
13
13
  end
14
14
  session[:crumbs] = @crumbs
15
15
  end
16
16
 
17
17
  def pop_crumb!( node_id )
18
+ path = "/explore/center/#{node_id}"
18
19
  level = 0
19
20
  range = nil
20
- path = "/explore/center/#{node_id}"
21
21
  @crumbs.each do |pair|
22
22
  if pair.last == path
23
23
  range = level
@@ -36,7 +36,7 @@ module CrumbHelper
36
36
 
37
37
  def add_crumb( title, url )
38
38
  titles = @crumbs.map{ |p| p.first }
39
- unless titles.include?( title )
39
+ unless titles.include?( title )
40
40
  @crumbs.pop if @crumbs.size == 3
41
41
  @crumbs << [title, url]
42
42
  session[:crumbs] = @crumbs
@@ -13,6 +13,29 @@ module MainHelper
13
13
 
14
14
  helpers do
15
15
 
16
+ def back_paths!
17
+ path_ids = session[:path_ids]
18
+ path_names = session[:path_names]
19
+ new_path_ids = path_ids.split( "|" )
20
+ new_path_ids.pop
21
+ session[:path_ids] = new_path_ids.join( "|" )
22
+
23
+ new_path_names = path_names.split( "|" )
24
+ new_path_names.pop
25
+ session[:path_names] = new_path_names.join( "|" )
26
+ end
27
+
28
+ def title_for( path_names )
29
+ tokens = path_names.split( "|" )
30
+ buff = case tokens.length
31
+ when 2 : "Environment"
32
+ when 3 : "Database"
33
+ else "Collection"
34
+ end
35
+
36
+ buff += " <em>#{tokens.last}</em>"
37
+ end
38
+
16
39
  def display_info( info )
17
40
  return info if info.is_a?( String )
18
41
  if info.is_a?( Hash )
data/lib/main.rb CHANGED
@@ -3,6 +3,8 @@ require 'sinatra'
3
3
  require 'forwardable'
4
4
  require File.join( File.dirname(__FILE__), 'mongo3.rb' )
5
5
  require 'mongo'
6
+ gem 'agnostic-will_paginate'
7
+ require 'will_paginate'
6
8
 
7
9
  set :public, File.join( File.dirname(__FILE__), %w[public] )
8
10
  set :views , File.join( File.dirname(__FILE__), %w[views] )
@@ -21,9 +23,9 @@ end
21
23
  # Before filters
22
24
  before do
23
25
  unless request.path =~ /\.[css gif png js]/
24
- @crumbs = session[:crumbs]
26
+ @crumbs = session[:crumbs]
25
27
  unless @crumbs
26
- @crumbs = [ ['HOME', '/center'] ]
28
+ @crumbs = [ ['home', '/explore/center/home'] ]
27
29
  session[:crumbs] = @crumbs
28
30
  end
29
31
  end
@@ -4,25 +4,14 @@ module Mongo3
4
4
  def initialize( config_file )
5
5
  @config_file = config_file
6
6
  end
7
-
8
- # Connects to mongo given an environment
9
- # BOZO !! Auth...
10
- # TODO - Need to close out connection via block
11
- def connect_for( env, &block )
12
- info = landscape[env]
13
- puts ">>> Connecting for #{env} -- #{info[:host]}-#{info[:port]}"
14
- con = Mongo::Connection.new( info[:host], info[:port] )
15
- yield con
16
- con.close()
17
- end
18
-
19
- def show( path, crumbs )
20
- path_tokens = path.split( "|" )
21
- crumb_tokens = crumbs.split( "|" )
22
- info = OrderedHash.new
23
- env = path_tokens[1]
7
+
8
+ def show( path_names )
9
+ path_name_tokens = path_names.split( "|" )
10
+ info = OrderedHash.new
11
+ env = path_name_tokens[1]
24
12
 
25
- if path_tokens.size == 2
13
+ info[:title] = path_name_tokens.last
14
+ if path_name_tokens.size == 2
26
15
  connect_for( env ) do |con|
27
16
  info[:name] = env
28
17
  info[:host] = con.host
@@ -31,62 +20,89 @@ module Mongo3
31
20
  con.database_info.sort { |a,b| b[1] <=> a[1] }.each { |e| info[:databases][e[0]] = to_mb( e[1] ) }
32
21
  info[:server] = con.server_info
33
22
  end
34
- elsif path_tokens.size == 3
35
- db_name = crumb_tokens.pop
36
- connect_for( env ) do |con|
23
+ elsif path_name_tokens.size == 3
24
+ db_name = path_name_tokens.pop
25
+ connect_for( env ) do |con|
37
26
  db = con.db( db_name )
38
- info[:size] = to_mb( con.database_info[db_name] )
39
- info[:node] = db.nodes
27
+ info[:edit] = "/db/1"
28
+ info[:size] = to_mb( con.database_info[db_name] )
29
+ info[:node] = db.nodes
40
30
  info[:collections] = db.collection_names.size
41
31
  info[:error] = db.error
42
32
  info[:last_status] = db.last_status
43
33
  end
44
- elsif path_tokens.size == 4
45
- cltn_name = crumb_tokens.pop
46
- db_name = crumb_tokens.pop
34
+ elsif path_name_tokens.size == 4
35
+ cltn_name = path_name_tokens.pop
36
+ db_name = path_name_tokens.pop
47
37
  connect_for( env ) do |con|
48
- db = con.db( db_name )
49
- cltn = db[cltn_name]
50
- info[:size] = cltn.count
38
+ db = con.db( db_name )
39
+ cltn = db[cltn_name]
40
+ indexes = db.index_information( cltn_name )
51
41
 
52
- indexes = db.index_information( cltn_name )
53
- info[:indexes] = format_indexes( db.index_information( cltn_name ) ) if indexes and !indexes.empty?
42
+ info[:edit] = "/cltn/1"
43
+ info[:size] = cltn.count
44
+ info[:indexes] = format_indexes( indexes ) if indexes and !indexes.empty?
54
45
  end
55
- end
56
-
46
+ end
57
47
  info
58
48
  end
49
+
50
+ def paginate_db( path_names, page=1, per_page=10 )
51
+ path_name_tokens = path_names.split( "|" )
52
+ env = path_name_tokens[1]
53
+ list = nil
54
+ connect_for( env ) do |con|
55
+ db_name = path_name_tokens.pop
56
+ db = con.db( db_name )
57
+ cltn = db.collection_names.sort
59
58
 
60
- def format_indexes( indexes )
61
- formatted = {}
62
- indexes.each_pair do |key, values|
63
- buff = []
64
- values.each do |pair|
65
- buff << "#{pair.first} [#{pair.last}]"
66
- end
67
- formatted[key] = buff
68
- end
69
- formatted
59
+ list = WillPaginate::Collection.create( page, per_page, cltn.size ) do |pager|
60
+ offset = (page-1)*per_page
61
+ names = cltn[offset..(offset+per_page)]
62
+ cltns = []
63
+ names.each do |name|
64
+ list = db[name]
65
+ row = OrderedHash.new
66
+ row[:name] = name
67
+ row[:count] = list.count
68
+ cltns << row
69
+ end
70
+ pager.replace( cltns )
71
+ end
72
+ end
73
+ list
70
74
  end
71
-
75
+
76
+ def paginate_cltn( path_names, query_params=[{},[]], page=1, per_page=10 )
77
+ path_name_tokens = path_names.split( "|" )
78
+ env = path_name_tokens[1]
79
+ list = nil
80
+ connect_for( env ) do |con|
81
+ cltn_name = path_name_tokens.pop
82
+ db_name = path_name_tokens.pop
83
+ db = con.db( db_name )
84
+ cltn = db[cltn_name]
85
+
86
+ list = WillPaginate::Collection.create( page, per_page, cltn.count ) do |pager|
87
+ offset = (page-1)*per_page
88
+ sort = query_params.last.empty? ? [ ['_id', Mongo::DESCENDING] ] : query_params.last
89
+ pager.replace( cltn.find( query_params.first,
90
+ :sort => sort,
91
+ :skip => offset,
92
+ :limit => per_page ).to_a)
93
+ end
94
+ end
95
+ list
96
+ end
97
+
72
98
  # Fetch the environment landscape from the config file
73
99
  def landscape
74
100
  config
75
101
  end
76
102
 
77
- # db request occurs within dist 2
78
- def db_request?( path )
79
- path.size == 2
80
- end
81
-
82
- # cltn request occurs within dist 3
83
- def cltn_request?( path )
84
- path.size == 3
85
- end
86
-
87
103
  # Build environment tree
88
104
  def build_tree
89
- root = Node.new( "home", "home", :path => 'home', :crumbs => 'home' )
105
+ root = Node.make_node( "home" )
90
106
 
91
107
  # iterate thru envs
92
108
  id = 1
@@ -97,39 +113,77 @@ module Mongo3
97
113
  end
98
114
  root
99
115
  end
116
+
117
+ # Build environment tree
118
+ def build_partial_tree( path_ids, path_names )
119
+ path_id_tokens = path_ids.split( "|" )
120
+ path_name_tokens = path_names.split( "|" )
121
+ bm_env = path_name_tokens[1]
122
+ bm_cltn = path_name_tokens.pop if path_name_tokens.size == 4
123
+ bm_db = path_name_tokens.pop if path_name_tokens.size == 3
124
+
125
+ root = Node.make_node( "home" )
126
+
127
+ # iterate thru envs
128
+ config.each_pair do |env, info|
129
+ node = Node.new( env, env, :dyna => true )
130
+ root << node
131
+ if node.name == bm_env
132
+ connect_for( env ) do |con|
133
+ count = 0
134
+ data = { :dyna => true }
135
+ con.database_names.each do |db_name|
136
+ db = con.db( db_name, :strict => true )
137
+ cltns = db.collection_names.size
138
+ db_node = Node.new( "#{env}_#{count}", "#{db_name}(#{cltns})", data.clone )
139
+ node << db_node
140
+ count += 1
141
+ if bm_db and db_node.name =~ /^#{bm_db}/
142
+ cltn_count = 0
143
+ data = { :dyna => false }
144
+ db.collection_names.each do |cltn_name|
145
+ size = db[cltn_name].count
146
+ cltn_node = Node.new( "#{db_name}_#{cltn_count}", "#{cltn_name}(#{size})", data.clone )
147
+ db_node << cltn_node
148
+ cltn_count += 1
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ root
156
+ end
100
157
 
101
158
  # Build an appropriate subtree based on requested item
102
- def build_sub_tree( path, crumbs )
103
- path_tokens = path.split( "|" )
104
- crumb_tokens = crumbs.split( "|" )
105
- parent_id = path_tokens.last
106
- db_name = crumb_tokens.last
107
-
108
- if db_request?( path_tokens )
109
- sub_tree = build_db_tree( parent_id, db_name )
159
+ def build_sub_tree( path_ids, path_names )
160
+ path_id_tokens = path_ids.split( "|" )
161
+ path_name_tokens = path_names.split( "|" )
162
+ env = path_name_tokens[1]
163
+ parent_id = path_id_tokens.last
164
+
165
+ if db_request?( path_name_tokens )
166
+ sub_tree = build_db_tree( parent_id, env )
110
167
  else
111
- env = crumb_tokens[1]
168
+ db_name = path_name_tokens.last
112
169
  sub_tree = build_cltn_tree( parent_id, env, db_name )
113
170
  end
114
171
  sub_tree.to_adjacencies
115
172
  end
116
-
117
-
173
+
118
174
  # Connects to host and spews out all available dbs
119
175
  # BOZO !! Need to deal with Auth?
120
- def build_db_tree( parent_id, env )
176
+ def build_db_tree( parent_id, env )
121
177
  sub_root = nil
122
178
  connect_for( env ) do |con|
123
- root = Node.new( "home", "home" )
179
+ root = Node.make_node( "home" )
124
180
  sub_root = Node.new( parent_id, env )
125
181
 
126
182
  root << sub_root
127
183
 
128
184
  count = 0
129
185
  data = { :dyna => true }
130
- # excludes = %w[admin local]
131
186
  con.database_names.each do |db_name|
132
- # next if excludes.include?( db_name )
133
187
  db = con.db( db_name, :strict => true )
134
188
  cltns = db.collection_names.size
135
189
  node = Node.new( "#{env}_#{count}", "#{db_name}(#{cltns})", data.clone )
@@ -145,17 +199,15 @@ module Mongo3
145
199
  sub_root = nil
146
200
  connect_for( env ) do |con|
147
201
  db = con.db( db_name )
148
- root = Node.new( "home", "home" )
149
- env_node = Node.new( env, env )
202
+ root = Node.make_node( "home" )
203
+ env_node = Node.make_node( env )
150
204
  sub_root = Node.new( parent_id, db_name )
151
205
  root << env_node
152
206
  env_node << sub_root
153
207
 
154
208
  count = 0
155
- # excludes = %w[system.indexes]
156
209
  data = { :dyna => false }
157
210
  db.collection_names.each do |cltn_name|
158
- # next if excludes.include?( cltn_name )
159
211
  size = db[cltn_name].count
160
212
  node = Node.new( "#{db_name}_#{count}", "#{cltn_name}(#{size})", data.clone )
161
213
  sub_root << node
@@ -164,9 +216,46 @@ module Mongo3
164
216
  end
165
217
  sub_root
166
218
  end
167
-
219
+
168
220
  # =========================================================================
169
221
  private
222
+
223
+ # Connects to mongo given an environment
224
+ # BOZO !! Auth...
225
+ def connect_for( env, &block )
226
+ info = landscape[env]
227
+ puts ">>> Connecting for #{env} -- #{info['host']}-#{info['port']}"
228
+ con = Mongo::Connection.new( info['host'], info['port'] )
229
+
230
+ if info['user'] and info['password']
231
+ con.db( 'admin' ).authenticate( info['user'], info['password'] )
232
+ end
233
+ yield con
234
+ con.close()
235
+ end
236
+
237
+ # db request occurs within dist 2
238
+ def db_request?( path )
239
+ path.size == 2
240
+ end
241
+
242
+ # cltn request occurs within dist 3
243
+ def cltn_request?( path )
244
+ path.size == 3
245
+ end
246
+
247
+ # Break down indexes in index + asc/desc
248
+ def format_indexes( indexes )
249
+ formatted = {}
250
+ indexes.each_pair do |key, values|
251
+ buff = []
252
+ values.each do |pair|
253
+ buff << "#{pair.first} [#{pair.last}]"
254
+ end
255
+ formatted[key] = buff
256
+ end
257
+ formatted
258
+ end
170
259
 
171
260
  # Convert size to mb
172
261
  def to_mb( val )
data/lib/mongo3/node.rb CHANGED
@@ -12,6 +12,10 @@ module Mongo3
12
12
  @parent = nil
13
13
  end
14
14
 
15
+ def self.make_node( name )
16
+ Node.new( name, name, :path_ids => name, :path_names => name )
17
+ end
18
+
15
19
  # Add a child node
16
20
  def <<( new_one )
17
21
  new_one.parent = self
@@ -20,8 +24,8 @@ module Mongo3
20
24
  end
21
25
 
22
26
  def update_paths( node )
23
- node.data[:crumbs] = node.path( :name )
24
- node.data[:path] = node.path
27
+ node.data[:path_names] = node.path( :name )
28
+ node.data[:path_ids] = node.path( :oid )
25
29
  node.children.each do |child|
26
30
  child.update_paths( child )
27
31
  end
data/lib/mongo3.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  module Mongo3
3
3
 
4
4
  # :stopdoc:
5
- VERSION = '0.0.1'
5
+ VERSION = '0.0.2'
6
6
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
7
7
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
8
8
  # :startdoc: