1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
|
# -*- coding: utf-8 -*- #
# frozen_string_literal: true
module Rouge
module Lexers
class Shell < RegexLexer
title "shell"
desc "Various shell languages, including sh and bash"
tag 'shell'
aliases 'bash', 'zsh', 'ksh', 'sh'
filenames '*.sh', '*.bash', '*.zsh', '*.ksh', '.bashrc',
'.kshrc', '.profile',
'.zshenv', '.zprofile', '.zshrc', '.zlogin', '.zlogout',
'zshenv', 'zprofile', 'zshrc', 'zlogin', 'zlogout',
'APKBUILD', 'PKGBUILD', '*.ebuild',
'*.eclass', '*.exheres-0', '*.exlib'
mimetypes 'application/x-sh', 'application/x-shellscript', 'text/x-sh',
'text/x-shellscript'
def self.detect?(text)
return true if text.shebang?(/(ba|z|k)?sh/)
return true if text.start_with?('#compdef', '#autoload')
end
KEYWORDS = %w(
if fi else while do done for then return function
select continue until esac elif in
).join('|')
BUILTINS = %w(
alias bg bind break builtin caller cd command compgen
complete declare dirs disown enable eval exec exit
export false fc fg getopts hash help history jobs let
local logout mapfile popd pushd pwd read readonly set
shift shopt source suspend test time times trap true type
typeset ulimit umask unalias unset wait
cat tac nl od base32 base64 fmt pr fold head tail split csplit
wc sum cksum b2sum md5sum sha1sum sha224sum sha256sum sha384sum
sha512sum sort shuf uniq comm ptx tsort cut paste join tr expand
unexpand ls dir vdir dircolors cp dd install mv rm shred link ln
mkdir mkfifo mknod readlink rmdir unlink chown chgrp chmod touch
df du stat sync truncate echo printf yes expr tee basename dirname
pathchk mktemp realpath pwd stty printenv tty id logname whoami
groups users who date arch nproc uname hostname hostid uptime chcon
runcon chroot env nice nohup stdbuf timeout kill sleep factor numfmt
seq tar grep sudo awk sed gzip gunzip
).join('|')
state :basic do
rule %r/#.*$/, Comment
rule %r/\b(#{KEYWORDS})\s*\b/, Keyword
rule %r/\bcase\b/, Keyword, :case
rule %r/\b(#{BUILTINS})\s*\b(?!(\.|-))/, Name::Builtin
rule %r/[.](?=\s)/, Name::Builtin
rule %r/(\b\w+)(=)/ do
groups Name::Variable, Operator
end
rule %r/[\[\]{}()!=>]/, Operator
rule %r/&&|\|\|/, Operator
# here-string
rule %r/<<</, Operator
rule %r/(<<-?)(\s*)(['"]?)(\\?)(\w+)(\3)/ do |m|
groups Operator, Text, Str::Heredoc, Str::Heredoc, Name::Constant, Str::Heredoc
@heredocstr = Regexp.escape(m[5])
push :heredoc
end
end
state :heredoc do
rule %r/\n/, Str::Heredoc, :heredoc_nl
rule %r/[^$\n\\]+/, Str::Heredoc
mixin :interp
rule %r/[$]/, Str::Heredoc
end
state :heredoc_nl do
rule %r/\s*(\w+)\s*\n/ do |m|
if m[1] == @heredocstr
token Name::Constant
pop! 2
else
token Str::Heredoc
end
end
rule(//) { pop! }
end
state :double_quotes do
# NB: "abc$" is literally the string abc$.
# Here we prevent :interp from interpreting $" as a variable.
rule %r/(?:\$#?)?"/, Str::Double, :pop!
mixin :interp
rule %r/[^"`\\$]+/, Str::Double
end
state :ansi_string do
rule %r/\\./, Str::Escape
rule %r/[^\\']+/, Str::Single
mixin :single_quotes
end
state :single_quotes do
rule %r/'/, Str::Single, :pop!
rule %r/[^']+/, Str::Single
end
state :data do
rule %r/\s+/, Text
rule %r/\\./, Str::Escape
rule %r/\$?"/, Str::Double, :double_quotes
rule %r/\$'/, Str::Single, :ansi_string
# single quotes are much easier than double quotes - we can
# literally just scan until the next single quote.
# POSIX: Enclosing characters in single-quotes ( '' )
# shall preserve the literal value of each character within the
# single-quotes. A single-quote cannot occur within single-quotes.
rule %r/'/, Str::Single, :single_quotes
rule %r/\*/, Keyword
rule %r/;/, Punctuation
rule %r/--?[\w-]+/, Name::Tag
rule %r/[^=\*\s{}()$"'`;\\<]+/, Text
rule %r/\d+(?= |\Z)/, Num
rule %r/</, Text
mixin :interp
end
state :curly do
rule %r/}/, Keyword, :pop!
rule %r/:-/, Keyword
rule %r/[a-zA-Z0-9_]+/, Name::Variable
rule %r/[^}:"`'$]+/, Punctuation
mixin :root
end
# the state inside $(...)
state :paren_interp do
rule %r/\)/, Str::Interpol, :pop!
rule %r/\(/, Operator, :paren_inner
mixin :root
end
# used to balance parentheses inside interpolation
state :paren_inner do
rule %r/\(/, Operator, :push
rule %r/\)/, Operator, :pop!
mixin :root
end
state :math do
rule %r/\)\)/, Keyword, :pop!
rule %r([-+*/%^|&!]|\*\*|\|\|), Operator
rule %r/\d+(#\w+)?/, Num
mixin :root
end
state :case do
rule %r/\besac\b/, Keyword, :pop!
rule %r/\|/, Punctuation
rule %r/\)/, Punctuation, :case_stanza
mixin :root
end
state :case_stanza do
rule %r/;;/, Punctuation, :pop!
mixin :root
end
state :backticks do
rule %r/`/, Str::Backtick, :pop!
mixin :root
end
state :interp do
rule %r/\\$/, Str::Escape # line continuation
rule %r/\\./, Str::Escape
rule %r/\$\(\(/, Keyword, :math
rule %r/\$\(/, Str::Interpol, :paren_interp
rule %r/\${#?/, Keyword, :curly
rule %r/`/, Str::Backtick, :backticks
rule %r/\$#?(\w+|.)/, Name::Variable
rule %r/\$[*@]/, Name::Variable
end
state :root do
mixin :basic
mixin :data
end
end
end
end
|