lightning 0.2.1 → 0.3.0

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 (43) hide show
  1. data/CHANGELOG.rdoc +9 -0
  2. data/README.rdoc +53 -125
  3. data/Rakefile +14 -40
  4. data/bin/lightning +4 -0
  5. data/bin/lightning-complete +1 -10
  6. data/bin/lightning-translate +4 -0
  7. data/lib/lightning.rb +36 -50
  8. data/lib/lightning/bolt.rb +53 -26
  9. data/lib/lightning/builder.rb +87 -0
  10. data/lib/lightning/commands.rb +92 -69
  11. data/lib/lightning/commands/bolt.rb +63 -0
  12. data/lib/lightning/commands/core.rb +57 -0
  13. data/lib/lightning/commands/function.rb +76 -0
  14. data/lib/lightning/commands/shell_command.rb +38 -0
  15. data/lib/lightning/commands_util.rb +75 -0
  16. data/lib/lightning/completion.rb +72 -28
  17. data/lib/lightning/completion_map.rb +42 -39
  18. data/lib/lightning/config.rb +92 -57
  19. data/lib/lightning/function.rb +70 -0
  20. data/lib/lightning/generator.rb +77 -43
  21. data/lib/lightning/generators.rb +53 -0
  22. data/lib/lightning/generators/misc.rb +12 -0
  23. data/lib/lightning/generators/ruby.rb +32 -0
  24. data/lib/lightning/util.rb +70 -0
  25. data/lib/lightning/version.rb +3 -0
  26. data/test/bolt_test.rb +16 -28
  27. data/test/builder_test.rb +54 -0
  28. data/test/commands_test.rb +98 -0
  29. data/test/completion_map_test.rb +31 -54
  30. data/test/completion_test.rb +106 -36
  31. data/test/config_test.rb +22 -56
  32. data/test/function_test.rb +90 -0
  33. data/test/generator_test.rb +73 -0
  34. data/test/lightning.yml +26 -34
  35. data/test/test_helper.rb +80 -15
  36. metadata +42 -20
  37. data/VERSION.yml +0 -4
  38. data/bin/lightning-full_path +0 -18
  39. data/bin/lightning-install +0 -7
  40. data/lib/lightning/bolts.rb +0 -12
  41. data/lightning.yml.example +0 -87
  42. data/lightning_completions.example +0 -147
  43. data/test/lightning_test.rb +0 -58
@@ -0,0 +1,87 @@
1
+ module Lightning
2
+ # Builds shell file ~/.lightning/functions.sh from the config file. This file
3
+ # is built and sourced into a user's shell using `lightning-reload`.
4
+ # Currently supports bash and zsh shells. Defaults to building for bash.
5
+ module Builder
6
+ extend self
7
+
8
+ # @private
9
+ HEADER = <<-INIT.gsub(/^\s{4}/,'')
10
+ #### This file was built by lightning. ####
11
+ #LBIN_PATH="$PWD/bin/" #only use for development
12
+ LBIN_PATH=""
13
+
14
+ lightning-reload() {
15
+ lightning install $@
16
+ source_file=$(lightning source_file)
17
+ source $source_file
18
+ echo Loaded $source_file
19
+ }
20
+ INIT
21
+
22
+ # @return [Boolean] Determines if Builder can build a file for its current shell
23
+ def can_build?
24
+ respond_to? "#{shell}_builder"
25
+ end
26
+
27
+ # @return [String] Current shell, defaults to 'bash'
28
+ def shell
29
+ Lightning.config[:shell] || 'bash'
30
+ end
31
+
32
+ # @return [String] Builds shell file
33
+ def run
34
+ return puts("No builder exists for #{Builder.shell} shell") unless Builder.can_build?
35
+ functions = Lightning.functions.values
36
+ check_for_existing_commands(functions)
37
+ output = build(functions)
38
+ File.open(Lightning.config.source_file, 'w') {|f| f.write(output) }
39
+ output
40
+ end
41
+
42
+ # @param [Array] Function objects
43
+ # @return [String] Shell file string to be saved and sourced
44
+ def build(args)
45
+ HEADER + "\n\n" + send("#{shell}_builder", *[args])
46
+ end
47
+
48
+ protected
49
+ def bash_builder(functions)
50
+ functions.map do |e|
51
+ <<-EOS.gsub(/^\s{10}/,'')
52
+ #{e.name} () {
53
+ local IFS=$'\\n'
54
+ local arr=( $(${LBIN_PATH}lightning-translate #{e.name} $@) )
55
+ #{e.shell_command} "${arr[@]}"
56
+ }
57
+ complete -o default -C "${LBIN_PATH}lightning-complete #{e.name}" #{e.name}
58
+ EOS
59
+ end.join("\n")
60
+ end
61
+
62
+ def zsh_builder(functions)
63
+ functions.map do |e|
64
+ <<-EOS.gsub(/^\s{10}/,'')
65
+ #{e.name} () {
66
+ local IFS=$'\\n'
67
+ local arr
68
+ arr=( $(${LBIN_PATH}lightning-translate #{e.name} $@) )
69
+ #{e.shell_command} "${arr[@]}"
70
+ }
71
+ _#{e.name} () {
72
+ local IFS=$'\\n'
73
+ reply=( $(${LBIN_PATH}lightning-complete #{e.name} "${@}") )
74
+ }
75
+ compctl -QK _#{e.name} #{e.name}
76
+ EOS
77
+ end.join("\n")
78
+ end
79
+
80
+ def check_for_existing_commands(functions)
81
+ if !(existing_commands = functions.select {|e| Util.shell_command_exists?(e.name) }).empty?
82
+ puts "The following commands already exist in $PATH and are being generated: "+
83
+ "#{existing_commands.map {|e| e.name}.join(', ')}"
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,92 +1,115 @@
1
- class Lightning
2
- # TODO: Provide commands for the lightning binary.
1
+ module Lightning
2
+ # Runs lightning commands which are methods in this namespace.
3
+ #
4
+ # == Command Basics
5
+ # To get a list of commands and their description: +lightning -h+. To get usage and description on
6
+ # a command +lightning COMMAND -h+ i.e +lightning bolt -h+. Any command and subcommand can be abbreviated.
7
+ # For example, +lightning b c gem path1+ is short for +lightning bolt create gem path1+.
8
+ #
9
+ # == Command Plugins
10
+ # Command plugins are a way for users to define their own lightning commands.
11
+ # A command plugin is a .rb file in ~/.lightning/commands/. Each plugin can have multiple
12
+ # commands since a command is just a method in Lightning::Commands.
13
+ #
14
+ # A sample command plugin looks like this:
15
+ # module Lightning::Commands
16
+ # desc 'COMMAND', 'Prints hello'
17
+ # def hello(argv)
18
+ # puts "Hello with #{argv.size} arguments"
19
+ # end
20
+ # end
21
+ #
22
+ # To register a command, {Commands#desc desc} must be placed before a method, describing the command's
23
+ # usage and description. Note that a command receives commandline arguments as an array. See
24
+ # {CommandsUtil} for helper methods to be used inside a command.
25
+ #
26
+ # For command plugin examples
27
+ # {read the source}[http://github.com/cldwalker/lightning/tree/master/lib/lightning/commands/].
3
28
  module Commands
4
- # @options :search=>:string, :command=>:string
5
- def lightning_command(*args)
6
- puts "Lightning Commands"
7
- if options[:command]
8
- lightning_commands = Lightning.config[:commands].select {|e| e['map_to'] == options['command']}
29
+ @desc = {}
30
+ extend self
31
+ extend CommandsUtil
32
+
33
+ # Called by `lightning` to call proper lightning command, print help or print version
34
+ def run(argv=ARGV)
35
+ if (command = argv.shift) && (actual_command = unalias_command(command))
36
+ run_command(actual_command, argv)
37
+ elsif command && respond_to?(command)
38
+ run_command(command, argv)
39
+ elsif %w{-v --version}.include?(command)
40
+ puts "lightning #{VERSION}"
9
41
  else
10
- lightning_commands = Lightning.config[:commands]
42
+ load_user_commands
43
+ puts "Command '#{command}' not found.","\n" if command && !%w{-h --help}.include?(command)
44
+ print_help
11
45
  end
12
- puts lightning_commands.map {|e| e['name'] }.join("\n")
13
46
  end
14
47
 
15
- #@options :add=>:boolean, :delete=>:boolean
16
- def path(*args)
17
- command_name = args.shift
18
- if options['delete']
19
- return puts("Path needed") if args[0].nil?
20
- delete_path(command_name, args[0])
21
- elsif options['add']
22
- return puts("Path needed") if args[0].nil?
23
- add_path(command_name, args[0])
24
- else
25
- puts "Paths for command '#{command_name}'"
26
- puts config_command_paths(command_name).join("\n")
27
- end
48
+ # @return [Array] Available lightning commands
49
+ def commands
50
+ @desc.keys
28
51
  end
29
52
 
30
- #@options :delete=>:boolean, :add=>:boolean, :global=>:boolean
31
- def alias(*args)
32
- if options['delete']
33
- delete_command_alias(*args.slice(0,2))
34
- elsif options['add']
35
- add_command_alias(*args.slice(0,3))
53
+ # Calls proper lightning command with remaining commandline arguments
54
+ def run_command(command, args)
55
+ @command = command.to_s
56
+ if %w{-h --help}.include?(args[0])
57
+ print_command_help
36
58
  else
37
- puts "Aliases"
38
- (Lightning.config[:aliases] || []).each do |path_alias, path|
39
- puts "#{path_alias} : #{path}"
40
- end
59
+ send(command, args)
41
60
  end
61
+ rescue StandardError
62
+ $stderr.puts "Error: "+ $!.message
63
+ end
64
+
65
+ # @return [String] Command usage for current command
66
+ def command_usage
67
+ "Usage: lightning #{@command} #{desc_array[0]}"
68
+ end
69
+
70
+ # Place before a command method to set its usage and description
71
+ def desc(*args)
72
+ @next_desc = args
42
73
  end
43
74
 
44
75
  private
45
- def config_command_paths(name)
46
- if command = Lightning.config_command(name, true)
47
- if command['paths'].is_a?(String)
48
- Lightning.config[:paths][command['paths']]
49
- else
50
- command['paths']
51
- end
52
- else
53
- []
54
- end
76
+ def print_command_help
77
+ puts [command_usage, '', desc_array[1]]
55
78
  end
56
79
 
57
- def add_command_alias(command_name, path_alias, path)
58
- if (command = Lightning.config_command(command_name, true))
59
- path = File.expand_path(path)
60
- command['aliases'][path_alias] = path
61
- Lightning.config.save
62
- puts "Path alias '#{path_alias}' added"
63
- end
80
+ def print_help
81
+ puts "lightning COMMAND [arguments]", ""
82
+ puts "Available commands:"
83
+ print_sorted_hash @desc.inject({}) {|a,(k,v)| a[k] = v[1]; a }, true
84
+ puts "", "For more information on a command use:"
85
+ puts " lightning COMMAND -h", ""
86
+ puts "Options: "
87
+ puts " -h, --help Show this help and exit"
88
+ puts " -v, --version Print current version and exit"
89
+ puts "", "Commands and subcommands can be abbreviated."
90
+ puts "For example, 'lightning b c gem path1' is short for 'lightning bolt create gem path1'."
64
91
  end
65
92
 
66
- def delete_command_alias(command_name, path_alias)
67
- if (command = Lightning.config_command(command_name, true))
68
- command['aliases'].delete(path_alias)
69
- Lightning.config.save
70
- puts "Path alias '#{path_alias}' deleted"
71
- end
93
+ def desc_array
94
+ Array(@desc[@command])
72
95
  end
73
96
 
74
- def add_path(command_name, path)
75
- if (command = Lightning.config_command(command_name, true))
76
- path = File.expand_path(path)
77
- config_command_paths(command_name) << path
78
- Lightning.config.save
79
- puts "Path '#{path}' added"
80
- end
97
+ def unalias_command(command)
98
+ actual_command = commands.sort.find {|e| e[/^#{command}/] }
99
+ # don't load plugin commands for faster completion/translation
100
+ load_user_commands unless %w{translate complete}.include?(actual_command)
101
+ actual_command || commands.sort.find {|e| e[/^#{command}/] }
81
102
  end
82
103
 
83
- def delete_path(command_name, path)
84
- if (command = Lightning.config_command(command_name, true))
85
- path = File.expand_path(path)
86
- config_command_paths(command_name).delete(path)
87
- Lightning.config.save
88
- puts "Path '#{path}' deleted"
89
- end
104
+ def load_user_commands
105
+ @load_user_commands ||= Util.load_plugins(Lightning.dir, 'commands') || true
106
+ end
107
+
108
+ def method_added(meth)
109
+ @desc[meth.to_s] = @next_desc if @next_desc
110
+ @next_desc = nil
90
111
  end
91
112
  end
92
113
  end
114
+
115
+ Lightning::Util.load_plugins File.dirname(__FILE__), 'commands'
@@ -0,0 +1,63 @@
1
+ module Lightning::Commands
2
+ protected
3
+ desc "(list [-a|--alias] | alias BOLT ALIAS | create BOLT GLOBS | delete BOLT |\n#{' '*22} "+
4
+ "generate BOLT [generator] [-t|--test] | global ON_OR_OFF BOLTS | show BOLT)",
5
+ "Commands for managing bolts. Defaults to listing them."
6
+ def bolt(argv)
7
+ subcommand = argv.shift || 'list'
8
+ subcommand = %w{alias create delete generate global list show}.find {|e| e[/^#{subcommand}/]} || subcommand
9
+ bolt_subcommand(subcommand, argv) if subcommand_has_required_args(subcommand, argv)
10
+ end
11
+
12
+ def bolt_subcommand(subcommand, argv)
13
+ case subcommand
14
+ when 'list' then list_subcommand(:bolts, argv)
15
+ when 'create' then create_bolt(argv.shift, argv)
16
+ when 'alias' then alias_bolt(argv[0], argv[1])
17
+ when 'delete' then delete_bolt(argv[0])
18
+ when 'show' then show_bolt(argv[0])
19
+ when 'global' then globalize_bolts(argv.shift, argv)
20
+ when 'generate' then generate_bolt(argv)
21
+ else puts "Invalid subcommand '#{subcommand}'", command_usage
22
+ end
23
+ end
24
+
25
+ def generate_bolt(argv)
26
+ args, options = parse_args argv
27
+ Lightning::Generator.run(args[0], :once=>args[1], :test=>options[:test] || options[:t])
28
+ end
29
+
30
+ def create_bolt(bolt, globs)
31
+ config.bolts[bolt] = Lightning::Config.bolt(globs)
32
+ save_and_say "Created bolt '#{bolt}'"
33
+ end
34
+
35
+ def alias_bolt(bolt, bolt_alias)
36
+ if_bolt_found(bolt) do |bolt|
37
+ config.bolts[bolt]['alias'] = bolt_alias
38
+ save_and_say "Aliased bolt '#{bolt}' to '#{bolt_alias}'"
39
+ end
40
+ end
41
+
42
+ def delete_bolt(bolt)
43
+ if_bolt_found(bolt) do |bolt|
44
+ config.bolts.delete(bolt)
45
+ save_and_say "Deleted bolt '#{bolt}' and its functions"
46
+ end
47
+ end
48
+
49
+ def show_bolt(bolt)
50
+ if_bolt_found(bolt) {|b| puts config.bolts[b].to_yaml.sub("--- \n", '') }
51
+ end
52
+
53
+ def globalize_bolts(boolean, arr)
54
+ return puts("First argument must be 'on' or 'off'") unless %w{on off}.include?(boolean)
55
+ if boolean == 'on'
56
+ valid = arr.select {|b| if_bolt_found(b) {|bolt| config.bolts[bolt]['global'] = true } }
57
+ save_and_say "Global on for bolts #{valid.join(', ')}" unless valid.empty?
58
+ else
59
+ valid = arr.select {|b| if_bolt_found(b) {|bolt| config.bolts[bolt].delete('global') ; true } }
60
+ save_and_say "Global off for bolts #{valid.join(', ')}" unless valid.empty?
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,57 @@
1
+ module Lightning
2
+ module Commands
3
+ # Silent command used by `lightning-reload` which prints {Builder}'s shell file
4
+ def source_file(argv)
5
+ puts config.source_file
6
+ end
7
+
8
+ protected
9
+ desc "FUNCTION [arguments]", "Prints a function's completions based on the last argument."
10
+ def complete(argv)
11
+ return unless command_has_required_args(argv, 1)
12
+ # this arg is needed by zsh in Complete
13
+ function = argv[0]
14
+ # bash hack: read ENV here because passing $COMP_LINE from the shell
15
+ # is a different incorrect buffer
16
+ buffer = ENV["COMP_LINE"] || argv.join(' ')
17
+ # zsh hack: when tabbing on blank space $@ is empty
18
+ # this ensures all completions
19
+ buffer += " " if argv.size == 1
20
+
21
+ puts Completion.complete(buffer, Lightning.functions[function])
22
+ end
23
+
24
+ desc "FUNCTION ARGUMENTS", "Translates each argument and prints it on a separate line."
25
+ def translate(argv)
26
+ return unless command_has_required_args(argv, 2)
27
+ translations = (fn = Lightning.functions[argv.shift]) ?
28
+ fn.translate(argv).join("\n") : '#Error-no_function_found_to_translate'
29
+ puts translations
30
+ end
31
+
32
+ desc "[--file=FILE] [--shell=SHELL]",
33
+ "Builds shell functions and installs them into FILE to be sourced by shell."
34
+ def install(argv)
35
+ args, options = parse_args(argv)
36
+ config[:shell] = options[:shell] if options[:shell]
37
+ config.source_file = options[:file] if options[:file]
38
+
39
+ if first_install?
40
+ Generator.run
41
+ puts "Created ~/.lightningrc"
42
+ config.bolts.each {|k,v| v['global'] = true }
43
+ config.save
44
+ end
45
+
46
+ Builder.run
47
+ puts "Created #{config.source_file}"+ (first_install? ? " for #{Builder.shell}" : '')
48
+ end
49
+
50
+ def first_install?; !File.exists?(Config.config_file); end
51
+
52
+ desc '', 'Lists available generators.'
53
+ def generator(argv)
54
+ print_sorted_hash Generator.generators
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,76 @@
1
+ module Lightning::Commands
2
+ protected
3
+ desc '(list [--command=SHELL_COMMAND] [--bolt=BOLT] | create SHELL_COMMAND BOLT [function] | delete FUNCTION)',
4
+ 'Commands for managing functions. Defaults to listing them.'
5
+ def function(argv)
6
+ subcommand = argv.shift || 'list'
7
+ subcommand = %w{create delete list}.find {|e| e[/^#{subcommand}/]} || subcommand
8
+ function_subcommand(subcommand, argv) if subcommand_has_required_args(subcommand, argv)
9
+ end
10
+
11
+ def function_subcommand(subcommand, argv)
12
+ case subcommand
13
+ when 'list' then list_function(argv)
14
+ when 'create' then create_function_and_bolt(argv)
15
+ when 'delete' then delete_function argv.shift
16
+ else puts "Invalid subcommand '#{subcommand}'", command_usage
17
+ end
18
+ end
19
+
20
+ def list_function(argv)
21
+ args, options = parse_args argv
22
+ functions = if options[:bolt]
23
+ Lightning.functions.values.select {|e| e.bolt.name == options[:bolt] }.map {|e| e.name}
24
+ elsif options[:command]
25
+ Lightning.functions.values.select {|e| e.shell_command == options[:command] }.map {|e| e.name}
26
+ else
27
+ Lightning.functions.keys
28
+ end
29
+ puts functions.sort
30
+ end
31
+
32
+ def create_function_and_bolt(argv)
33
+ bolt = config.unalias_bolt(argv[1])
34
+ (config.bolts[bolt] || Lightning::Generator.run(bolt, :once=>bolt)) &&
35
+ create_function(argv[0], bolt, :name=>argv[2])
36
+ end
37
+
38
+ def create_function(scmd, bolt, options={})
39
+ options[:name] ||= Lightning.bolts[bolt].function_name(scmd) unless global_commands.include?(scmd)
40
+ function = options[:name] || Lightning.bolts[bolt].function_name(scmd)
41
+
42
+ if find_function(config.bolts[bolt]['functions'], 'shell_command', scmd)
43
+ puts("Function '#{function}' already exists")
44
+ else
45
+ if_bolt_found(bolt) do |bolt|
46
+ fn = options[:name] ? {'name'=>options[:name],'shell_command'=>scmd} : scmd
47
+ (config.bolts[bolt]['functions'] ||= []) << fn
48
+ save_and_say "Created function '#{function}'"
49
+ end
50
+ end
51
+ end
52
+
53
+ def find_function(functions, attribute, query)
54
+ Array(functions).find {|e|
55
+ attribute != 'shell_command' ? (e.is_a?(Hash) && e[attribute] == query) :
56
+ config.only_command(e.is_a?(Hash) ? e['shell_command'] : e) == query
57
+ }
58
+ end
59
+
60
+ def delete_function(fn)
61
+ if (function = Lightning.functions[fn])
62
+ cmd = config.unaliased_command(fn.split('-')[0])
63
+ if_bolt_found(function.bolt.name) {|bolt|
64
+ if (element = find_function(config.bolts[bolt]['functions'], 'name', fn) ||
65
+ find_function(config.bolts[bolt]['functions'], 'shell_command', cmd) )
66
+ config.bolts[bolt]['functions'].delete(element)
67
+ return save_and_say("Deleted function '#{fn}'")
68
+ elsif function.bolt.global && config.global_commands.include?(cmd)
69
+ return puts("Can't delete function '#{fn}' which is generated by a shell command '#{cmd}'.",
70
+ "Can only delete by deleting shell command: shell_command delete #{cmd}")
71
+ end
72
+ }
73
+ end
74
+ puts "Can't find function '#{fn}'"
75
+ end
76
+ end