treequel-shell 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,397 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'yaml'
4
+ require 'abbrev'
5
+ require 'trollop'
6
+ require 'highline'
7
+ require 'shellwords'
8
+ require 'loggability'
9
+
10
+ # Work around MacOS X's vendored 'sysexits' that does the same thing,
11
+ # but with a different API
12
+ gem 'sysexits'
13
+ require 'sysexits'
14
+
15
+ require 'treequel'
16
+ require 'treequel/mixins'
17
+ require 'treequel/constants'
18
+
19
+
20
+ # A tool for displaying information about a directory's records and schema artifacts.
21
+ class Treequel::What
22
+ extend Sysexits,
23
+ Loggability
24
+ include Sysexits,
25
+ Treequel::ANSIColorUtilities,
26
+ Treequel::Constants::Patterns,
27
+ Treequel::HashUtilities
28
+
29
+ # Loggability API -- log to Treequel's logger
30
+ log_to :treequel
31
+
32
+
33
+ # Highline color scheme
34
+ COLOR_SCHEME = HighLine::ColorScheme.new do |scheme|
35
+ scheme[:header] = [ :bold, :yellow ]
36
+ scheme[:subheader] = [ :bold, :white ]
37
+ scheme[:key] = [ :white ]
38
+ scheme[:value] = [ :bold, :white ]
39
+ scheme[:error] = [ :red ]
40
+ scheme[:warning] = [ :yellow ]
41
+ scheme[:message] = [ :reset ]
42
+ end
43
+
44
+
45
+ ### Run the utility with the given +args+.
46
+ def self::run( args )
47
+ HighLine.color_scheme = COLOR_SCHEME
48
+
49
+ oparser = self.make_option_parser
50
+ opts = Trollop.with_standard_exception_handling( oparser ) do
51
+ oparser.parse( args )
52
+ end
53
+
54
+ pattern = oparser.leftovers.join( ' ' ) if oparser.leftovers
55
+
56
+ self.new( opts ).run( pattern )
57
+ exit :ok
58
+
59
+ rescue => err
60
+ self.log.fatal "Oops: %s: %s" % [ err.class.name, err.message ]
61
+ self.log.debug { ' ' + err.backtrace.join("\n ") }
62
+
63
+ exit :software_error
64
+ end
65
+
66
+
67
+ ### Create and configure a command-line option parser (a Trollop::Parser) for the command.
68
+ def self::make_option_parser
69
+ progname = File.basename( $0 )
70
+ default_directory = Treequel.directory_from_config
71
+ loglevels = Loggability::LOG_LEVELS.keys.join( ', ' )
72
+
73
+ return Trollop::Parser.new do
74
+ banner "Usage: #{progname} [OPTIONS] [PATTERN]"
75
+
76
+ text ''
77
+ text %{Search for an object in an LDAP directory that matches PATTERN and } +
78
+ %{display some information about it.}
79
+ text ''
80
+ text %{The PATTERN can be the DN (or RDN relative to the base) of an entry, } +
81
+ %{a search filter, or the name of an artifact in the directory's schema, } +
82
+ %{such as an objectClass, matching rule, syntax, etc.}
83
+ text ''
84
+ text %{If no PATTERN is specified, general information about the directory is } +
85
+ %{output instead.}
86
+ text ''
87
+
88
+ text 'Connection Options:'
89
+ opt :ldapurl, "Specify the directory to connect to.",
90
+ :default => default_directory.uri.to_s
91
+ text ''
92
+
93
+ text 'Display Options:'
94
+ opt :attrtypes, "Show attribute types for objects that have them."
95
+ opt :objectclasses, "Show objectclasses for objects that have them."
96
+ opt :syntaxes, "Show syntaxes for objects that have them."
97
+ opt :matching_rules, "Show matching rules for objects that have them."
98
+ opt :matching_rule_uses, "Show matching rule uses for objects that have them."
99
+ opt :all, "Show any of the above that are applicable."
100
+ text ''
101
+
102
+ text 'Other Options:'
103
+ opt :debug, "Turn debugging on. Also sets the --loglevel to 'debug'."
104
+ opt :loglevel, "Set the logging level. Must be one of: #{loglevels}",
105
+ :default => Loggability[ Treequel ].level.to_s
106
+ opt :binddn, "The DN of the user to bind as. Defaults to anonymous binding.",
107
+ :type => :string
108
+ end
109
+ end
110
+
111
+
112
+ #################################################################
113
+ ### I N S T A N C E M E T H O D S
114
+ #################################################################
115
+
116
+ ### Create a new instance of the command and set it up with the given
117
+ ### +options+.
118
+ def initialize( options )
119
+ Loggability.format_with( :color ) if $stderr.tty?
120
+
121
+ if options.debug
122
+ $DEBUG = true
123
+ $VERBOSE = true
124
+ Loggability.level = :debug
125
+ elsif options.loglevel
126
+ Loggability.level = options.loglevel
127
+ end
128
+
129
+ @options = options
130
+ if @options.all?
131
+ @options[:attrtypes] =
132
+ @options[:objectclasses] =
133
+ @options[:syntaxes] =
134
+ @options[:matching_rules] =
135
+ @options[:matching_rule_uses] =
136
+ true
137
+ end
138
+
139
+ @directory = Treequel.directory( options.ldapurl )
140
+ @prompt = HighLine.new
141
+
142
+ @prompt.wrap_at = @prompt.output_cols - 10
143
+
144
+ self.log.debug "Created new treewhat command object for %s" % [ @directory ]
145
+ end
146
+
147
+
148
+ ######
149
+ public
150
+ ######
151
+
152
+ # The LDAP directory the command will connect to
153
+ attr_reader :directory
154
+
155
+ # The Trollop options hash the command will read its configuration from
156
+ attr_reader :options
157
+
158
+ # The HighLine object to use for prompting and displaying stuff
159
+ attr_reader :prompt
160
+
161
+
162
+ ### Display an +object+ highlighted as a header.
163
+ def print_header( object )
164
+ self.prompt.say( self.prompt.color(object.to_s, :header) )
165
+ end
166
+
167
+
168
+ ### Run the command with the specified +pattern+.
169
+ def run( pattern=nil )
170
+ self.log.debug "Running with pattern = %p" % [ pattern ]
171
+
172
+ self.bind_to_directory if self.options.binddn
173
+
174
+ case pattern
175
+
176
+ # No argument
177
+ when NilClass, ''
178
+ self.show_directory_overview
179
+
180
+ # DN/RDN or filter if it contains a '='
181
+ when %r/=/
182
+ self.show_entry( pattern )
183
+
184
+ # Otherwise, try to find a schema item that matches
185
+ else
186
+ self.show_schema_artifact( pattern )
187
+ end
188
+
189
+ end
190
+
191
+
192
+ ### Prompt for a password and then bind to the command's directory using the binddn in
193
+ ### the options.
194
+ def bind_to_directory
195
+ binddn = self.options.binddn or
196
+ raise ArgumentError, "no binddn in the options hash?!"
197
+ self.log.debug "Attempting to bind to the directory as %s" % [ binddn ]
198
+
199
+ pass = self.prompt.ask( "password: " ) {|q| q.echo = '*' }
200
+ user = Treequel::Branch.new( self.directory, binddn )
201
+
202
+ self.directory.bind_as( user, pass )
203
+ self.log.debug " bound as %s" % [ user ]
204
+
205
+ return true
206
+ end
207
+
208
+
209
+ ### Show general information about the directory if the user doesn't give a pattern on
210
+ ### the command line.
211
+ def show_directory_overview
212
+ pr = self.prompt
213
+ dir = self.directory
214
+
215
+ self.print_header( dir.uri.to_s )
216
+ pr.say( "\n" )
217
+ pr.say( dir.schema.to_s )
218
+
219
+ self.show_column_list( dir.schema.attribute_types.values, 'Attribute Types' ) if
220
+ self.options.attrtypes
221
+ self.show_column_list( dir.schema.object_classes.values, "Object Classes" ) if
222
+ self.options.objectclasses
223
+ self.show_column_list( dir.schema.ldap_syntaxes.values, "Syntaxes" ) if
224
+ self.options.syntaxes
225
+ self.show_column_list( dir.schema.matching_rules.values, "Matching Rules" ) if
226
+ self.options.matching_rules
227
+ self.show_column_list( dir.schema.matching_rule_uses.values, "Matching Rule Uses" ) if
228
+ self.options.matching_rule_uses
229
+ end
230
+
231
+
232
+ ### Show the items from the given +enum+ under the specified +subheading+ in a columnized list.
233
+ def show_column_list( enum, subheading )
234
+ pr = self.prompt
235
+ items = nil
236
+
237
+ if enum.first.respond_to?( :name )
238
+ items = enum.map( &:name ).map( &:to_s ).uniq
239
+ else
240
+ items = enum.map( &:oid ).uniq
241
+ end
242
+
243
+ pr.say( "\n" )
244
+ pr.say( pr.color(subheading, :subheader) )
245
+ pr.say( pr.list(items.sort_by(&:downcase), :columns_down) )
246
+ end
247
+
248
+
249
+ #
250
+ # 'Show entry' mode
251
+ #
252
+
253
+ ### Fetch an entry from the directory and display it like Treequel's editing mode.
254
+ def show_entry( pattern )
255
+ dir = self.directory
256
+ branch = Treequel::Branch.new( dir, pattern )
257
+
258
+ if !branch.exists?
259
+ branch = Treequel::Branch.new( dir, pattern + ',' + dir.base_dn )
260
+ end
261
+
262
+ if !branch.exists?
263
+ branch = dir.filter( pattern ).first
264
+ end
265
+
266
+ if !branch
267
+ self.prompt.say( self.prompt.color("No match.", :error) )
268
+ end
269
+
270
+ yaml = self.branch_as_yaml( branch )
271
+ self.prompt.say( yaml )
272
+ end
273
+
274
+
275
+ ### Return the specified Treequel::Branch object as YAML. If +include_operational+ is true,
276
+ ### include the entry's operational attributes. If +extra_objectclasses+ contains
277
+ ### one or more objectClass OIDs, include their MUST and MAY attributes when building the
278
+ ### YAML representation of the branch.
279
+ def branch_as_yaml( object, include_operational=false )
280
+ object.include_operational_attrs = include_operational
281
+
282
+ # Make sure the displayed entry has the MUST attributes
283
+ entryhash = stringify_keys( object.must_attributes_hash )
284
+ entryhash.merge!( object.entry || {} )
285
+ entryhash['objectClass'] ||= []
286
+
287
+ entryhash.delete( 'dn' ) # Special attribute, can't be edited
288
+
289
+ yaml = entryhash.to_yaml
290
+ yaml[ 5, 0 ] = self.prompt.color( "# #{object.dn}\n", :header )
291
+
292
+ # Make comments out of MAY attributes that are unset
293
+ mayhash = stringify_keys( object.may_attributes_hash )
294
+ self.log.debug "MAY hash is: %p" % [ mayhash ]
295
+ mayhash.delete_if {|attrname,val| entryhash.key?(attrname) }
296
+ yaml << mayhash.to_yaml[5..-1].gsub( /\n\n/, "\n" ).gsub( /^/, '# ' )
297
+
298
+ return yaml
299
+ end
300
+
301
+
302
+
303
+
304
+ #
305
+ # 'Show schema artifact' mode
306
+ #
307
+
308
+ SCHEMA_ARTIFACT_TYPES = [
309
+ :object_classes,
310
+ :attribute_types,
311
+ :ldap_syntaxes,
312
+ :matching_rules,
313
+ :matching_rule_uses,
314
+ ]
315
+
316
+ ### Find an artifact in the directory's schema that matches +pattern+, and display it
317
+ ### if it exists.
318
+ def show_schema_artifact( pattern )
319
+ pr = self.prompt
320
+ schema = self.directory.schema
321
+ artifacts = SCHEMA_ARTIFACT_TYPES.
322
+ collect {|type| schema.send( type ).values.uniq }.flatten
323
+
324
+ if match = find_exact_matching_artifact( artifacts, pattern )
325
+ self.display_schema_artifact( match )
326
+ elsif match = find_substring_matching_artifact( artifacts, pattern )
327
+ pr.say( "No exact match. Falling back to substring match:" )
328
+ self.display_schema_artifact( match )
329
+ else
330
+ pr.say( pr.color("No match.", :error) )
331
+ end
332
+ end
333
+
334
+
335
+ ### Display a schema artifact in a readable way.
336
+ def display_schema_artifact( artifact )
337
+ self.prompt.say( self.prompt.color(artifact.class.name.sub(/.*::/, ''), :header) + ' ' )
338
+ self.prompt.say( self.prompt.color(artifact.to_s, :subheader) )
339
+
340
+ # Display some other stuff depending on what kind of thing it is
341
+ case artifact
342
+ when Treequel::Schema::AttributeType
343
+ self.display_attrtype_details( artifact )
344
+ end
345
+ end
346
+
347
+
348
+ ### Display additional details for the specified +attrtype+ (a Treequel::Schema::AttributeType).
349
+ def display_attrtype_details( attrtype )
350
+ ocs = self.directory.schema.object_classes.values.find_all do |oc|
351
+ ( oc.must_oids | oc.may_oids ).include?( attrtype.name.to_sym )
352
+ end
353
+
354
+ if ocs.empty?
355
+ self.prompt.say "No objectClasses with the '%s' attribute are in the current schema." %
356
+ [ attrtype.name ]
357
+ else
358
+ ocnames = ocs.uniq.map( &:name ).map( &:to_s ).sort
359
+
360
+ self.prompt.say "objectClasses with the '%s' attribute in the current schema:" %
361
+ [ attrtype.name ]
362
+ self.prompt.say( self.prompt.list(ocnames, :columns_across) )
363
+ end
364
+ end
365
+
366
+
367
+ ### Try to find an artifact in +artifacts+ whose name or oid matches +pattern+ exactly.
368
+ ### Returns the first matching artifact.
369
+ def find_exact_matching_artifact( artifacts, pattern )
370
+ self.log.debug "Trying to find an exact match for %p in %d artifacts." %
371
+ [ pattern, artifacts.length ]
372
+ return artifacts.find do |obj|
373
+ (obj.respond_to?( :names ) && obj.names.map(&:to_s).include?(pattern) ) ||
374
+ (obj.respond_to?( :name ) && obj.name.to_s == pattern ) ||
375
+ (obj.respond_to?( :oid ) && obj.oid == pattern )
376
+ end
377
+ end
378
+
379
+
380
+ ### Try to find an artifact in +artifacts+ whose name or oid contains +pattern+.
381
+ ### Returns the first matching artifact.
382
+ def find_substring_matching_artifact( artifacts, pattern )
383
+ pattern = Regexp.new( Regexp.escape(pattern), Regexp::IGNORECASE )
384
+
385
+ return artifacts.find do |obj|
386
+ (obj.respond_to?( :names ) && obj.names.find {|name| name.to_s =~ pattern} ) ||
387
+ (obj.respond_to?( :name ) && obj.name.to_s =~ pattern ) ||
388
+ (obj.respond_to?( :oid ) && obj.oid =~ pattern )
389
+ end
390
+ end
391
+
392
+
393
+ end # class Treequel::What
394
+
395
+
396
+ Treequel::What.run( ARGV.dup )
397
+
metadata ADDED
@@ -0,0 +1,238 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: treequel-shell
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.10.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michael Granger
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain:
12
+ - !binary |-
13
+ LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURMRENDQWhTZ0F3SUJB
14
+ Z0lCQURBTkJna3Foa2lHOXcwQkFRVUZBREE4TVF3d0NnWURWUVFEREFOblpX
15
+ UXgKRnpBVkJnb0praWFKay9Jc1pBRVpGZ2RmWVdWeWFXVmZNUk13RVFZS0Na
16
+ SW1pWlB5TEdRQkdSWURiM0puTUI0WApEVEV3TURreE5qRTBORGcxTVZvWERU
17
+ RXhNRGt4TmpFME5EZzFNVm93UERFTU1Bb0dBMVVFQXd3RFoyVmtNUmN3CkZR
18
+ WUtDWkltaVpQeUxHUUJHUllIWDJGbGNtbGxYekVUTUJFR0NnbVNKb21UOGl4
19
+ a0FSa1dBMjl5WnpDQ0FTSXcKRFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURD
20
+ Q0FRb0NnZ0VCQUx5Ly9CRnhDMWYvY1BTbnd0SkJXb0ZpRnJpcgpoN1JpY0kr
21
+ am9xL29jVlhRcUk0VERXUHlGLzh0cWt2dCtyRDk5WDlxczJZZVI4Q1UvWWlJ
22
+ cExXclFPWVNUNzBKCnZEbjdVdmhiMm11RlZxcTYrdm9iZVRrSUxCRU82cGlv
23
+ bldERzhqU2JvM3FLbTFSaktKRHdnOXA0d05LaFB1dTgKS0d1ZS9CRmI2N0tm
24
+ bHF5QXBQbVBlYjNWZGQ5Y2xzcHpxZUZxcDdjVUJNRXBGUzZMV3h5NEdrK3F2
25
+ RkZKQkpMQgpCVUhFL0xaVkpNVnpmcEM1VXErUW1ZN0IrRkgvUXFObmRuM3RP
26
+ SGdzUGFkTFROaW11QjFzQ3VMMWE0ejNQZXBkClRlTEJFRm1FYW81RGszSy9R
27
+ OG84dmxiSUIvakJEVFV4NkRqYmd4dzc3OTA5eDZnSTlkb1U0TEQ1WE1jQ0F3
28
+ RUEKQWFNNU1EY3dDUVlEVlIwVEJBSXdBREFMQmdOVkhROEVCQU1DQkxBd0hR
29
+ WURWUjBPQkJZRUZKZW9Ha09yOWw0Qgorc2FNa1cvWlhUNFVlU3ZWTUEwR0NT
30
+ cUdTSWIzRFFFQkJRVUFBNElCQVFCRzJLT2J2WUkyZUh5eUJVSlNKM2pOCnZF
31
+ blUzZDYwem5BWGJyU2QycWIzcjFsWTFFUEREM2JjeTBNZ2dDZkdkZzNYdTU0
32
+ ejIxb3F5SWRrOHVHdFdCUEwKSElhOUVnZkZHU1VFZ3ZjSXZhWXFpTjRqVFV0
33
+ aWRmRUZ3K0x0anM4QVA5Z1dnU0lZUzZHcjM4VjBXR0ZGTnpJSAphT0Qyd211
34
+ OW9vL1JmZlc0aFMvOEd1dmZNemN3N0NRMzU1d0ZSNEtCL255emUrRXNaMVk1
35
+ RGVyQ0FhZ01WdURRClUwQkxtV0RGelBHR1dsUGVRQ3JZSENyK0FjSnorTlJu
36
+ YUhDS0xaZFNLai9SSHVUT3QrZ2JsUmV4OEZBaDhOZUEKY21saFhlNDZwWk5K
37
+ Z1dLYnhaYWg4NWpJang5NWhSOHZPSStOQU01aUg5a09xSzEzRHJ4YWNUS1Bo
38
+ cWo1UGp3RgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
39
+ date: 2012-08-29 00:00:00.000000000 Z
40
+ dependencies:
41
+ - !ruby/object:Gem::Dependency
42
+ name: loggability
43
+ requirement: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ~>
47
+ - !ruby/object:Gem::Version
48
+ version: '0.5'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ version: '0.5'
57
+ - !ruby/object:Gem::Dependency
58
+ name: treequel
59
+ requirement: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ~>
63
+ - !ruby/object:Gem::Version
64
+ version: '1.10'
65
+ type: :runtime
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ~>
71
+ - !ruby/object:Gem::Version
72
+ version: '1.10'
73
+ - !ruby/object:Gem::Dependency
74
+ name: highline
75
+ requirement: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ~>
79
+ - !ruby/object:Gem::Version
80
+ version: '1.6'
81
+ type: :runtime
82
+ prerelease: false
83
+ version_requirements: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ~>
87
+ - !ruby/object:Gem::Version
88
+ version: '1.6'
89
+ - !ruby/object:Gem::Dependency
90
+ name: trollop
91
+ requirement: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
97
+ type: :runtime
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ~>
103
+ - !ruby/object:Gem::Version
104
+ version: '2.0'
105
+ - !ruby/object:Gem::Dependency
106
+ name: sysexits
107
+ requirement: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ~>
111
+ - !ruby/object:Gem::Version
112
+ version: '1.0'
113
+ type: :runtime
114
+ prerelease: false
115
+ version_requirements: !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ~>
119
+ - !ruby/object:Gem::Version
120
+ version: '1.0'
121
+ - !ruby/object:Gem::Dependency
122
+ name: hoe-mercurial
123
+ requirement: !ruby/object:Gem::Requirement
124
+ none: false
125
+ requirements:
126
+ - - ~>
127
+ - !ruby/object:Gem::Version
128
+ version: 1.4.0
129
+ type: :development
130
+ prerelease: false
131
+ version_requirements: !ruby/object:Gem::Requirement
132
+ none: false
133
+ requirements:
134
+ - - ~>
135
+ - !ruby/object:Gem::Version
136
+ version: 1.4.0
137
+ - !ruby/object:Gem::Dependency
138
+ name: hoe-highline
139
+ requirement: !ruby/object:Gem::Requirement
140
+ none: false
141
+ requirements:
142
+ - - ~>
143
+ - !ruby/object:Gem::Version
144
+ version: 0.1.0
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ none: false
149
+ requirements:
150
+ - - ~>
151
+ - !ruby/object:Gem::Version
152
+ version: 0.1.0
153
+ - !ruby/object:Gem::Dependency
154
+ name: rdoc
155
+ requirement: !ruby/object:Gem::Requirement
156
+ none: false
157
+ requirements:
158
+ - - ~>
159
+ - !ruby/object:Gem::Version
160
+ version: '3.10'
161
+ type: :development
162
+ prerelease: false
163
+ version_requirements: !ruby/object:Gem::Requirement
164
+ none: false
165
+ requirements:
166
+ - - ~>
167
+ - !ruby/object:Gem::Version
168
+ version: '3.10'
169
+ - !ruby/object:Gem::Dependency
170
+ name: hoe
171
+ requirement: !ruby/object:Gem::Requirement
172
+ none: false
173
+ requirements:
174
+ - - ~>
175
+ - !ruby/object:Gem::Version
176
+ version: '3.0'
177
+ type: :development
178
+ prerelease: false
179
+ version_requirements: !ruby/object:Gem::Requirement
180
+ none: false
181
+ requirements:
182
+ - - ~>
183
+ - !ruby/object:Gem::Version
184
+ version: '3.0'
185
+ description: ! "Treequel-Shell is a collection of LDAP tools based on the Treequel
186
+ LDAP\ntoolkit.\n\nIt includes:\n\ntreequel :: an LDAP shell/editor; treat your LDAP
187
+ directory like a filesystem!\ntreewhat :: an LDAP schema explorer. Dump objectClasses
188
+ and attribute type info\n in several convenient formats."
189
+ email:
190
+ - ged@FaerieMUD.org
191
+ executables:
192
+ - treeirb
193
+ - treequel
194
+ - treewhat
195
+ extensions: []
196
+ extra_rdoc_files:
197
+ - History.rdoc
198
+ - Manifest.txt
199
+ - README.rdoc
200
+ files:
201
+ - ChangeLog
202
+ - History.rdoc
203
+ - Manifest.txt
204
+ - README.rdoc
205
+ - Rakefile
206
+ - bin/treeirb
207
+ - bin/treequel
208
+ - bin/treewhat
209
+ homepage: https://bitbucket.org/ged/Treequel-Shell
210
+ licenses:
211
+ - BSD
212
+ post_install_message:
213
+ rdoc_options:
214
+ - -f
215
+ - fivefish
216
+ - -t
217
+ - Treequel
218
+ require_paths:
219
+ - lib
220
+ required_ruby_version: !ruby/object:Gem::Requirement
221
+ none: false
222
+ requirements:
223
+ - - ! '>='
224
+ - !ruby/object:Gem::Version
225
+ version: 1.8.7
226
+ required_rubygems_version: !ruby/object:Gem::Requirement
227
+ none: false
228
+ requirements:
229
+ - - ! '>='
230
+ - !ruby/object:Gem::Version
231
+ version: '0'
232
+ requirements: []
233
+ rubyforge_project: treequel-shell
234
+ rubygems_version: 1.8.24
235
+ signing_key:
236
+ specification_version: 3
237
+ summary: Treequel-Shell is a collection of LDAP tools based on the Treequel LDAP toolkit
238
+ test_files: []