ihelp 0.3.2

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.
Files changed (2) hide show
  1. data/lib/ihelp.rb +661 -0
  2. metadata +45 -0
@@ -0,0 +1,661 @@
1
+ require 'rdoc/ri/ri_driver'
2
+ require 'rexml/document'
3
+
4
+ # Ri bindings for interactive use from within Ruby.
5
+ # Does a bit of second-guessing (Instance method? Class method?
6
+ # Try both unless explicitly defined. Not found in this class? Try the
7
+ # ancestor classes.)
8
+ #
9
+ # The goal is that help is given for all methods that have help.
10
+ #
11
+ # Examples:
12
+ #
13
+ # require 'ihelp'
14
+ #
15
+ # a = "string"
16
+ # a.help
17
+ # a.help :reverse
18
+ # a.help :map
19
+ # String.help
20
+ # String.help :new
21
+ # String.help :reverse
22
+ # String.help :map
23
+ # String.instance_help :reverse
24
+ # String.instance_help :new # => No help found.
25
+ # a.help :new
26
+ # help "String#reverse"
27
+ # help "String.reverse"
28
+ # a.method(:reverse).help # gets help for Method
29
+ # help "Hash#map"
30
+ #
31
+ #
32
+ # Custom help renderers:
33
+ #
34
+ # The help-method calls IHelp::Renderer's method defined by
35
+ # IHelp.renderer with the RI info object. You can print help
36
+ # out the way you want by defining your own renderer method
37
+ # in IHelp::Renderer and setting IHelp.renderer to the name
38
+ # of the method.
39
+ #
40
+ # Example:
41
+ #
42
+ # require 'ihelp'
43
+ #
44
+ # IHelp.renderers
45
+ # # => ["emacs", "rubydoc", "ri", "source", "html"]
46
+ #
47
+ # class IHelp::Renderer
48
+ # def print_name(info)
49
+ # puts info.full_name
50
+ # end
51
+ # end
52
+ #
53
+ # IHelp.renderers
54
+ # # => ["emacs", "rubydoc", "ri", "source", "print_name", "html"]
55
+ #
56
+ # IHelp.renderer = :print_name
57
+ #
58
+ # [1,2,3].help:reject
59
+ # # Array#reject
60
+ # # => nil
61
+ #
62
+ # The current renderers are:
63
+ #
64
+ # ri -- the default renderer
65
+ # html -- creates a HTML document for the help and opens it
66
+ # with the program named in IHelp.web_browser
67
+ # rubydoc -- opens the corresponding www.ruby-doc.org class
68
+ # documentation page with the program named in
69
+ # IHelp.web_browser
70
+ # emacs -- uses gnudoit and ri-emacs to display help in an Emacs buffer.
71
+ # The Emacs commands that I got it running with were:
72
+ # ;; make ri-emacs autoload according to its instructions
73
+ # M-x ruby-mode
74
+ # M-x gnuserv-start
75
+ # M-x run-ruby
76
+ # > IHelp.renderer = :emacs
77
+ # > String.help:center
78
+ # source -- uses RubyToRuby to print the source for the method
79
+ # (experimental)
80
+ #
81
+ #
82
+ # Changelog:
83
+ #
84
+ # 0.3.2
85
+ # Added support for ruby 1.8.5, added emacs renderer from
86
+ # rubykitch <rubykitch@ruby-lang.org>, made source renderer use the
87
+ # released version of RubyToRuby.
88
+ #
89
+ # License: Ruby's
90
+ #
91
+ # Author: Ilmari Heikkinen <kig misfiring net>
92
+ #
93
+ module IHelp
94
+ HELP_VERSION = "0.3.2"
95
+ end
96
+
97
+
98
+ module IHelp
99
+
100
+ # Returns list of available renderers.
101
+ def self.renderers
102
+ Renderer.instance_methods(false)
103
+ end
104
+
105
+ # Contains the help renderer methods to be used by IHelp#help.
106
+ # The help-method creates a new instance of Renderer and calls
107
+ # the method defined by IHelp.renderer with the RI info object.
108
+ #
109
+ class Renderer
110
+
111
+ # Default renderer method, opens the help using the IHelpDriver
112
+ # gotten from IHelp.ri_driver.
113
+ #
114
+ def ri(info)
115
+ IHelp.ri_driver.display_info(info)
116
+ end
117
+
118
+ # Opens the class documentation page on www.ruby-doc.org using
119
+ # the program defined in IHelp::Renderer.web_browser.
120
+ #
121
+ def rubydoc(info)
122
+ require 'uri'
123
+ class_name = parse_ruby_doc_url(info.full_name)
124
+ puts "Opening help for: #{class_name.gsub(/\//,"::")}"
125
+ system(IHelp.web_browser, "http://www.ruby-doc.org/core/classes/#{class_name}.html")
126
+ end
127
+
128
+ # Show sources -renderer using RubyToRuby.
129
+ #
130
+ # http://seattlerb.rubyforge.org/
131
+ #
132
+ # sudo gem install ruby2ruby
133
+ #
134
+ def source(info)
135
+ require 'ruby2ruby'
136
+ class_name = info.full_name.split(/[#\.]/).first
137
+ klass = class_name.split("::").inject(Object){|o,i| o.const_get(i)}
138
+ args = [klass]
139
+ args << info.name if info.is_a? RI::MethodDescription
140
+ puts RubyToRuby.translate(*args)
141
+ end
142
+
143
+ # XEmacs renderer.
144
+ # Uses ri-emacs to show the ri output in Emacs.
145
+ #
146
+ # http://rubyforge.org/projects/ri-emacs/
147
+ # http://www.rubyist.net/~rubikitch/computer/irbsh/index.en.html
148
+ #
149
+ def emacs(info)
150
+ system "gnudoit", %Q[(progn (ri "#{info.full_name}") "#{info.full_name}")]
151
+ end
152
+
153
+ def html(info)
154
+ puts "Opening help for: #{info.full_name}"
155
+ doc = REXML::Document.new
156
+ root = doc.add_element("html")
157
+ head = root.add_element("head")
158
+ title = head.add_element("title")
159
+ title.add_text("#{info.full_name} - RI Documentation")
160
+ body = root.add_element("body")
161
+ body.add_element(info.to_html.root)
162
+ tmp = Tempfile.new("#{info.full_name.gsub(/\W/,"_")}_doc.html")
163
+ tmp.write( doc.to_s(2) )
164
+ tmp.flush
165
+ pid = fork{
166
+ system(IHelp.web_browser, "file://#{tmp.path}")
167
+ tmp.close!
168
+ }
169
+ Process.detach(pid)
170
+ pid
171
+ end
172
+
173
+ private
174
+ def parse_ruby_doc_url(item_name)
175
+ item_name.split(/\.|#/,2).first.gsub(/::/,"/")
176
+ end
177
+
178
+ end
179
+
180
+
181
+ # Print out help for self.
182
+ #
183
+ # If method_name is given, prints help for that method.
184
+ # If instance is true, tries to find help only for the instance method.
185
+ # If instance is false, tries to find help for the object's method only.
186
+ # If instance is nil, checks object's method first, then instance method.
187
+ #
188
+ # Uses help_description(method_name, instance).
189
+ #
190
+ def help(method_name=nil, instance=nil)
191
+ info = help_description(method_name, instance)
192
+ if not info
193
+ puts "No help found."
194
+ return
195
+ end
196
+ IHelp.render(info)
197
+ end
198
+
199
+ # Print out help for instance method method_name.
200
+ # If no method_name given, behaves like #help.
201
+ #
202
+ def instance_help(method_name = nil)
203
+ help(method_name, true)
204
+ end
205
+
206
+ # Returns help string in YAML for self.
207
+ #
208
+ # If method_name is given, prints help for that method.
209
+ # If instance is true, tries to find help only for the instance method.
210
+ # If instance is false, tries to find help for the object's method only.
211
+ # If instance is nil, checks object's method first, then instance method.
212
+ # Returns nil if there is no help to be found.
213
+ #
214
+ def help_yaml(method_name=nil, instance=nil)
215
+ info = help_description(method_name, instance)
216
+ info.to_yaml if info
217
+ end
218
+
219
+ # Returns help string as a HTML REXML::Document with a DIV element as the root.
220
+ #
221
+ # If method_name is given, prints help for that method.
222
+ # If instance is true, tries to find help only for the instance method.
223
+ # If instance is false, tries to find help for the object's method only.
224
+ # If instance is nil, checks object's method first, then instance method.
225
+ # Returns nil if there is no help to be found.
226
+ #
227
+ def help_html(method_name=nil, instance=nil)
228
+ info = help_description(method_name, instance)
229
+ info.to_html if info
230
+ end
231
+
232
+ # Return RI::ClassDescription / RI::MethodDescription for self
233
+ # or its method meth, or its instance method meth if instance == true.
234
+ #
235
+ def help_description(method_name=nil, instance=nil)
236
+ IHelp.generate_help_description(self, method_name, instance)
237
+ end
238
+
239
+
240
+ class << self
241
+
242
+ # Help renderer to use.
243
+ attr_accessor :renderer
244
+
245
+ # Web browser to use for html and rubydoc renderers.
246
+ attr_accessor :web_browser
247
+
248
+ IHelp.renderer ||= :ri
249
+ IHelp.web_browser ||= 'firefox'
250
+
251
+ IHelp::RI_ARGS = []
252
+ if ENV["RI"]
253
+ IHelp::RI_ARGS = ENV["RI"].split.concat(ARGV)
254
+ end
255
+
256
+ # Render the RI info object a renderer method in IHelp::Renderer.
257
+ # The name of the renderer method to use is returned by IHelp.renderer,
258
+ # and can be set with IHelp.renderer=.
259
+ #
260
+ def render(info)
261
+ IHelp::Renderer.new.send(renderer, info)
262
+ end
263
+
264
+ def ri_driver
265
+ @ri_driver ||= IHelpDriver.new(RI_ARGS)
266
+ end
267
+
268
+ # Return RI::ClassDescription / RI::MethodDescription for klass
269
+ # or its method meth, or its instance method meth if instance == true.
270
+ #
271
+ def generate_help_description(klass, meth=nil, instance=nil)
272
+ meth_str = nil
273
+ double_colon = false
274
+ if meth
275
+ meth_str = meth.to_s
276
+ if /::|\.|#/ === meth_str # called with e.g."Array#str","String.new"
277
+ meth_str, klass_name, instance_help, double_colon =
278
+ get_help_klass_info_for_name(meth_str)
279
+ klass_ancs = find_ancestors(klass_name, instance)
280
+ else
281
+ klass_name, klass_ancs, instance_help =
282
+ get_help_klass_info(klass, instance)
283
+ end
284
+ else
285
+ klass_name, klass_ancs, instance_help =
286
+ get_help_klass_info(klass, instance)
287
+ end
288
+ info = get_help_info(meth_str, klass_name, klass_ancs, instance_help,
289
+ instance)
290
+ # Retry with method as class if double_colon-splitted and no info
291
+ if info.nil? and double_colon
292
+ klass_name = [klass_name, meth_str].join("::")
293
+ meth_str = nil
294
+ klass_ancs = find_ancestors(klass_name, instance)
295
+ info = get_help_info(
296
+ meth_str, klass_name, klass_ancs, instance_help, instance)
297
+ end
298
+ info
299
+ end
300
+
301
+ private
302
+ def get_help_klass_info(klass,instance)
303
+ if klass.is_a? Class or klass.is_a? Module
304
+ klass_ancs = klass.ancestors + klass.class.ancestors
305
+ klass_ancs -= [klass, klass.class]
306
+ instance = false if instance.nil?
307
+ # If we are an instance, set klass to our class
308
+ #
309
+ else
310
+ klass = klass.class
311
+ klass_ancs = klass.ancestors - [klass]
312
+ instance = true if instance.nil?
313
+ end
314
+ klass_name = klass.name
315
+ [klass_name, klass_ancs, instance]
316
+ end
317
+
318
+ def get_help_klass_info_for_name(meth_str)
319
+ double_colon = false
320
+ # Maybe we are being called with something like "Array#slice"
321
+ if /#/ === meth_str
322
+ klass_name, meth_str = meth_str.split(/#/, 2)
323
+ instance = true
324
+
325
+ # Or maybe the requested item is "Ri::RiDriver.new"
326
+ elsif /\./ === meth_str
327
+ klass_name, meth_str = meth_str.reverse.split(/\./, 2).
328
+ reverse.map{|i| i.reverse}
329
+ instance = false
330
+
331
+ # And the problematic case of "Test::Unit" (is Unit a class name or
332
+ # a method name? Why does Ri even care?)
333
+ else
334
+ klass_name, meth_str = meth_str.reverse.split(/::/, 2).
335
+ reverse.map{|i| i.reverse}
336
+ double_colon = true
337
+ instance = false
338
+ end
339
+ [meth_str, klass_name, instance, double_colon]
340
+ end
341
+
342
+ # Find ancestors for klass_name (works if the class has been loaded)
343
+ def find_ancestors(klass_name, instance)
344
+ similarily_named_class = nil
345
+ ObjectSpace.each_object(Class){|k|
346
+ similarily_named_class = k if k.name == klass_name
347
+ break if similarily_named_class
348
+ }
349
+ if similarily_named_class
350
+ klass_ancs = similarily_named_class.ancestors
351
+ klass_ancs += similarily_named_class.class.ancestors unless instance
352
+ else
353
+ klass_ancs = []
354
+ end
355
+ klass_ancs
356
+ end
357
+
358
+ def get_help_info(meth_str, klass_name, klass_ancs, instance_help, instance)
359
+ info = get_help_info_str(meth_str, klass_name, klass_ancs, instance_help)
360
+ # If instance is undefined, try both the class methods and instance
361
+ # methods.
362
+ if info.nil? and instance.nil?
363
+ info = get_help_info_str(
364
+ meth_str, klass_name, klass_ancs, (not instance_help))
365
+ end
366
+ info
367
+ end
368
+
369
+ def get_help_info_str(meth_str, klass_name, klass_ancs, instance)
370
+ info_str = ri_driver.get_info_str(klass_name, meth_str, instance)
371
+ if not info_str
372
+ # Walk through class hierarchy to find an inherited method
373
+ ancest = klass_ancs.find{|anc|
374
+ info_str = ri_driver.get_info_str(anc.name, meth_str, instance)
375
+ }
376
+ # Avoid returning Object in case of no help.
377
+ if ancest == Object and meth_str.nil? and klass_name != Object.name
378
+ info_str = nil
379
+ end
380
+ end
381
+ info_str
382
+ end
383
+ end
384
+
385
+
386
+ # Version of RiDriver that takes its options
387
+ # as parameter to #initialize.
388
+ #
389
+ class IHelpDriver < RiDriver
390
+
391
+ # Create new IHelpDriver, with the given args
392
+ # passed to @options, which is a RI::Options.instance
393
+ #
394
+ def initialize(args = [])
395
+ @options = RI::Options.instance
396
+ @options.parse(args)
397
+
398
+ paths = (if RUBY_VERSION > "1.8.4"
399
+ @options.doc_dir
400
+ else
401
+ @options.paths
402
+ end) || RI::Paths::PATH
403
+ if paths.empty?
404
+ report_missing_documentation(paths)
405
+ end
406
+ @ri_reader = RI::RiReader.new(RI::RiCache.new(paths))
407
+ @display = @options.displayer
408
+ end
409
+
410
+ # Get info string from ri database for klass_name [method_name]
411
+ #
412
+ def get_info_str(klass_name, method_name = nil, instance = false)
413
+ is_class_method = (not instance)
414
+ top_level_namespace = @ri_reader.top_level_namespace
415
+ namespaces = klass_name.split(/::/).inject(top_level_namespace){
416
+ |ns, current_name|
417
+ @ri_reader.lookup_namespace_in(current_name, ns)
418
+ }
419
+ return nil if namespaces.empty?
420
+ if method_name.nil?
421
+ get_class_info_str(namespaces)
422
+ else
423
+ methods = @ri_reader.find_methods(
424
+ method_name, is_class_method, namespaces)
425
+ return nil if methods.empty?
426
+ get_method_info_str(method_name, methods)
427
+ end
428
+ end
429
+
430
+ # Display the info based on if it's
431
+ # for a class or a method. Using ri's pager.
432
+ #
433
+ def display_info(info)
434
+ case [info.class] # only info.class doesn't work
435
+ when [RI::ClassDescription]
436
+ @display.display_class_info(info, @ri_reader)
437
+ when [RI::MethodDescription]
438
+ @display.display_method_info(info)
439
+ end
440
+ end
441
+
442
+ # Get info for the class in the given namespaces.
443
+ #
444
+ def get_class_info_str(namespaces)
445
+ return nil if namespaces.empty?
446
+ klass = nil
447
+ namespaces.find{|ns|
448
+ begin
449
+ klass = @ri_reader.get_class(ns)
450
+ rescue TypeError
451
+ nil
452
+ end
453
+ }
454
+ klass
455
+ end
456
+
457
+ # Get info for the method in the given methods.
458
+ #
459
+ def get_method_info_str(requested_method_name, methods)
460
+ if methods.size == 1
461
+ @ri_reader.get_method(methods.first)
462
+ else
463
+ entries = methods.find_all {|m| m.name == requested_method_name}
464
+ return nil if entries.empty?
465
+ method = nil
466
+ entries.find{|entry| method = @ri_reader.get_method(entry)}
467
+ method
468
+ end
469
+ end
470
+
471
+ end
472
+
473
+
474
+ end
475
+
476
+
477
+ module RI
478
+
479
+ class MethodDescription
480
+
481
+ # Creates HTML element from the MethodDescription.
482
+ # Uses container_tag as the root node name and header_tag
483
+ # as the tag for the header element that contains the method's name.
484
+ #
485
+ # Returns a REXML document with container_tag as the root element name.
486
+ #
487
+ def to_html(container_tag="div", header_tag="h1")
488
+ doc = REXML::Document.new
489
+ root = doc.add_element(container_tag)
490
+ header = root.add_element(header_tag)
491
+ header.add_text(full_name)
492
+ comment.each{|c|
493
+ tag = c.class.to_s.split("::").last
494
+ tag = "PRE" if tag == "VERB"
495
+ xmlstr = "<#{tag}>#{c.body}</#{tag}>"
496
+ c_doc = REXML::Document.new(xmlstr)
497
+ root.add_element( c_doc.root )
498
+ }
499
+ doc
500
+ end
501
+
502
+ end
503
+
504
+
505
+ class ClassDescription
506
+
507
+ # Creates HTML element from the ClassDescription.
508
+ # Uses container_tag as the root node name and header_tag
509
+ # as the tag for the header element that contains the classes name.
510
+ # Uses methods_header_tag as the tag for the "Class/Instance Methods"
511
+ # method list headers.
512
+ # Uses methods_tag as the tag for the method lists.
513
+ #
514
+ # Returns a REXML document with container_tag as the root element name.
515
+ #
516
+ def to_html(container_tag="div", header_tag="h1",
517
+ methods_header_tag="h2", methods_tag="p")
518
+ doc = REXML::Document.new
519
+ root = doc.add_element(container_tag)
520
+ header = root.add_element(header_tag)
521
+ header.add_text(full_name)
522
+ comment.each{|c|
523
+ tag = c.class.to_s.split("::").last
524
+ tag = "PRE" if tag == "VERB"
525
+ xmlstr = "<#{tag}>#{c.body}</#{tag}>"
526
+ c_doc = REXML::Document.new(xmlstr)
527
+ root.add_element( c_doc.root )
528
+ }
529
+ root.add_element(methods_header_tag).add_text("Class Methods")
530
+ cmethods = root.add_element(methods_tag)
531
+ class_methods[0...-1].each{|m|
532
+ cmethods.add(m.to_html.root)
533
+ cmethods.add_text(", ")
534
+ }
535
+ cmethods.add(class_methods.last.to_html.root)
536
+ root.add_element(methods_header_tag).add_text("Instance Methods")
537
+ imethods = root.add_element(methods_tag)
538
+ instance_methods[0...-1].each{|m|
539
+ imethods.add(m.to_html.root)
540
+ imethods.add_text(", ")
541
+ }
542
+ imethods.add(instance_methods.last.to_html.root)
543
+ doc
544
+ end
545
+
546
+ end
547
+
548
+
549
+ class MethodSummary
550
+
551
+ # Creates HTML element from the ClassDescription.
552
+ # Puts the method's name inside the tag named in
553
+ # container_tag.
554
+ #
555
+ # Returns a REXML document with container_tag as the root element name.
556
+ #
557
+ def to_html(container_tag="em")
558
+ doc = REXML::Document.new
559
+ doc.add_element(container_tag).add_text(name)
560
+ doc
561
+ end
562
+
563
+ end
564
+
565
+ end
566
+
567
+
568
+ class Object
569
+ include IHelp
570
+ extend IHelp
571
+ end
572
+
573
+
574
+ if __FILE__ == $0
575
+ require 'test/unit'
576
+
577
+ # to get around rdoc documenting NoHelp
578
+ eval("module NoHelp; end")
579
+
580
+ class HelpTest < Test::Unit::TestCase
581
+
582
+ def no_warn
583
+ old_w = $-w
584
+ $-w = nil
585
+ yield
586
+ $-w = old_w
587
+ end
588
+
589
+ def setup
590
+ no_warn{
591
+ Object.const_set("ARGV", ["--readline", "--prompt-mode", "simple"])
592
+ }
593
+ IHelp.instance_variable_set(
594
+ :@ri_driver,
595
+ IHelp::IHelpDriver.new(IHelp::RI_ARGS))
596
+ end
597
+
598
+ def test_simple_help
599
+ assert("string".help_yaml)
600
+ end
601
+
602
+ def test_method_help
603
+ assert("string".help_yaml(:reverse))
604
+ end
605
+
606
+ def test_inherited_method_help
607
+ assert("string".help_yaml(:map))
608
+ end
609
+
610
+ def test_class_help
611
+ assert(String.help_yaml)
612
+ end
613
+
614
+ def test_class_method_help
615
+ assert(String.help_yaml(:new))
616
+ end
617
+
618
+ def test_class_inherited_method_help
619
+ assert(String.help_yaml(:map))
620
+ end
621
+
622
+ def test_method_equalities
623
+ assert(String.help_yaml(:new) ==
624
+ "string".help_yaml(:new))
625
+ assert(String.help_yaml(:reverse) ==
626
+ "string".help_yaml(:reverse))
627
+ end
628
+
629
+ def test_method_constraints
630
+ assert((not "string".help_yaml(:new,true)))
631
+ assert((not "string".help_yaml(:reverse,false)))
632
+ assert((not String.help_yaml(:new,true)))
633
+ assert((not String.help_yaml(:reverse,false)))
634
+ end
635
+
636
+ def test_help_yamlings
637
+ assert("string".help_yaml(:reverse) ==
638
+ help_yaml("String#reverse"))
639
+ assert(String.help_yaml(:new) ==
640
+ help_yaml("String::new"))
641
+ end
642
+
643
+ def test_multipart_namespaces
644
+ assert(Test::Unit.help_yaml)
645
+ assert(help_yaml("Test::Unit"))
646
+ assert(Test::Unit.help_yaml("run?"))
647
+ assert(help_yaml("Test::Unit.run?"))
648
+ assert(help_yaml("Test::Unit::run?"))
649
+ assert(help_yaml("Test::Unit#run?"))
650
+ end
651
+
652
+ def test_not_found
653
+ assert((NoHelp.help_yaml == nil))
654
+ assert((String.help_yaml(:nonexistent) == nil))
655
+ end
656
+
657
+ end
658
+
659
+
660
+ end
661
+
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: ihelp
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.3.2
7
+ date: 2006-11-13 00:00:00 +02:00
8
+ summary: Interactive help
9
+ require_paths:
10
+ - lib
11
+ email: kig@misfiring.net
12
+ homepage: ihelp.rubyforge.org
13
+ rubyforge_project: ihelp
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.8.1
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Ilmari Heikkinen
30
+ files:
31
+ - lib/ihelp.rb
32
+ test_files: []
33
+
34
+ rdoc_options: []
35
+
36
+ extra_rdoc_files: []
37
+
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ requirements: []
43
+
44
+ dependencies: []
45
+