You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							216 lines
						
					
					
						
							6.4 KiB
						
					
					
				
			
		
		
	
	
							216 lines
						
					
					
						
							6.4 KiB
						
					
					
				### | 
						|
# Omelette Simple Auto Completion for Node | 
						|
### | 
						|
{EventEmitter} = require "events" | 
						|
path           = require "path" | 
						|
fs             = require "fs" | 
						|
os             = require "os" | 
						|
 | 
						|
depthOf = (object) -> | 
						|
  level = 1 | 
						|
  for own key of object | 
						|
    if typeof object[key] is 'object' | 
						|
      depth = depthOf(object[key]) + 1 | 
						|
      level = Math.max(depth, level) | 
						|
  level | 
						|
 | 
						|
class Omelette extends EventEmitter | 
						|
 | 
						|
  {log} = console | 
						|
 | 
						|
  constructor: -> | 
						|
    @compgen  = process.argv.indexOf "--compgen" | 
						|
    @install  = process.argv.indexOf("--completion") > -1 | 
						|
    @installFish  = process.argv.indexOf("--completion-fish") > -1 | 
						|
    isZsh     = process.argv.indexOf("--compzsh") > -1 | 
						|
    isFish    = process.argv.indexOf("--compfish") > -1 | 
						|
    @isDebug  = process.argv.indexOf("--debug") > -1 | 
						|
 | 
						|
    @fragment = parseInt(process.argv[@compgen+1])-(if isZsh then 1 else 0) | 
						|
    @line     = process.argv[@compgen+3] | 
						|
    @word     = @line?.trim().split(/\s+/).pop() | 
						|
 | 
						|
    {@HOME, @SHELL} = process.env | 
						|
 | 
						|
  setProgram: (programs)-> | 
						|
    programs = programs.split '|' | 
						|
    [@program] = programs | 
						|
    @programs = programs.map (program)-> program.replace /// | 
						|
      [ | 
						|
       ^     # Do not allow except: | 
						|
        A-Z  # .. uppercase | 
						|
        a-z  # .. lowercase | 
						|
        0-9  # .. numbers | 
						|
        \.   # .. dots | 
						|
        \_   # .. underscores | 
						|
        \-   # .. dashes | 
						|
      ] | 
						|
    ///g, '' | 
						|
 | 
						|
  setFragments: (@fragments...)-> | 
						|
 | 
						|
  generate: -> | 
						|
    data = {before: @word, @fragment, @line, @reply} | 
						|
    @emit "complete", @fragments[@fragment-1], data | 
						|
    @emit @fragments[@fragment-1], data | 
						|
    @emit "$#{@fragment}", data | 
						|
    process.exit() | 
						|
 | 
						|
  reply: (words=[])-> | 
						|
    console.log words.join? os.EOL | 
						|
    process.exit() | 
						|
 | 
						|
  tree: (objectTree={})-> | 
						|
    depth = depthOf objectTree | 
						|
    for level in [1..depth] | 
						|
      @on "$#{level}", ({ fragment, reply, line })-> | 
						|
        accessor = new Function '_', """ | 
						|
          return _['#{line.split(/\s+/).slice(1).filter(Boolean).join("']['")}'] | 
						|
        """ | 
						|
        replies = if fragment is 1 then Object.keys(objectTree) else accessor(objectTree) | 
						|
        reply do (replies = replies)-> | 
						|
          return replies() if replies instanceof Function | 
						|
          return replies if replies instanceof Array | 
						|
          return Object.keys(replies) if replies instanceof Object | 
						|
    this | 
						|
 | 
						|
  generateCompletionCode: -> | 
						|
    completions = @programs.map (program)=> | 
						|
      completion = "_#{program}_completion" | 
						|
      """ | 
						|
      ### #{program} completion - begin. generated by omelette.js ### | 
						|
      if type compdef &>/dev/null; then | 
						|
        #{completion}() { | 
						|
          compadd -- `#{@program} --compzsh --compgen "${CURRENT}" "${words[CURRENT-1]}" "${BUFFER}"` | 
						|
        } | 
						|
        compdef #{completion} #{program} | 
						|
      elif type complete &>/dev/null; then | 
						|
        #{completion}() { | 
						|
          local cur prev nb_colon | 
						|
          _get_comp_words_by_ref -n : cur prev | 
						|
          nb_colon=$(grep -o ":" <<< "$COMP_LINE" | wc -l) | 
						|
 | 
						|
          COMPREPLY=( $(compgen -W '$(#{@program} --compbash --compgen "$((COMP_CWORD - (nb_colon * 2)))" "$prev" "${COMP_LINE}")' -- "$cur") ) | 
						|
 | 
						|
          __ltrim_colon_completions "$cur" | 
						|
        } | 
						|
        complete -F #{completion} #{program} | 
						|
      fi | 
						|
      ### #{program} completion - end ### | 
						|
      """ | 
						|
 | 
						|
    # Adding aliases for testing purposes | 
						|
    completions.push @generateTestAliases() if @isDebug | 
						|
    completions.join os.EOL | 
						|
 | 
						|
  generateCompletionCodeFish: -> | 
						|
    completions = @programs.map (program)=> | 
						|
      completion = "_#{program}_completion" | 
						|
      """ | 
						|
      ### #{program} completion - begin. generated by omelette.js ### | 
						|
      function #{completion} | 
						|
        #{@program} --compfish --compgen (count (commandline -poc)) (commandline -pt) (commandline -pb) | 
						|
      end | 
						|
      complete -f -c #{program} -a '(#{completion})' | 
						|
      ### #{program} completion - end ### | 
						|
      """ | 
						|
 | 
						|
    # Adding aliases for testing purposes | 
						|
    completions.push @generateTestAliases() if @isDebug | 
						|
    completions.join os.EOL | 
						|
 | 
						|
  generateTestAliases: -> | 
						|
    fullPath = path.join process.cwd(), @program | 
						|
    debugAliases   = @programs.map((program)-> "  alias #{program}=#{fullPath}").join os.EOL | 
						|
    debugUnaliases = @programs.map((program)-> "  unalias #{program}").join os.EOL | 
						|
 | 
						|
    """ | 
						|
    ### test method ### | 
						|
    omelette-debug-#{@program}() { | 
						|
    #{debugAliases} | 
						|
    } | 
						|
    omelette-nodebug-#{@program}() { | 
						|
    #{debugUnaliases} | 
						|
    } | 
						|
    ### tests ### | 
						|
    """ | 
						|
 | 
						|
  checkInstall: -> | 
						|
    if @install | 
						|
      log @generateCompletionCode() | 
						|
      process.exit() | 
						|
    if @installFish | 
						|
      log @generateCompletionCodeFish() | 
						|
      process.exit() | 
						|
 | 
						|
  getActiveShell: -> | 
						|
    {SHELL} = process.env | 
						|
    if SHELL.match /bash/      then 'bash' | 
						|
    else if SHELL.match /zsh/  then 'zsh' | 
						|
    else if SHELL.match /fish/ then 'fish' | 
						|
 | 
						|
  getDefaultShellInitFile: -> | 
						|
 | 
						|
    fileAt = (root)-> | 
						|
      (file)-> path.join root, file | 
						|
 | 
						|
    fileAtHome = fileAt @HOME | 
						|
 | 
						|
    switch @shell = @getActiveShell() | 
						|
      when 'bash'  then fileAtHome '.bash_profile' | 
						|
      when 'zsh'   then fileAtHome '.zshrc' | 
						|
      when 'fish'  then fileAtHome '.config/fish/config.fish' | 
						|
 | 
						|
  setupShellInitFile: (initFile=@getDefaultShellInitFile())-> | 
						|
 | 
						|
    template = (command)=> | 
						|
      """ | 
						|
 | 
						|
      # begin #{@program} completion | 
						|
      #{command} | 
						|
      # end #{@program} completion | 
						|
 | 
						|
      """ | 
						|
 | 
						|
    switch @shell | 
						|
      when 'bash' | 
						|
        programFolder = path.join @HOME, ".#{@program}" | 
						|
        completionPath = path.join programFolder, 'completion.sh' | 
						|
 | 
						|
        fs.mkdirSync programFolder unless fs.existsSync programFolder | 
						|
        fs.writeFileSync completionPath, @generateCompletionCode() | 
						|
        fs.appendFileSync initFile, template "source #{completionPath}" | 
						|
 | 
						|
      when 'zsh' | 
						|
        fs.appendFileSync initFile, template ". <(#{@program} --completion)" | 
						|
 | 
						|
      when 'fish' | 
						|
        fs.appendFileSync initFile, template "#{@program} --completion-fish | source" | 
						|
 | 
						|
    process.exit(); | 
						|
 | 
						|
  init: -> | 
						|
    do @generate if @compgen > -1 | 
						|
 | 
						|
module.exports = (template, args...)-> | 
						|
  if template instanceof Array and args.length > 0 | 
						|
    [program, callbacks] = [template[0].trim(), args] | 
						|
    fragments = callbacks.map (callback, index) -> "arg#{index}" | 
						|
  else | 
						|
    [program, fragments...] = template.split /\s+/ | 
						|
    callbacks = [] | 
						|
 | 
						|
  fragments = fragments.map (fragment)-> fragment.replace /^\<+|\>+$/g, '' | 
						|
  _omelette = new Omelette | 
						|
  _omelette.setProgram program | 
						|
  _omelette.setFragments fragments... | 
						|
  _omelette.checkInstall() | 
						|
  for callback, index in callbacks | 
						|
    fragment = "arg#{index}" | 
						|
    do (callback = callback)-> | 
						|
      _omelette.on fragment, (args...)-> | 
						|
        @reply if callback instanceof Array | 
						|
          callback | 
						|
        else | 
						|
          callback args... | 
						|
  _omelette
 | 
						|
 |