bolt 3.11.0 → 3.15.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Puppetfile +2 -2
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +137 -104
- data/bolt-modules/boltlib/lib/puppet/functions/background.rb +2 -1
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +5 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +13 -0
- data/bolt-modules/boltlib/lib/puppet/functions/wait.rb +47 -7
- data/bolt-modules/log/lib/puppet/functions/log/debug.rb +39 -0
- data/bolt-modules/log/lib/puppet/functions/log/error.rb +40 -0
- data/bolt-modules/log/lib/puppet/functions/log/fatal.rb +40 -0
- data/bolt-modules/log/lib/puppet/functions/log/info.rb +39 -0
- data/bolt-modules/log/lib/puppet/functions/log/trace.rb +39 -0
- data/bolt-modules/log/lib/puppet/functions/log/warn.rb +41 -0
- data/bolt-modules/out/lib/puppet/functions/out/message.rb +9 -49
- data/bolt-modules/out/lib/puppet/functions/out/verbose.rb +35 -0
- data/guides/{debugging.txt → debugging.yaml} +5 -6
- data/guides/{inventory.txt → inventory.yaml} +6 -7
- data/guides/{links.txt → links.yaml} +3 -4
- data/guides/{logging.txt → logging.yaml} +5 -6
- data/guides/{module.txt → module.yaml} +5 -6
- data/guides/{modulepath.txt → modulepath.yaml} +5 -6
- data/guides/{project.txt → project.yaml} +6 -7
- data/guides/{targets.txt → targets.yaml} +5 -6
- data/guides/{transports.txt → transports.yaml} +6 -7
- data/lib/bolt/analytics.rb +1 -1
- data/lib/bolt/applicator.rb +23 -1
- data/lib/bolt/bolt_option_parser.rb +6 -3
- data/lib/bolt/cli.rb +34 -14
- data/lib/bolt/config/options.rb +2 -2
- data/lib/bolt/config/transport/options.rb +12 -0
- data/lib/bolt/config/transport/ssh.rb +7 -0
- data/lib/bolt/error.rb +3 -3
- data/lib/bolt/executor.rb +12 -4
- data/lib/bolt/fiber_executor.rb +57 -12
- data/lib/bolt/outputter/human.rb +124 -15
- data/lib/bolt/outputter/json.rb +5 -5
- data/lib/bolt/outputter/logger.rb +6 -0
- data/lib/bolt/pal.rb +81 -21
- data/lib/bolt/pal/yaml_plan/step.rb +2 -0
- data/lib/bolt/pal/yaml_plan/step/message.rb +0 -8
- data/lib/bolt/pal/yaml_plan/step/verbose.rb +31 -0
- data/lib/bolt/pal/yaml_plan/transpiler.rb +1 -1
- data/lib/bolt/plan_future.rb +21 -6
- data/lib/bolt/plugin/task.rb +1 -1
- data/lib/bolt/transport/ssh/exec_connection.rb +3 -1
- data/lib/bolt/util/format.rb +68 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/schemas/connect-data.json +4 -1
- data/lib/bolt_server/schemas/partials/target-ssh.json +4 -0
- data/lib/bolt_server/schemas/partials/target-winrm.json +4 -0
- data/lib/bolt_server/transport_app.rb +93 -52
- data/lib/bolt_spec/bolt_context.rb +9 -0
- data/lib/bolt_spec/plans.rb +1 -1
- data/lib/bolt_spec/plans/mock_executor.rb +31 -7
- data/lib/bolt_spec/plans/publish_stub.rb +4 -4
- data/modules/canary/plans/init.pp +1 -1
- data/resources/bolt_bash_completion.sh +1 -1
- metadata +28 -14
- data/guides/guide.txt +0 -17
data/lib/bolt/outputter/human.rb
CHANGED
@@ -78,6 +78,8 @@ module Bolt
|
|
78
78
|
@disable_depth += 1
|
79
79
|
when :message
|
80
80
|
print_message(event[:message])
|
81
|
+
when :verbose
|
82
|
+
print_message(event[:message]) if @verbose
|
81
83
|
end
|
82
84
|
|
83
85
|
if enabled?
|
@@ -339,7 +341,7 @@ module Bolt
|
|
339
341
|
info << if task.description
|
340
342
|
indent(2, task.description.chomp)
|
341
343
|
else
|
342
|
-
indent(2, 'No description')
|
344
|
+
indent(2, 'No description available.')
|
343
345
|
end
|
344
346
|
info << "\n\n"
|
345
347
|
|
@@ -398,7 +400,7 @@ module Bolt
|
|
398
400
|
info << if plan['description']
|
399
401
|
indent(2, plan['description'].chomp)
|
400
402
|
else
|
401
|
-
indent(2, 'No description')
|
403
|
+
indent(2, 'No description available.')
|
402
404
|
end
|
403
405
|
info << "\n\n"
|
404
406
|
|
@@ -462,13 +464,23 @@ module Bolt
|
|
462
464
|
end
|
463
465
|
|
464
466
|
def print_topics(topics)
|
465
|
-
|
466
|
-
|
467
|
-
|
467
|
+
info = +"#{colorize(:cyan, 'Topics')}\n"
|
468
|
+
info << indent(2, topics.join("\n"))
|
469
|
+
info << "\n\n#{colorize(:cyan, 'Additional information')}\n"
|
470
|
+
info << indent(2, "Use 'bolt guide <TOPIC>' to view a specific guide.")
|
471
|
+
@stream.puts info
|
468
472
|
end
|
469
473
|
|
470
|
-
def print_guide(guide
|
471
|
-
|
474
|
+
def print_guide(topic:, guide:, documentation: nil)
|
475
|
+
info = +"#{colorize(:cyan, topic)}\n"
|
476
|
+
info << indent(2, guide)
|
477
|
+
|
478
|
+
if documentation
|
479
|
+
info << "\n#{colorize(:cyan, 'Documentation')}\n"
|
480
|
+
info << indent(2, documentation.join("\n"))
|
481
|
+
end
|
482
|
+
|
483
|
+
@stream.puts info
|
472
484
|
end
|
473
485
|
|
474
486
|
def print_plan_lookup(value)
|
@@ -476,15 +488,19 @@ module Bolt
|
|
476
488
|
end
|
477
489
|
|
478
490
|
def print_module_list(module_list)
|
491
|
+
info = +''
|
492
|
+
|
479
493
|
module_list.each do |path, modules|
|
480
|
-
if (mod = modules.find { |m| m[:internal_module_group] })
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
494
|
+
info << if (mod = modules.find { |m| m[:internal_module_group] })
|
495
|
+
colorize(:cyan, mod[:internal_module_group])
|
496
|
+
else
|
497
|
+
colorize(:cyan, path)
|
498
|
+
end
|
499
|
+
|
500
|
+
info << "\n"
|
485
501
|
|
486
502
|
if modules.empty?
|
487
|
-
|
503
|
+
info << '(no modules installed)'
|
488
504
|
else
|
489
505
|
module_info = modules.map do |m|
|
490
506
|
version = if m[:version].nil?
|
@@ -496,11 +512,87 @@ module Bolt
|
|
496
512
|
[m[:name], version]
|
497
513
|
end
|
498
514
|
|
499
|
-
|
515
|
+
info << format_table(module_info, 2, 1).to_s
|
500
516
|
end
|
501
517
|
|
502
|
-
|
518
|
+
info << "\n\n"
|
503
519
|
end
|
520
|
+
|
521
|
+
command = Bolt::Util.powershell? ? 'Get-BoltModule -Name <MODULE>' : 'bolt module show <MODULE>'
|
522
|
+
info << colorize(:cyan, "Additional information\n")
|
523
|
+
info << indent(2, "Use '#{command}' to view details for a specific module.")
|
524
|
+
|
525
|
+
@stream.puts info
|
526
|
+
end
|
527
|
+
|
528
|
+
# Prints detailed module information.
|
529
|
+
#
|
530
|
+
# @param name [String] The module's short name.
|
531
|
+
# @param metadata [Hash] The module's metadata.
|
532
|
+
# @param path [String] The path to the module.
|
533
|
+
# @param plans [Array] The module's plans.
|
534
|
+
# @param tasks [Array] The module's tasks.
|
535
|
+
#
|
536
|
+
def print_module_info(name:, metadata:, path:, plans:, tasks:, **_kwargs)
|
537
|
+
info = +''
|
538
|
+
|
539
|
+
info << colorize(:cyan, name)
|
540
|
+
|
541
|
+
info << colorize(:dim, " [#{metadata['version']}]") if metadata['version']
|
542
|
+
info << "\n"
|
543
|
+
|
544
|
+
info << if metadata['summary']
|
545
|
+
indent(2, wrap(metadata['summary'].strip, 76))
|
546
|
+
else
|
547
|
+
indent(2, "No description available.\n")
|
548
|
+
end
|
549
|
+
info << "\n"
|
550
|
+
|
551
|
+
if tasks.any?
|
552
|
+
length = tasks.map(&:first).map(&:length).max
|
553
|
+
data = tasks.map { |task, desc| [task, truncate(desc, 76 - length)] }
|
554
|
+
info << colorize(:cyan, "Tasks\n")
|
555
|
+
info << format_table(data, 2).to_s
|
556
|
+
info << "\n\n"
|
557
|
+
end
|
558
|
+
|
559
|
+
if plans.any?
|
560
|
+
length = plans.map(&:first).map(&:length).max
|
561
|
+
data = plans.map { |plan, desc| [plan, truncate(desc, 76 - length)] }
|
562
|
+
info << colorize(:cyan, "Plans\n")
|
563
|
+
info << format_table(data, 2).to_s
|
564
|
+
info << "\n\n"
|
565
|
+
end
|
566
|
+
|
567
|
+
if metadata['operatingsystem_support']&.any?
|
568
|
+
supported = metadata['operatingsystem_support'].map do |os|
|
569
|
+
[os['operatingsystem'], os['operatingsystemrelease']&.join(', ')]
|
570
|
+
end
|
571
|
+
|
572
|
+
info << colorize(:cyan, "Operating system support\n")
|
573
|
+
info << format_table(supported, 2).to_s
|
574
|
+
info << "\n\n"
|
575
|
+
end
|
576
|
+
|
577
|
+
if metadata['dependencies']&.any?
|
578
|
+
dependencies = metadata['dependencies'].map do |dep|
|
579
|
+
[dep['name'], dep['version_requirement']]
|
580
|
+
end
|
581
|
+
|
582
|
+
info << colorize(:cyan, "Dependencies\n")
|
583
|
+
info << format_table(dependencies, 2).to_s
|
584
|
+
info << "\n\n"
|
585
|
+
end
|
586
|
+
|
587
|
+
info << colorize(:cyan, "Path\n")
|
588
|
+
info << if path.start_with?(Bolt::Config::Modulepath::MODULES_PATH) ||
|
589
|
+
path.start_with?(Bolt::Config::Modulepath::BOLTLIB_PATH)
|
590
|
+
indent(2, 'built-in module')
|
591
|
+
else
|
592
|
+
indent(2, path)
|
593
|
+
end
|
594
|
+
|
595
|
+
@stream.puts info
|
504
596
|
end
|
505
597
|
|
506
598
|
def print_plugin_list(plugin_list, modulepath)
|
@@ -661,6 +753,12 @@ module Bolt
|
|
661
753
|
print_container_result(value.result)
|
662
754
|
when Bolt::ResultSet
|
663
755
|
print_result_set(value)
|
756
|
+
when Bolt::Result
|
757
|
+
print_result(value)
|
758
|
+
when Bolt::ApplyResult
|
759
|
+
print_apply_result(value)
|
760
|
+
when Bolt::Error
|
761
|
+
print_bolt_error(**value.to_h.transform_keys(&:to_sym))
|
664
762
|
else
|
665
763
|
@stream.puts(::JSON.pretty_generate(plan_result, quirks_mode: true))
|
666
764
|
end
|
@@ -700,6 +798,17 @@ module Bolt
|
|
700
798
|
@stream.puts(colorize(:red, message))
|
701
799
|
end
|
702
800
|
|
801
|
+
def print_bolt_error(msg:, details:, **_kwargs)
|
802
|
+
err = msg
|
803
|
+
if (f = details[:file])
|
804
|
+
err += "\n (file: #{f}"
|
805
|
+
err += ", line: #{details[:line]}" if details[:line]
|
806
|
+
err += ", column: #{details[:column]}" if details[:column]
|
807
|
+
err += ")"
|
808
|
+
end
|
809
|
+
@stream.puts(colorize(:red, err))
|
810
|
+
end
|
811
|
+
|
703
812
|
def print_prompt(prompt)
|
704
813
|
@stream.print(colorize(:cyan, indent(4, prompt)))
|
705
814
|
end
|
data/lib/bolt/outputter/json.rb
CHANGED
@@ -23,6 +23,8 @@ module Bolt
|
|
23
23
|
print_result(event[:result])
|
24
24
|
when :message
|
25
25
|
print_message(event[:message])
|
26
|
+
when :verbose
|
27
|
+
print_message(event[:message]) if @verbose
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
@@ -45,6 +47,7 @@ module Bolt
|
|
45
47
|
@stream.puts results.to_json
|
46
48
|
end
|
47
49
|
alias print_module_list print_table
|
50
|
+
alias print_module_info print_table
|
48
51
|
|
49
52
|
def print_task_info(task)
|
50
53
|
path = task.files.first['path'].chomp("/tasks/#{task.files.first['name']}")
|
@@ -96,11 +99,8 @@ module Bolt
|
|
96
99
|
print_table('topics' => topics)
|
97
100
|
end
|
98
101
|
|
99
|
-
def print_guide(
|
100
|
-
@stream.puts(
|
101
|
-
'topic' => topic,
|
102
|
-
'guide' => guide
|
103
|
-
}.to_json)
|
102
|
+
def print_guide(**kwargs)
|
103
|
+
@stream.puts(kwargs.to_json)
|
104
104
|
end
|
105
105
|
|
106
106
|
def print_plan_lookup(value)
|
@@ -24,6 +24,8 @@ module Bolt
|
|
24
24
|
log_container_start(event)
|
25
25
|
when :container_finish
|
26
26
|
log_container_finish(event)
|
27
|
+
when :log, :message, :verbose
|
28
|
+
log_message(**event)
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
@@ -65,6 +67,10 @@ module Bolt
|
|
65
67
|
@logger.info("Finished: run container '#{result.object}' failed.")
|
66
68
|
end
|
67
69
|
end
|
70
|
+
|
71
|
+
def log_message(level:, message:, **_kwargs)
|
72
|
+
@logger.send(level, message)
|
73
|
+
end
|
68
74
|
end
|
69
75
|
end
|
70
76
|
end
|
data/lib/bolt/pal.rb
CHANGED
@@ -324,7 +324,7 @@ module Bolt
|
|
324
324
|
data['files'].each do |f|
|
325
325
|
# If any file has been updated since we last cached, update the
|
326
326
|
# cache
|
327
|
-
next
|
327
|
+
next unless file_modified?(f['path'], f['mtime'])
|
328
328
|
data = get_task_info(task_name, with_mtime: true)
|
329
329
|
data = Bolt::Util.walk_keys(data, &:to_s)
|
330
330
|
# Tell Bolt to write to the cache file once we're done
|
@@ -339,7 +339,7 @@ module Bolt
|
|
339
339
|
end
|
340
340
|
|
341
341
|
# Write the cache if any entries were updated
|
342
|
-
File.write(@project.task_cache_file, task_cache.to_json) if updated
|
342
|
+
File.write(@project.task_cache_file, task_cache.to_json) if updated && @project
|
343
343
|
filter_content ? filter_content(task_list, @project&.tasks) : task_list
|
344
344
|
end
|
345
345
|
|
@@ -423,13 +423,10 @@ module Bolt
|
|
423
423
|
|
424
424
|
plan_list = plan_names.each_with_object([]) do |plan_name, list|
|
425
425
|
data = plan_cache[plan_name] || get_plan_info(plan_name, with_mtime: true)
|
426
|
-
|
427
|
-
#
|
428
|
-
#
|
429
|
-
|
430
|
-
# new data.
|
431
|
-
if data['file'] &&
|
432
|
-
File.mtime(data.dig('file', 'path')).to_s != data.dig('file', 'mtime')
|
426
|
+
# If the plan is a 'local' plan (in the project itself, or the modules/
|
427
|
+
# directory) then verify it hasn't been updated since we cached it. If
|
428
|
+
# it has been updated, refresh the cache and use the new data.
|
429
|
+
if file_modified?(data.dig('file', 'path'), data.dig('file', 'mtime'))
|
433
430
|
data = get_plan_info(plan_name, with_mtime: true)
|
434
431
|
updated = true
|
435
432
|
plan_cache[plan_name] = data
|
@@ -438,11 +435,21 @@ module Bolt
|
|
438
435
|
list << [plan_name, data['description']] unless data['private']
|
439
436
|
end
|
440
437
|
|
441
|
-
File.write(@project.plan_cache_file, plan_cache.to_json) if updated
|
438
|
+
File.write(@project.plan_cache_file, plan_cache.to_json) if updated && @project
|
442
439
|
|
443
440
|
filter_content ? filter_content(plan_list, @project&.plans) : plan_list
|
444
441
|
end
|
445
442
|
|
443
|
+
# Returns true if a file has been modified or no longer exists, false
|
444
|
+
# otherwise.
|
445
|
+
#
|
446
|
+
# @param path [String] The path to the file.
|
447
|
+
# @param mtime [String] The last time the file was modified.
|
448
|
+
#
|
449
|
+
private def file_modified?(path, mtime)
|
450
|
+
path && !(File.exist?(path) && File.mtime(path).to_s == mtime.to_s)
|
451
|
+
end
|
452
|
+
|
446
453
|
def list_plans(filter_content: false)
|
447
454
|
in_bolt_compiler do |compiler|
|
448
455
|
errors = []
|
@@ -505,19 +512,14 @@ module Bolt
|
|
505
512
|
end
|
506
513
|
end
|
507
514
|
|
508
|
-
privie = plan.tag(:private)&.text
|
509
|
-
unless privie.nil? || %w[true false].include?(privie.downcase)
|
510
|
-
msg = "Plan #{plan_name} key 'private' must be a boolean, received: #{privie}"
|
511
|
-
raise Bolt::Error.new(msg, 'bolt/invalid-plan')
|
512
|
-
end
|
513
|
-
|
514
515
|
pp_info = {
|
515
516
|
'name' => plan_name,
|
516
517
|
'description' => description,
|
517
518
|
'parameters' => parameters,
|
518
|
-
'module' => mod
|
519
|
+
'module' => mod,
|
520
|
+
'private' => private_plan?(plan)
|
519
521
|
}
|
520
|
-
|
522
|
+
|
521
523
|
pp_info.merge!(get_plan_mtime(plan.file)) if with_mtime
|
522
524
|
pp_info
|
523
525
|
|
@@ -547,14 +549,39 @@ module Bolt
|
|
547
549
|
'name' => plan_name,
|
548
550
|
'description' => plan.description,
|
549
551
|
'parameters' => parameters,
|
550
|
-
'module' => mod
|
552
|
+
'module' => mod,
|
553
|
+
'private' => !!plan.private
|
551
554
|
}
|
552
|
-
|
555
|
+
|
553
556
|
yaml_info.merge!(get_plan_mtime(yaml_path)) if with_mtime
|
554
557
|
yaml_info
|
555
558
|
end
|
556
559
|
end
|
557
560
|
|
561
|
+
# Returns true if the plan is private, false otherwise.
|
562
|
+
#
|
563
|
+
# @param plan [PuppetStrings::Yard::CodeObjects::Plan] The puppet-strings plan documentation.
|
564
|
+
# @return [Boolean]
|
565
|
+
#
|
566
|
+
private def private_plan?(plan)
|
567
|
+
if plan.tag(:private)
|
568
|
+
value = plan.tag(:private).text
|
569
|
+
api_value = value.downcase == 'true' ? 'private' : 'public'
|
570
|
+
|
571
|
+
Bolt::Logger.deprecate(
|
572
|
+
'plan_private_tag',
|
573
|
+
"Tag '@private #{value}' in plan '#{plan.name}' is deprecated, use '@api #{api_value}' instead"
|
574
|
+
)
|
575
|
+
|
576
|
+
unless %w[true false].include?(plan.tag(:private).text.downcase)
|
577
|
+
msg = "Value for '@private' tag in plan '#{plan.name}' must be a boolean, received: #{value}"
|
578
|
+
raise Bolt::Error.new(msg, 'bolt/invalid-plan')
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
plan.tag(:api).text == 'private' || plan.tag(:private)&.text&.downcase == 'true'
|
583
|
+
end
|
584
|
+
|
558
585
|
def get_plan_mtime(path)
|
559
586
|
# If the plan is from the project modules/ directory, or is in the
|
560
587
|
# project itself, include the last mtime of the file so we can compare
|
@@ -628,6 +655,36 @@ module Bolt
|
|
628
655
|
end
|
629
656
|
end
|
630
657
|
|
658
|
+
# Return information about a module.
|
659
|
+
#
|
660
|
+
# @param name [String] The name of the module.
|
661
|
+
# @return [Hash]
|
662
|
+
#
|
663
|
+
def show_module(name)
|
664
|
+
name = name.tr('-', '/')
|
665
|
+
|
666
|
+
data = in_bolt_compiler do |_compiler|
|
667
|
+
mod = Puppet.lookup(:current_environment).module(name.split(%r{[/-]}, 2).last)
|
668
|
+
|
669
|
+
unless mod && (mod.forge_name == name || mod.name == name)
|
670
|
+
raise Bolt::Error.new("Could not find module '#{name}' on the modulepath.", 'bolt/unknown-module')
|
671
|
+
end
|
672
|
+
|
673
|
+
{
|
674
|
+
name: mod.forge_name || mod.name,
|
675
|
+
metadata: mod.metadata,
|
676
|
+
path: mod.path,
|
677
|
+
plans: mod.plans.map(&:name).sort,
|
678
|
+
tasks: mod.tasks.map(&:name).sort
|
679
|
+
}
|
680
|
+
end
|
681
|
+
|
682
|
+
data[:plans] = list_plans_with_cache.to_h.slice(*data[:plans]).to_a
|
683
|
+
data[:tasks] = list_tasks_with_cache.to_h.slice(*data[:tasks]).to_a
|
684
|
+
|
685
|
+
data
|
686
|
+
end
|
687
|
+
|
631
688
|
def generate_types(cache: false)
|
632
689
|
require 'puppet/face/generate'
|
633
690
|
in_bolt_compiler do
|
@@ -676,7 +733,10 @@ module Bolt
|
|
676
733
|
# Create a Fiber for the main plan. This will be run along with any
|
677
734
|
# other Fibers created during the plan run in the round_robin, with the
|
678
735
|
# main plan always taking precedence in being resumed.
|
679
|
-
|
736
|
+
#
|
737
|
+
# Every future except for the main plan needs to have a plan id in
|
738
|
+
# order to be tracked for the `wait()` function with no arguments.
|
739
|
+
future = executor.create_future(name: plan_name, plan_id: 1) do |_scope|
|
680
740
|
r = compiler.call_function('run_plan', plan_name, params.merge('_bolt_api_call' => true))
|
681
741
|
Bolt::PlanResult.from_pcore(r, 'success')
|
682
742
|
rescue Bolt::Error => e
|
@@ -13,6 +13,7 @@ module Bolt
|
|
13
13
|
download
|
14
14
|
eval
|
15
15
|
message
|
16
|
+
verbose
|
16
17
|
plan
|
17
18
|
resources
|
18
19
|
script
|
@@ -219,3 +220,4 @@ require 'bolt/pal/yaml_plan/step/task'
|
|
219
220
|
require 'bolt/pal/yaml_plan/step/upload'
|
220
221
|
require 'bolt/pal/yaml_plan/step/download'
|
221
222
|
require 'bolt/pal/yaml_plan/step/message'
|
223
|
+
require 'bolt/pal/yaml_plan/step/verbose'
|
@@ -24,14 +24,6 @@ module Bolt
|
|
24
24
|
private def function
|
25
25
|
'out::message'
|
26
26
|
end
|
27
|
-
|
28
|
-
# Transpiles the step into the plan language
|
29
|
-
#
|
30
|
-
def transpile
|
31
|
-
code = String.new(" ")
|
32
|
-
code << function_call(function, format_args(body))
|
33
|
-
code << "\n"
|
34
|
-
end
|
35
27
|
end
|
36
28
|
end
|
37
29
|
end
|