lightning 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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