Add Executable class to abstract CLI beautifiers
This commit is contained in:
parent
800066963c
commit
bc21afd424
|
@ -157,6 +157,8 @@ Handlebars.registerHelper('beautifiers-info', (beautifiers, options) ->
|
|||
rows = _.map(beautifiers, (beautifier, k) ->
|
||||
name = beautifier.name
|
||||
isPreInstalled = beautifier.isPreInstalled
|
||||
if typeof isPreInstalled is "function"
|
||||
isPreInstalled = beautifier.isPreInstalled()
|
||||
link = beautifier.link
|
||||
installationInstructions = if isPreInstalled then "Nothing!" else "Go to #{link} and follow the instructions."
|
||||
return "| #{name} | #{if isPreInstalled then ':white_check_mark:' else ':x:'} | #{installationInstructions} |"
|
||||
|
|
|
@ -179,6 +179,8 @@
|
|||
"pug-beautify": "^0.1.1",
|
||||
"remark": "^6.0.1",
|
||||
"season": "^5.3.0",
|
||||
"semver": "^5.3.0",
|
||||
"shell-env": "^0.3.0",
|
||||
"space-pen": "^5.1.1",
|
||||
"strip-json-comments": "^2.0.1",
|
||||
"temp": "^0.8.3",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
Beautifiers = require "../src/beautifiers"
|
||||
Executable = require "../src/beautifiers/executable"
|
||||
beautifiers = new Beautifiers()
|
||||
Beautifier = require "../src/beautifiers/beautifier"
|
||||
Languages = require('../src/languages/')
|
||||
|
@ -124,7 +125,7 @@ describe "Atom-Beautify", ->
|
|||
pathOption: "Lang - Test Program Path"
|
||||
}
|
||||
# Force to be Windows
|
||||
beautifier.isWindows = true
|
||||
Executable.isWindows = () ->true
|
||||
terminal = 'CMD prompt'
|
||||
whichCmd = "where.exe"
|
||||
# Process
|
||||
|
@ -132,7 +133,7 @@ describe "Atom-Beautify", ->
|
|||
expect(p).not.toBe(null)
|
||||
expect(p instanceof beautifier.Promise).toBe(true)
|
||||
cb = (v) ->
|
||||
# console.log(v)
|
||||
console.log("error", v, v.description)
|
||||
expect(v).not.toBe(null)
|
||||
expect(v instanceof Error).toBe(true)
|
||||
expect(v.code).toBe("CommandNotFound")
|
||||
|
@ -167,7 +168,7 @@ describe "Atom-Beautify", ->
|
|||
pathOption: "Lang - Test Program Path"
|
||||
}
|
||||
# Force to be Mac/Linux (not Windows)
|
||||
beautifier.isWindows = false
|
||||
Executable.isWindows = () ->false
|
||||
terminal = "Terminal"
|
||||
whichCmd = "which"
|
||||
# Process
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
PHPCSFixer = require "../src/beautifiers/php-cs-fixer"
|
||||
Beautifier = require "../src/beautifiers/beautifier"
|
||||
Executable = require "../src/beautifiers/executable"
|
||||
path = require 'path'
|
||||
|
||||
# Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs.
|
||||
|
@ -30,10 +31,15 @@ describe "PHP-CS-Fixer Beautifier", ->
|
|||
describe "Beautifier::beautify", ->
|
||||
|
||||
beautifier = null
|
||||
execSpawn = null
|
||||
|
||||
beforeEach ->
|
||||
beautifier = new PHPCSFixer()
|
||||
# console.log('new beautifier')
|
||||
execSpawn = Executable.prototype.spawn
|
||||
|
||||
afterEach ->
|
||||
Executable.prototype.spawn = execSpawn
|
||||
|
||||
OSSpecificSpecs = ->
|
||||
text = "<?php echo \"test\"; ?>"
|
||||
|
@ -49,13 +55,14 @@ describe "PHP-CS-Fixer Beautifier", ->
|
|||
levels: ""
|
||||
}
|
||||
# Mock spawn
|
||||
beautifier.spawn = (exe, args, options) ->
|
||||
# beautifier.spawn
|
||||
Executable.prototype.spawn = (exe, args, options) ->
|
||||
# console.log('spawn', exe, args, options)
|
||||
er = new Error('ENOENT')
|
||||
er.code = 'ENOENT'
|
||||
return beautifier.Promise.reject(er)
|
||||
# Beautify
|
||||
p = beautifier.beautify(text, language, options)
|
||||
p = beautifier.loadExecutables().then(() -> beautifier.beautify(text, language, options))
|
||||
expect(p).not.toBe(null)
|
||||
expect(p instanceof beautifier.Promise).toBe(true)
|
||||
cb = (v) ->
|
||||
|
@ -74,7 +81,7 @@ describe "PHP-CS-Fixer Beautifier", ->
|
|||
expect(beautifier).not.toBe(null)
|
||||
expect(beautifier instanceof Beautifier).toBe(true)
|
||||
|
||||
if not beautifier.isWindows and failingProgram is "php"
|
||||
if not Executable.isWindows and failingProgram is "php"
|
||||
# Only applicable on Windows
|
||||
return
|
||||
|
||||
|
@ -104,8 +111,9 @@ describe "PHP-CS-Fixer Beautifier", ->
|
|||
# console.log('fake exe path', exe)
|
||||
beautifier.Promise.resolve("/#{exe}")
|
||||
|
||||
oldSpawn = beautifier.spawn.bind(beautifier)
|
||||
beautifier.spawn = (exe, args, options) ->
|
||||
# oldSpawn = beautifier.spawn.bind(beautifier)
|
||||
# beautifier.spawn
|
||||
Executable.prototype.spawn = (exe, args, options) ->
|
||||
# console.log('spawn', exe, args, options)
|
||||
if exe is failingProgram
|
||||
er = new Error('ENOENT')
|
||||
|
@ -117,21 +125,21 @@ describe "PHP-CS-Fixer Beautifier", ->
|
|||
stdout: 'stdout',
|
||||
stderr: ''
|
||||
})
|
||||
p = beautifier.beautify(text, language, options)
|
||||
p = beautifier.loadExecutables().then(() -> beautifier.beautify(text, language, options))
|
||||
expect(p).not.toBe(null)
|
||||
expect(p instanceof beautifier.Promise).toBe(true)
|
||||
p.then(cb, cb)
|
||||
return p
|
||||
|
||||
# failWhichProgram('php')
|
||||
failWhichProgram('php-cs-fixer')
|
||||
failWhichProgram('PHP')
|
||||
# failWhichProgram('php-cs-fixer')
|
||||
|
||||
unless isWindows
|
||||
describe "Mac/Linux", ->
|
||||
|
||||
beforeEach ->
|
||||
# console.log('mac/linx')
|
||||
beautifier.isWindows = false
|
||||
Executable.isWindows = () -> false
|
||||
|
||||
do OSSpecificSpecs
|
||||
|
||||
|
@ -139,6 +147,6 @@ describe "PHP-CS-Fixer Beautifier", ->
|
|||
|
||||
beforeEach ->
|
||||
# console.log('windows')
|
||||
beautifier.isWindows = true
|
||||
Executable.isWindows = () -> true
|
||||
|
||||
do OSSpecificSpecs
|
||||
|
|
|
@ -4,8 +4,9 @@ fs = require('fs')
|
|||
temp = require('temp').track()
|
||||
readFile = Promise.promisify(fs.readFile)
|
||||
which = require('which')
|
||||
spawn = require('child_process').spawn
|
||||
path = require('path')
|
||||
shellEnv = require('shell-env')
|
||||
Executable = require('./executable')
|
||||
|
||||
module.exports = class Beautifier
|
||||
|
||||
|
@ -31,10 +32,42 @@ module.exports = class Beautifier
|
|||
###
|
||||
options: {}
|
||||
|
||||
executables: []
|
||||
|
||||
###
|
||||
Is the beautifier a command-line interface beautifier?
|
||||
###
|
||||
isPreInstalled: true
|
||||
isPreInstalled: () ->
|
||||
@executables.length is 0
|
||||
|
||||
_exe: {}
|
||||
loadExecutables: () ->
|
||||
if Object.keys(@_exe).length is @executables.length
|
||||
Promise.resolve(@_exe)
|
||||
else
|
||||
Promise.resolve(executables = @executables.map((e) -> new Executable(e)))
|
||||
.then((executables) -> Promise.all(executables.map((e) -> e.init())))
|
||||
.then((es) =>
|
||||
exe = {}
|
||||
missingInstalls = []
|
||||
es.forEach((e) ->
|
||||
exe[e.cmd] = e
|
||||
if not e.isInstalled
|
||||
missingInstalls.push(e)
|
||||
)
|
||||
@_exe = exe
|
||||
@debug("exe", exe)
|
||||
if missingInstalls.length is 0
|
||||
return @_exe
|
||||
else
|
||||
throw new Error("Missing required executables: #{missingInstalls.map((e) -> e.cmd).join(' and ')}")
|
||||
)
|
||||
exe: (cmd) ->
|
||||
console.log('exe', cmd, @_exe)
|
||||
e = @_exe[cmd]
|
||||
if !e?
|
||||
throw new Error("Missing executable \"#{cmd}\". Please report this bug to https://github.com/Glavin001/atom-beautify/issues")
|
||||
e
|
||||
|
||||
###
|
||||
Supported languages by this Beautifier
|
||||
|
@ -102,7 +135,7 @@ module.exports = class Beautifier
|
|||
startDir.pop()
|
||||
return null
|
||||
|
||||
# Retrieves the default line ending based upon the Atom configuration
|
||||
# Retrieves the default line ending based upon the Atom configuration
|
||||
# `line-ending-selector.defaultLineEnding`. If the Atom configuration
|
||||
# indicates "OS Default", the `process.platform` is queried, returning
|
||||
# CRLF for Windows systems and LF for all other systems.
|
||||
|
@ -124,64 +157,6 @@ module.exports = class Beautifier
|
|||
else
|
||||
return lf
|
||||
|
||||
###
|
||||
If platform is Windows
|
||||
###
|
||||
isWindows: do ->
|
||||
return new RegExp('^win').test(process.platform)
|
||||
|
||||
###
|
||||
Get Shell Environment variables
|
||||
|
||||
Special thank you to @ioquatix
|
||||
See https://github.com/ioquatix/script-runner/blob/v1.5.0/lib/script-runner.coffee#L45-L63
|
||||
###
|
||||
_envCache: null
|
||||
_envCacheDate: null
|
||||
_envCacheExpiry: 10000 # 10 seconds
|
||||
getShellEnvironment: ->
|
||||
return new Promise((resolve, reject) =>
|
||||
# Check Cache
|
||||
if @_envCache? and @_envCacheDate?
|
||||
# Check if Cache is old
|
||||
if (new Date() - @_envCacheDate) < @_envCacheExpiry
|
||||
# Still fresh
|
||||
return resolve(@_envCache)
|
||||
|
||||
# Check if Windows
|
||||
if @isWindows
|
||||
# Windows
|
||||
# Use default
|
||||
resolve(process.env)
|
||||
else
|
||||
# Mac & Linux
|
||||
# I tried using ChildProcess.execFile but there is no way to set detached and
|
||||
# this causes the child shell to lock up.
|
||||
# This command runs an interactive login shell and
|
||||
# executes the export command to get a list of environment variables.
|
||||
# We then use these to run the script:
|
||||
child = spawn process.env.SHELL, ['-ilc', 'env'],
|
||||
# This is essential for interactive shells, otherwise it never finishes:
|
||||
detached: true,
|
||||
# We don't care about stdin, stderr can go out the usual way:
|
||||
stdio: ['ignore', 'pipe', process.stderr]
|
||||
# We buffer stdout:
|
||||
buffer = ''
|
||||
child.stdout.on 'data', (data) -> buffer += data
|
||||
# When the process finishes, extract the environment variables and pass them to the callback:
|
||||
child.on 'close', (code, signal) =>
|
||||
if code isnt 0
|
||||
return reject(new Error("Could not get Shell Environment. Exit code: "+code+", Signal: "+signal))
|
||||
environment = {}
|
||||
for definition in buffer.split('\n')
|
||||
[key, value] = definition.split('=', 2)
|
||||
environment[key] = value if key != ''
|
||||
# Cache Environment
|
||||
@_envCache = environment
|
||||
@_envCacheDate = new Date()
|
||||
resolve(environment)
|
||||
)
|
||||
|
||||
###
|
||||
Like the unix which utility.
|
||||
|
||||
|
@ -191,182 +166,19 @@ module.exports = class Beautifier
|
|||
See https://github.com/isaacs/node-which
|
||||
###
|
||||
which: (exe, options = {}) ->
|
||||
# Get PATH and other environment variables
|
||||
@getShellEnvironment()
|
||||
.then((env) =>
|
||||
new Promise((resolve, reject) =>
|
||||
options.path ?= env.PATH
|
||||
if @isWindows
|
||||
# Environment variables are case-insensitive in windows
|
||||
# Check env for a case-insensitive 'path' variable
|
||||
if !options.path
|
||||
for i of env
|
||||
if i.toLowerCase() is "path"
|
||||
options.path = env[i]
|
||||
break
|
||||
|
||||
# Trick node-which into including files
|
||||
# with no extension as executables.
|
||||
# Put empty extension last to allow for other real extensions first
|
||||
options.pathExt ?= "#{process.env.PATHEXT ? '.EXE'};"
|
||||
which(exe, options, (err, path) ->
|
||||
resolve(exe) if err
|
||||
resolve(path)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
###
|
||||
Add help to error.description
|
||||
|
||||
Note: error.description is not officially used in JavaScript,
|
||||
however it is used internally for Atom Beautify when displaying errors.
|
||||
###
|
||||
commandNotFoundError: (exe, help) ->
|
||||
# Create new improved error
|
||||
# notify user that it may not be
|
||||
# installed or in path
|
||||
message = "Could not find '#{exe}'. \
|
||||
The program may not be installed."
|
||||
er = new Error(message)
|
||||
er.code = 'CommandNotFound'
|
||||
er.errno = er.code
|
||||
er.syscall = 'beautifier::run'
|
||||
er.file = exe
|
||||
if help?
|
||||
if typeof help is "object"
|
||||
# Basic notice
|
||||
helpStr = "See #{help.link} for program \
|
||||
installation instructions.\n"
|
||||
# Help to configure Atom Beautify for program's path
|
||||
helpStr += "You can configure Atom Beautify \
|
||||
with the absolute path \
|
||||
to '#{help.program or exe}' by setting \
|
||||
'#{help.pathOption}' in \
|
||||
the Atom Beautify package settings.\n" if help.pathOption
|
||||
# Optional, additional help
|
||||
helpStr += help.additional if help.additional
|
||||
# Common Help
|
||||
issueSearchLink =
|
||||
"https://github.com/Glavin001/atom-beautify/\
|
||||
search?q=#{exe}&type=Issues"
|
||||
docsLink = "https://github.com/Glavin001/\
|
||||
atom-beautify/tree/master/docs"
|
||||
helpStr += "Your program is properly installed if running \
|
||||
'#{if @isWindows then 'where.exe' \
|
||||
else 'which'} #{exe}' \
|
||||
in your #{if @isWindows then 'CMD prompt' \
|
||||
else 'Terminal'} \
|
||||
returns an absolute path to the executable. \
|
||||
If this does not work then you have not \
|
||||
installed the program correctly and so \
|
||||
Atom Beautify will not find the program. \
|
||||
Atom Beautify requires that the program be \
|
||||
found in your PATH environment variable. \n\
|
||||
Note that this is not an Atom Beautify issue \
|
||||
if beautification does not work and the above \
|
||||
command also does not work: this is expected \
|
||||
behaviour, since you have not properly installed \
|
||||
your program. Please properly setup the program \
|
||||
and search through existing Atom Beautify issues \
|
||||
before creating a new issue. \
|
||||
See #{issueSearchLink} for related Issues and \
|
||||
#{docsLink} for documentation. \
|
||||
If you are still unable to resolve this issue on \
|
||||
your own then please create a new issue and \
|
||||
ask for help.\n"
|
||||
er.description = helpStr
|
||||
else #if typeof help is "string"
|
||||
er.description = help
|
||||
return er
|
||||
Executable.which(exe, options)
|
||||
|
||||
###
|
||||
Run command-line interface command
|
||||
###
|
||||
run: (executable, args, {cwd, ignoreReturnCode, help, onStdin} = {}) ->
|
||||
# Flatten args first
|
||||
args = _.flatten(args)
|
||||
|
||||
# Resolve executable and all args
|
||||
Promise.all([executable, Promise.all(args)])
|
||||
.then(([exeName, args]) =>
|
||||
@debug('exeName, args:', exeName, args)
|
||||
|
||||
# Get PATH and other environment variables
|
||||
Promise.all([exeName, args, @getShellEnvironment(), @which(exeName)])
|
||||
)
|
||||
.then(([exeName, args, env, exePath]) =>
|
||||
@debug('exePath, env:', exePath, env)
|
||||
@debug('args', args)
|
||||
|
||||
exe = exePath ? exeName
|
||||
options = {
|
||||
cwd: cwd
|
||||
env: env
|
||||
}
|
||||
|
||||
@spawn(exe, args, options, onStdin)
|
||||
.then(({returnCode, stdout, stderr}) =>
|
||||
@verbose('spawn result', returnCode, stdout, stderr)
|
||||
|
||||
# If return code is not 0 then error occured
|
||||
if not ignoreReturnCode and returnCode isnt 0
|
||||
# operable program or batch file
|
||||
windowsProgramNotFoundMsg = "is not recognized as an internal or external command"
|
||||
|
||||
@verbose(stderr, windowsProgramNotFoundMsg)
|
||||
|
||||
if @isWindows and returnCode is 1 and stderr.indexOf(windowsProgramNotFoundMsg) isnt -1
|
||||
throw @commandNotFoundError(exeName, help)
|
||||
else
|
||||
throw new Error(stderr)
|
||||
else
|
||||
stdout
|
||||
)
|
||||
.catch((err) =>
|
||||
@debug('error', err)
|
||||
|
||||
# Check if error is ENOENT (command could not be found)
|
||||
if err.code is 'ENOENT' or err.errno is 'ENOENT'
|
||||
throw @commandNotFoundError(exeName, help)
|
||||
else
|
||||
# continue as normal error
|
||||
throw err
|
||||
)
|
||||
)
|
||||
|
||||
###
|
||||
Spawn
|
||||
###
|
||||
spawn: (exe, args, options, onStdin) ->
|
||||
# Remove undefined/null values
|
||||
args = _.without(args, undefined)
|
||||
args = _.without(args, null)
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
@debug('spawn', exe, args)
|
||||
|
||||
cmd = spawn(exe, args, options)
|
||||
stdout = ""
|
||||
stderr = ""
|
||||
|
||||
cmd.stdout.on('data', (data) ->
|
||||
stdout += data
|
||||
)
|
||||
cmd.stderr.on('data', (data) ->
|
||||
stderr += data
|
||||
)
|
||||
cmd.on('close', (returnCode) =>
|
||||
@debug('spawn done', returnCode, stderr, stdout)
|
||||
resolve({returnCode, stdout, stderr})
|
||||
)
|
||||
cmd.on('error', (err) =>
|
||||
@debug('error', err)
|
||||
reject(err)
|
||||
)
|
||||
|
||||
onStdin cmd.stdin if onStdin
|
||||
)
|
||||
exe = new Executable({
|
||||
name: @name
|
||||
homepage: @link
|
||||
installation: @link
|
||||
cmd: executable
|
||||
})
|
||||
exe.run(args, {cwd, ignoreReturnCode, help, onStdin})
|
||||
|
||||
###
|
||||
Logger instance
|
||||
|
|
|
@ -0,0 +1,309 @@
|
|||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
which = require('which')
|
||||
spawn = require('child_process').spawn
|
||||
path = require('path')
|
||||
semver = require('semver')
|
||||
shellEnv = require('shell-env')
|
||||
|
||||
module.exports = class Executable
|
||||
|
||||
name = null
|
||||
cmd = null
|
||||
homepage = null
|
||||
installation = null
|
||||
versionArgs = ['--version']
|
||||
versionParse = (text) -> semver.clean(text)
|
||||
versionsSupported: '>= 0.0.0'
|
||||
|
||||
constructor: (options) ->
|
||||
@name = options.name
|
||||
@cmd = options.cmd
|
||||
@homepage = options.homepage
|
||||
@installation = options.installation
|
||||
if options.version?
|
||||
versionOptions = options.version
|
||||
@versionArgs = versionOptions.args
|
||||
@versionParse = versionOptions.parse
|
||||
@versionsSupported = versionOptions.supported
|
||||
@setupLogger()
|
||||
|
||||
init: () ->
|
||||
Promise.all([
|
||||
@loadVersion()
|
||||
])
|
||||
.then(() => @)
|
||||
|
||||
###
|
||||
Logger instance
|
||||
###
|
||||
logger: null
|
||||
###
|
||||
Initialize and configure Logger
|
||||
###
|
||||
setupLogger: ->
|
||||
@logger = require('../logger')("#{@name} Executable")
|
||||
for key, method of @logger
|
||||
@[key] = method
|
||||
@verbose("#{@name} executable logger has been initialized.")
|
||||
|
||||
isInstalled = null
|
||||
version = null
|
||||
loadVersion: (force = false) ->
|
||||
@verbose("loadVersion", @version, force)
|
||||
if force or !@version?
|
||||
@verbose("Loading version without cache")
|
||||
@runVersion()
|
||||
.then((text) => @versionParse(text))
|
||||
.then((version) ->
|
||||
valid = Boolean(semver.valid(version))
|
||||
if not valid
|
||||
throw new Error("Version is not valid: "+version)
|
||||
version
|
||||
)
|
||||
.then((version) =>
|
||||
@isInstalled = true
|
||||
@version = version
|
||||
)
|
||||
.then((version) =>
|
||||
@verbose("#{@cmd} version: #{version}")
|
||||
version
|
||||
)
|
||||
.catch((error) =>
|
||||
@isInstalled = false
|
||||
@error(error)
|
||||
Promise.reject(@commandNotFoundError())
|
||||
)
|
||||
else
|
||||
@verbose("Loading cached version")
|
||||
Promise.resolve(@version)
|
||||
|
||||
runVersion: () ->
|
||||
@run(@versionArgs)
|
||||
|
||||
isSupported: () ->
|
||||
@isVersion(@versionsSupported)
|
||||
|
||||
isVersion: (range) ->
|
||||
semver.satisfies(@version, range)
|
||||
|
||||
###
|
||||
Run command-line interface command
|
||||
###
|
||||
run: (args, options = {}) ->
|
||||
@debug("Run: ", args, options)
|
||||
exeName = @cmd
|
||||
{ cwd, ignoreReturnCode, help, onStdin } = options
|
||||
# Flatten args first
|
||||
args = _.flatten(args)
|
||||
|
||||
# Resolve executable and all args
|
||||
Promise.all([@shellEnv(), Promise.all(args)])
|
||||
.then(([env, args]) =>
|
||||
@debug('exeName, args:', exeName, args)
|
||||
|
||||
# Get PATH and other environment variables
|
||||
Promise.all([exeName, args, env, @which(exeName)])
|
||||
)
|
||||
.then(([exeName, args, env, exePath]) =>
|
||||
@debug('exePath:', exePath)
|
||||
@debug('env:', env)
|
||||
@debug('args', args)
|
||||
|
||||
exe = exePath ? exeName
|
||||
spawnOptions = {
|
||||
cwd: cwd
|
||||
env: env
|
||||
}
|
||||
|
||||
@spawn(exe, args, spawnOptions, onStdin)
|
||||
.then(({returnCode, stdout, stderr}) =>
|
||||
@verbose('spawn result', returnCode, stdout, stderr)
|
||||
|
||||
# If return code is not 0 then error occured
|
||||
if not ignoreReturnCode and returnCode isnt 0
|
||||
# operable program or batch file
|
||||
windowsProgramNotFoundMsg = "is not recognized as an internal or external command"
|
||||
|
||||
@verbose(stderr, windowsProgramNotFoundMsg)
|
||||
|
||||
if @isWindows() and returnCode is 1 and stderr.indexOf(windowsProgramNotFoundMsg) isnt -1
|
||||
throw @commandNotFoundError(exeName, help)
|
||||
else
|
||||
throw new Error(stderr)
|
||||
else
|
||||
stdout
|
||||
)
|
||||
.catch((err) =>
|
||||
@debug('error', err)
|
||||
|
||||
# Check if error is ENOENT (command could not be found)
|
||||
if err.code is 'ENOENT' or err.errno is 'ENOENT'
|
||||
throw @commandNotFoundError(exeName, help)
|
||||
else
|
||||
# continue as normal error
|
||||
throw err
|
||||
)
|
||||
)
|
||||
|
||||
###
|
||||
Spawn
|
||||
###
|
||||
spawn: (exe, args, options, onStdin) ->
|
||||
# Remove undefined/null values
|
||||
args = _.without(args, undefined)
|
||||
args = _.without(args, null)
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
@debug('spawn', exe, args)
|
||||
|
||||
cmd = spawn(exe, args, options)
|
||||
stdout = ""
|
||||
stderr = ""
|
||||
|
||||
cmd.stdout.on('data', (data) ->
|
||||
stdout += data
|
||||
)
|
||||
cmd.stderr.on('data', (data) ->
|
||||
stderr += data
|
||||
)
|
||||
cmd.on('close', (returnCode) =>
|
||||
@debug('spawn done', returnCode, stderr, stdout)
|
||||
resolve({returnCode, stdout, stderr})
|
||||
)
|
||||
cmd.on('error', (err) =>
|
||||
@debug('error', err)
|
||||
reject(err)
|
||||
)
|
||||
|
||||
onStdin cmd.stdin if onStdin
|
||||
)
|
||||
|
||||
|
||||
###
|
||||
Add help to error.description
|
||||
|
||||
Note: error.description is not officially used in JavaScript,
|
||||
however it is used internally for Atom Beautify when displaying errors.
|
||||
###
|
||||
commandNotFoundError: (exe, help) ->
|
||||
exe ?= @name or @cmd
|
||||
# help ?= {
|
||||
# program: @cmd
|
||||
# link: @installation or @homepage
|
||||
# }
|
||||
# Create new improved error
|
||||
# notify user that it may not be
|
||||
# installed or in path
|
||||
message = "Could not find '#{exe}'. \
|
||||
The program may not be installed."
|
||||
er = new Error(message)
|
||||
er.code = 'CommandNotFound'
|
||||
er.errno = er.code
|
||||
er.syscall = 'beautifier::run'
|
||||
er.file = exe
|
||||
if help?
|
||||
if typeof help is "object"
|
||||
# Basic notice
|
||||
helpStr = "See #{help.link} for program \
|
||||
installation instructions.\n"
|
||||
# Help to configure Atom Beautify for program's path
|
||||
helpStr += "You can configure Atom Beautify \
|
||||
with the absolute path \
|
||||
to '#{help.program or exe}' by setting \
|
||||
'#{help.pathOption}' in \
|
||||
the Atom Beautify package settings.\n" if help.pathOption
|
||||
# Optional, additional help
|
||||
helpStr += help.additional if help.additional
|
||||
# Common Help
|
||||
issueSearchLink =
|
||||
"https://github.com/Glavin001/atom-beautify/\
|
||||
search?q=#{exe}&type=Issues"
|
||||
docsLink = "https://github.com/Glavin001/\
|
||||
atom-beautify/tree/master/docs"
|
||||
helpStr += "Your program is properly installed if running \
|
||||
'#{if @isWindows() then 'where.exe' \
|
||||
else 'which'} #{exe}' \
|
||||
in your #{if @isWindows() then 'CMD prompt' \
|
||||
else 'Terminal'} \
|
||||
returns an absolute path to the executable. \
|
||||
If this does not work then you have not \
|
||||
installed the program correctly and so \
|
||||
Atom Beautify will not find the program. \
|
||||
Atom Beautify requires that the program be \
|
||||
found in your PATH environment variable. \n\
|
||||
Note that this is not an Atom Beautify issue \
|
||||
if beautification does not work and the above \
|
||||
command also does not work: this is expected \
|
||||
behaviour, since you have not properly installed \
|
||||
your program. Please properly setup the program \
|
||||
and search through existing Atom Beautify issues \
|
||||
before creating a new issue. \
|
||||
See #{issueSearchLink} for related Issues and \
|
||||
#{docsLink} for documentation. \
|
||||
If you are still unable to resolve this issue on \
|
||||
your own then please create a new issue and \
|
||||
ask for help.\n"
|
||||
er.description = helpStr
|
||||
else #if typeof help is "string"
|
||||
er.description = help
|
||||
return er
|
||||
|
||||
|
||||
@_envCache = null
|
||||
shellEnv: () ->
|
||||
@constructor.shellEnv()
|
||||
@shellEnv: () ->
|
||||
if @_envCache
|
||||
return Promise.resolve(@_envCache)
|
||||
else
|
||||
shellEnv()
|
||||
.then((env) =>
|
||||
@_envCache = env
|
||||
)
|
||||
|
||||
###
|
||||
Like the unix which utility.
|
||||
|
||||
Finds the first instance of a specified executable in the PATH environment variable.
|
||||
Does not cache the results,
|
||||
so hash -r is not needed when the PATH changes.
|
||||
See https://github.com/isaacs/node-which
|
||||
###
|
||||
which: (exe, options) ->
|
||||
@.constructor.which(exe, options)
|
||||
@_whichCache = {}
|
||||
@which: (exe, options = {}) ->
|
||||
if @_whichCache[exe]
|
||||
return Promise.resolve(@_whichCache[exe])
|
||||
# Get PATH and other environment variables
|
||||
@shellEnv()
|
||||
.then((env) =>
|
||||
new Promise((resolve, reject) =>
|
||||
options.path ?= env.PATH
|
||||
if @isWindows()
|
||||
# Environment variables are case-insensitive in windows
|
||||
# Check env for a case-insensitive 'path' variable
|
||||
if !options.path
|
||||
for i of env
|
||||
if i.toLowerCase() is "path"
|
||||
options.path = env[i]
|
||||
break
|
||||
|
||||
# Trick node-which into including files
|
||||
# with no extension as executables.
|
||||
# Put empty extension last to allow for other real extensions first
|
||||
options.pathExt ?= "#{process.env.PATHEXT ? '.EXE'};"
|
||||
which(exe, options, (err, path) =>
|
||||
return resolve(exe) if err
|
||||
@_whichCache[exe] = path
|
||||
resolve(path)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
###
|
||||
If platform is Windows
|
||||
###
|
||||
isWindows: () -> @constructor.isWindows()
|
||||
@isWindows: () -> new RegExp('^win').test(process.platform)
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
"use strict"
|
||||
Beautifier = require('./beautifier')
|
||||
Lexer = require('gherkin').Lexer('en')
|
||||
logger = require('../logger')(__filename)
|
||||
|
||||
module.exports = class Gherkin extends Beautifier
|
||||
name: "Gherkin formatter"
|
||||
|
@ -15,6 +13,8 @@ module.exports = class Gherkin extends Beautifier
|
|||
}
|
||||
|
||||
beautify: (text, language, options) ->
|
||||
Lexer = require('gherkin').Lexer('en')
|
||||
logger = @logger
|
||||
return new @Promise((resolve, reject) ->
|
||||
recorder = {
|
||||
lines: []
|
||||
|
|
|
@ -345,36 +345,40 @@ module.exports = class Beautifiers extends EventEmitter
|
|||
filePath: filePath
|
||||
|
||||
startTime = new Date()
|
||||
beautifier.beautify(text, language.name, options, context)
|
||||
.then((result) =>
|
||||
resolve(result)
|
||||
# Track Timing
|
||||
@trackTiming({
|
||||
utc: "Beautify" # Category
|
||||
utv: language?.name # Variable
|
||||
utt: (new Date() - startTime) # Value
|
||||
utl: version # Label
|
||||
})
|
||||
# Track Empty beautification results
|
||||
if not result
|
||||
beautifier.loadExecutables()
|
||||
.then((executables) ->
|
||||
logger.verbose('executables', executables)
|
||||
beautifier.beautify(text, language.name, options, context)
|
||||
)
|
||||
.then((result) =>
|
||||
resolve(result)
|
||||
# Track Timing
|
||||
@trackTiming({
|
||||
utc: "Beautify" # Category
|
||||
utv: language?.name # Variable
|
||||
utt: (new Date() - startTime) # Value
|
||||
utl: version # Label
|
||||
})
|
||||
# Track Empty beautification results
|
||||
if not result
|
||||
@trackEvent({
|
||||
ec: version, # Category
|
||||
ea: "Beautify:Empty" # Action
|
||||
el: language?.name # Label
|
||||
})
|
||||
)
|
||||
.catch((error) =>
|
||||
reject(error)
|
||||
# Track Errors
|
||||
@trackEvent({
|
||||
ec: version, # Category
|
||||
ea: "Beautify:Empty" # Action
|
||||
ea: "Beautify:Error" # Action
|
||||
el: language?.name # Label
|
||||
})
|
||||
)
|
||||
.catch((error) =>
|
||||
reject(error)
|
||||
# Track Errors
|
||||
@trackEvent({
|
||||
ec: version, # Category
|
||||
ea: "Beautify:Error" # Action
|
||||
el: language?.name # Label
|
||||
})
|
||||
)
|
||||
.finally(=>
|
||||
@emit "beautify::end"
|
||||
)
|
||||
)
|
||||
.finally(=>
|
||||
@emit "beautify::end"
|
||||
)
|
||||
|
||||
# Check if Analytics is enabled
|
||||
@trackEvent({
|
||||
|
|
|
@ -10,7 +10,30 @@ module.exports = class PHPCSFixer extends Beautifier
|
|||
|
||||
name: 'PHP-CS-Fixer'
|
||||
link: "https://github.com/FriendsOfPHP/PHP-CS-Fixer"
|
||||
isPreInstalled: false
|
||||
executables: [
|
||||
{
|
||||
name: "PHP"
|
||||
cmd: "php"
|
||||
homepage: "http://php.net/"
|
||||
installation: "http://php.net/manual/en/install.php"
|
||||
version: {
|
||||
args: ['--version']
|
||||
parse: (text) -> text.match(/PHP (.*) \(cli\)/)[1]
|
||||
supported: '>= 0.0.0'
|
||||
}
|
||||
}
|
||||
{
|
||||
name: "PHP-CS-Fixer"
|
||||
cmd: "php-cs-fixer"
|
||||
homepage: "https://github.com/FriendsOfPHP/PHP-CS-Fixer"
|
||||
installation: "https://github.com/FriendsOfPHP/PHP-CS-Fixer#installation"
|
||||
version: {
|
||||
args: ['--version']
|
||||
parse: (text) -> text.match(/version (.*) by/)[1] + ".0"
|
||||
supported: '>= 0.0.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
options:
|
||||
PHP:
|
||||
|
@ -24,7 +47,8 @@ module.exports = class PHPCSFixer extends Beautifier
|
|||
|
||||
beautify: (text, language, options, context) ->
|
||||
@debug('php-cs-fixer', options)
|
||||
version = options.cs_fixer_version
|
||||
php = @exe('php')
|
||||
phpCsFixer = @exe('php-cs-fixer')
|
||||
configFiles = ['.php_cs', '.php_cs.dist']
|
||||
|
||||
# Find a config file in the working directory if a custom one was not provided
|
||||
|
@ -42,7 +66,7 @@ module.exports = class PHPCSFixer extends Beautifier
|
|||
"--allow-risky=#{options.allow_risky}" if options.allow_risky
|
||||
"--using-cache=no"
|
||||
]
|
||||
if version is 1
|
||||
if phpCsFixer.isVersion('1.x')
|
||||
phpCsFixerOptions = [
|
||||
"fix"
|
||||
"--level=#{options.level}" if options.level
|
||||
|
@ -60,7 +84,9 @@ module.exports = class PHPCSFixer extends Beautifier
|
|||
@Promise.all([
|
||||
@which(options.cs_fixer_path) if options.cs_fixer_path
|
||||
@which('php-cs-fixer')
|
||||
]).then((paths) =>
|
||||
tempFile = @tempFile("temp", text, '.php')
|
||||
]).then(([customPath, phpCsFixerPath]) =>
|
||||
paths = [customPath, phpCsFixerPath]
|
||||
@debug('php-cs-fixer paths', paths)
|
||||
_ = require 'lodash'
|
||||
# Get first valid, absolute path
|
||||
|
@ -71,10 +97,8 @@ module.exports = class PHPCSFixer extends Beautifier
|
|||
# Check if PHP-CS-Fixer path was found
|
||||
if phpCSFixerPath?
|
||||
# Found PHP-CS-Fixer path
|
||||
tempFile = @tempFile("temp", text)
|
||||
|
||||
if @isWindows
|
||||
@run("php", [phpCSFixerPath, phpCsFixerOptions, tempFile], runOptions)
|
||||
php([phpCSFixerPath, phpCsFixerOptions, tempFile], runOptions)
|
||||
.then(=>
|
||||
@readFile(tempFile)
|
||||
)
|
||||
|
|
|
@ -8,6 +8,17 @@ Beautifier = require('./beautifier')
|
|||
module.exports = class PHPCBF extends Beautifier
|
||||
name: "PHPCBF"
|
||||
link: "http://php.net/manual/en/install.php"
|
||||
executables: [
|
||||
{
|
||||
name: "PHPCBF"
|
||||
cmd: "phpcbf"
|
||||
homepage: "https://github.com/squizlabs/PHP_CodeSniffer"
|
||||
installation: "https://github.com/squizlabs/PHP_CodeSniffer#installation"
|
||||
version: {
|
||||
args: ['--version']
|
||||
}
|
||||
}
|
||||
]
|
||||
isPreInstalled: false
|
||||
|
||||
options: {
|
||||
|
|
Loading…
Reference in New Issue