pmacs3/mode/sh.py

240 lines
9.9 KiB
Python

import re
import commands
from tab import StackTabber
from mode import Fundamental
from lex import Grammar, PatternRule, RegionRule, PatternMatchRule, OverridePatternRule
from method import Method
from method.shell import Interact
char = '[a-zA-Z0-9_]'
word = char + '+'
pname = '[.a-zA-Z0-9_]+'
class StringGrammar1(Grammar): pass
class StringGrammar2(Grammar): pass
class HereGrammar(Grammar): pass
class EvalGrammar(Grammar): pass
class NevalGrammar(Grammar): pass
class StanzaGrammar(Grammar): pass
class CaseGrammar(Grammar): pass
class TestGrammar(Grammar): pass
class ShGrammar(Grammar): pass
StringGrammar1.rules = [
PatternRule(r'data', r'[^\']+'),
]
StringGrammar2.rules = [
PatternRule(r'escaped', r'\\.'),
PatternRule(r'variable', r"\${(?:" + word + "|\?\$)}"),
PatternRule(r"variable", r"\$" + word),
PatternRule(r'variable', r"\$(?=\()"),
PatternRule(r'data', r'[^\\$"]+'),
]
HereGrammar.rules = [
PatternRule(r'escaped', r'\\.'),
PatternRule(r'variable', r"\${(?:" + word + "|\?\$)}"),
PatternRule(r"variable", r"\$" + word),
PatternRule(r'variable', r"\$(?=\()"),
PatternRule(r'data', r'[^\\$]+'),
]
EvalGrammar.rules = [
RegionRule(r'string', "'", StringGrammar1, "'"),
RegionRule(r'string', '"', StringGrammar2, '"'),
PatternRule(r'escaped', r'\\.'),
PatternRule(r'variable', r"\${(?:" + word + "|\?\$)}"),
PatternRule(r"variable", r"\$" + word),
PatternRule(r'variable', r"\$(?=\()"),
PatternRule(r'data', r'[^\\`$\'"]+'),
]
NevalGrammar.rules = [
RegionRule(r'string', "'", Grammar, "'"),
RegionRule(r'string', '"', StringGrammar2, '"'),
PatternRule(r'escaped', r'\\.'),
PatternRule(r'variable', r"\${(?:" + word + "|\?\$)}"),
PatternRule(r"variable", r"\$" + word),
PatternRule(r'variable', r"\$(?=\()"),
PatternRule(r'data', r'[^\\)$]+'),
]
StanzaGrammar.rules = [
PatternRule(r'spaces', r' +'),
PatternRule(r'start_cont', r'.+\\'),
PatternRule(r'eol', r'\n'),
]
CaseGrammar.rules = [
PatternRule(r'comment', r'#.*$'),
PatternRule(r'spaces', r' +'),
RegionRule(r'stanza', r'.+\\\n$', StanzaGrammar, r'.+\)', ShGrammar, r';;'),
RegionRule(r'stanza', r'.+?\)', ShGrammar, r';;'),
PatternRule(r'eol', r'\n'),
]
TestGrammar.rules = [
PatternRule(r'spaces', r' +'),
PatternRule(r'sh_builtin', r"(?<![-a-zA-Z0-9_])(?:source|alias|bg|bind|break|builtin|cd|command|compgen|complete|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getops|hash|help|history|jobs|kill|let|local|logout|popd|printf|pushd|pwd|readonly|read|return|set|shift|shopt|suspend|test|times|trap|type|ulimit|umask|unalias|unset|wait)(?![a-zA-Z0-9_=/])"),
PatternRule(r'sh_reserved', r"(?:done|do|elif|else|esac|fi|for|function|if|in|select|then|until|while|time)(?![a-zA-Z0-9_=/])"),
PatternRule(r'binop', r'==|=|!='),
PatternRule(r'binop', r'-(?:nt|ot|ef|eq|ne|lt|gt|le|ge)(?!' + char + ')'),
PatternRule(r'unop', r'-[a-zA-Z](?!' + char + ')'),
PatternRule(r'continuation', r'\\\n$'),
PatternRule(r'redirect', r'<|>'),
PatternRule(r'delimiter', r";;|[();{}|&><]"),
RegionRule(r'test', r'test(?![a-zA-Z0-9_=/])', None, r';|\n'),
RegionRule(r'test2', r'\[\[', None, r'\]\]'),
RegionRule(r'test3', r'\[', None, r'\]'),
RegionRule(r'eval', r'`', EvalGrammar, r'`'),
RegionRule(r'neval', r'\$\(', NevalGrammar, r'\)'),
PatternRule(r'variable', r"(?:^|(?<= ))" + word + "(?==)"),
PatternRule(r'variable', r"\${(?:" + word + "|\?\$)}"),
PatternRule(r"variable", r"\$" + word),
PatternRule(r'variable', r"\$(?=\()"),
RegionRule(r'string', "'", StringGrammar1, "'"),
RegionRule(r'string', '"', StringGrammar2, '"'),
PatternRule(r'sh_bareword', r'[-a-zA-Z0-9_.]+'),
]
ShGrammar.rules = [
PatternMatchRule('x', '( *)(' + word + ')(=)',
'spaces', 'variable', 'delimiter'),
PatternMatchRule('x', '(alias|export)( +)(' + word + ')(=)',
'sh_builtin', 'spaces', 'variable', 'delimiter'),
PatternMatchRule('x', '(unset)( +)(' + word + ')',
'sh_builtin', 'spaces', 'variable'),
PatternRule(r'spaces', r' +'),
RegionRule(r'heredoc', r"<<[<\\]?(?P<heredoc>" + word + ")", None, "\n", HereGrammar, r'^%(heredoc)s$'),
RegionRule(r'heredoc', r"<<-(?P<heredoc>" + word + ")", None, "\n", HereGrammar, r'^ *%(heredoc)s$'),
PatternRule(r'sh_function', word + r'(?= *\(\))'),
PatternRule(r'sh_reserved', r"(?:done|do|elif|else|esac|fi|for|function|if|in|select|then|until|while|time)(?![a-zA-Z0-9_=/])"),
RegionRule(r'case', r'case', None, 'in', CaseGrammar, r'esac'),
PatternRule(r'sh_builtin', r"(?<![-a-zA-Z0-9_])(?:source|alias|bg|bind|break|builtin|cd|command|compgen|complete|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getops|hash|help|history|jobs|kill|let|local|logout|popd|printf|pushd|pwd|readonly|read|return|set|shift|shopt|suspend|times|trap|type|ulimit|umask|unalias|unset|wait)(?![a-zA-Z0-9_=/])(?![-a-zA-Z0-9_])"),
RegionRule(r'test', r'test', TestGrammar, r';|\n'),
RegionRule(r'test2', r'\[\[', TestGrammar, r'\]\]'),
RegionRule(r'test3', r'\[', TestGrammar, r'\]'),
PatternRule(r'redirect', r'<|>'),
PatternRule(r'delimiter', r";;|[();{}|&><:=/]"),
RegionRule(r'eval', r'`', EvalGrammar, r'`'),
RegionRule(r'neval', r'\$\(', NevalGrammar, r'\)'),
PatternRule(r'variable', r"\${(?:" + word + "|\?\$)}"),
PatternRule(r"variable", r"\$" + word),
#PatternRule(r'variable', r"\$(?=\()"),
RegionRule(r'string', "'", StringGrammar1, "'"),
RegionRule(r'string', '"', StringGrammar2, '"'),
OverridePatternRule(r'comment', r'#@@:(?P<token>' + pname + '):(?P<mode>' + pname + ') *$'),
PatternRule(r'comment', r'#.*$'),
PatternRule(r'sh_bareword', r'(?:[-a-zA-Z0-9_.]|\\.)+'),
PatternRule(r'continuation', r'\\\n$'),
PatternRule(r'eol', r'\n$'),
]
class ShTabber(StackTabber):
def is_base(self, y):
if y == 0:
return True
highlighter = self.mode.window.buffer.highlights[self.mode.name]
if not highlighter.tokens[y]:
return False
t = highlighter.tokens[y][0]
return t.name == 'sh_function'
def _handle_close_token(self, currlvl, y, i):
s = self.get_token(y, i).string
if s == ')' and self.markers and self._peek_name() == "case":
# we have to ignore ) when used in "case" statements.
return currlvl
else:
return StackTabber._handle_close_token(self, currlvl, y, i)
def _handle_other_token(self, currlvl, y, i):
w = self.mode.tabwidth
token = self.get_token(y, i)
fqname = token.fqname()
if token.name == 'continuation':
self._opt_append("cont", currlvl + w)
elif token.name == 'sh_reserved' and token.string == 'else':
currlvl -= w
elif token.name == 'eol':
self._opt_pop("cont")
return currlvl
class ShCheckSyntax(Method):
'''Check the syntax of a shell script'''
def _execute(self, w, **vargs):
app = w.application
cmd = "bash -n %r" % w.buffer.path
(status, output) = commands.getstatusoutput(cmd)
if status == 0:
app.set_error("Syntax OK")
app.data_buffer("*Sh-Check-Syntax*", output, switch_to=False)
else:
app.data_buffer("*Sh-Check-Syntax*", output)
class BashStart(Interact):
args = []
reuse = True
def _execute(self, w, **vargs):
Interact._execute(self, w, bname='*Bash*', cmd='bash')
class BashLoadFile(Interact):
args = []
reuse = True
def _execute(self, w, **vargs):
path = w.buffer.path
cmd = 'bash %r' % path
Interact._execute(self, w, bname='*Bash*', cmd=cmd)
b = w.application.get_buffer_by_name('*Bash*')
class Sh(Fundamental):
name = 'sh'
paths = ['/etc/profile']
basenames = ['.bashrc', '.bash_profile', '.profile']
extensions = ['.bash', '.sh']
#detection = ['sh', 'bash']
detection = [re.compile('^#!(?:.+/)sh'), re.compile('^#!(?:.+/)bash')]
grammar = ShGrammar
tabbercls = ShTabber
opentokens = ('delimiter', 'sh_reserved', 'case.start')
opentags = {'(': ')', '[': ']', '{': '}', 'do': 'done', 'then': 'fi',
'case': 'esac'}
closetokens = ('delimiter', 'sh_reserved', 'case.end')
closetags = {')': '(', ']': '[', '}': '{', 'done': 'do', 'fi': 'then',
'esac': 'case'}
colors = {
'sh_builtin': ('cyan', 'default', 'bold'),
'sh_function': ('magenta', 'default', 'bold'),
'sh_reserved': ('magenta', 'default', 'bold'),
'variable': ('yellow', 'default', 'bold'),
# case statements
'case.start': ('magenta', 'default', 'bold'),
'case.stanza.start': ('cyan', 'default', 'bold'),
'case.stanza.start_cont': ('cyan', 'default', 'bold'),
'case.stanza.middle0': ('cyan', 'default', 'bold'),
'case.middle0': ('magenta', 'default', 'bold'),
'case.end': ('magenta', 'default', 'bold'),
'test.start': ('cyan', 'default', 'bold'),
'binop': ('magenta', 'default', 'bold'),
'unop': ('magenta', 'default', 'bold'),
'eval.start': ('cyan', 'default', 'bold'),
'eval.variable': ('yellow', 'default', 'bold'),
'eval.data': ('cyan', 'default', 'bold'),
'eval.null': ('cyan', 'default', 'bold'),
'eval.end': ('cyan', 'default', 'bold'),
'neval.start': ('yellow', 'default', 'bold'),
'neval.variable': ('yellow', 'default', 'bold'),
'neval.data': ('cyan', 'default', 'bold'),
'neval.null': ('cyan', 'default', 'bold'),
'neval.end': ('yellow', 'default', 'bold'),
}
actions = [ShCheckSyntax, BashStart, BashLoadFile]
_bindings = {
'sh-check-syntax': ('C-c s',),
}
install = Sh.install