Skip to content

Instantly share code, notes, and snippets.

@jlieske
Last active May 19, 2016 06:48
Show Gist options
  • Save jlieske/e9eb1f53fd7021bea7877397429dc50e to your computer and use it in GitHub Desktop.
Save jlieske/e9eb1f53fd7021bea7877397429dc50e to your computer and use it in GitHub Desktop.
Script for BBEdit to perform check of JavaScript program using JSCS.
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Check JavaScript with JSCS:
# Script to perform check of front window using JSCS (JavaScript Check Style).
# Place this script in the script menu and invoke it from BBEdit.
# You will also need a .jscsrc or package.json file with the style rules.
# Copyright Jay Lieske Jr.
# 11 May 2016
import sys, re, os, subprocess, struct
import Foundation as ns
import ScriptingBridge as sb
_PRINT_DEBUG = False
def getFrontBBEditDocument():
BBEdit = sb.SBApplication.applicationWithBundleIdentifier_(
"com.barebones.BBEdit")
document = BBEdit.documents().firstObject()
if document:
return document.file().path()
else:
return None
def invokeChecker(filePath):
"""Call JSCS to check the filePath, returning stderr from the run."""
# In order for JSCS to find the .jscsrc or package.json config,
# switch to the file's directory.
dir = os.path.dirname(filePath)
# JSCS driver script can be installed by NPM.
# Annoyingly, the script menu does not respect the shell path.
nodejs = "/usr/local/bin/node"
checker = "/usr/local/bin/jscs"
command = [nodejs, checker, "--reporter=unix", filePath]
if _PRINT_DEBUG:
import pipes
print " ".join(map(pipes.quote, command))
# Run the checker and capture the output.
try:
output = subprocess.check_output(command, stderr=subprocess.STDOUT,
cwd=dir)
except subprocess.CalledProcessError, e:
output = e.output
if _PRINT_DEBUG:
print output
return output
def fourCharCode(string):
"Convert a four-char code string into a 32-bit int."
return struct.unpack('>I', string)[0]
def processMessages(compilerOutput):
# Matches just the error messages from the compiler:
# a file name, followed by a colon and line number, an optional column number,
# and then the error message.
# Since BBEdit doesn't accept column numbers, it doesn't try to parse
# other positional feedback from the compiler.
# To avoid false matches, this version doesn't allow spaces in file names.
pat = re.compile(r"^([^: ]+):(\d+)(?:[.:-]\d+)*[:\s]+(.+)$")
# Distinguish warning from errors.
warning = re.compile(r"warning", re.IGNORECASE)
warning_kind = ns.NSAppleEventDescriptor.descriptorWithEnumCode_(
fourCharCode('Wrng'))
error_kind = ns.NSAppleEventDescriptor.descriptorWithEnumCode_(
fourCharCode('Err '))
# Accumulate the set of errors found on compiler output.
entries = []
for line in compilerOutput.splitlines():
match = pat.match(line)
if match:
file, line, message = match.groups()
kind = warning_kind if warning.match(message) else error_kind
file = os.path.abspath(file)
# BBEdit returns error -1701 for nonexistent files.
if os.path.exists(file):
entry = {
'result_kind': kind,
'result_file': file,
'result_line': int(line),
'message': message
}
entries.append(entry)
return entries
def showResultsBrowser(title, entries):
BBEdit = sb.SBApplication.applicationWithBundleIdentifier_(
"com.barebones.BBEdit")
ResultsBrowser = BBEdit.classForScriptingClass_("results browser")
properties = {'name': title}
browserSpecifier = ResultsBrowser.alloc().initWithElementCode_properties_data_(
fourCharCode('RslW'), properties, entries)
BBEdit.windows().addObject_(browserSpecifier)
def showNotification(title, message):
notification = ns.NSUserNotification.alloc().init()
notification.setTitle_(title)
notification.setInformativeText_(message)
notifier = ns.NSUserNotificationCenter.defaultUserNotificationCenter()
notifier.deliverNotification_(notification)
filePath = getFrontBBEditDocument()
# print filePath
if filePath:
compilerOutput = invokeChecker(filePath)
# print compilerOutput
entries = processMessages(compilerOutput)
title = "JSCS"
message = "%d issues" % len(entries)
if entries:
showResultsBrowser(title, entries)
showNotification(title, message)
# MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment