123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- ###
- # 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
|