pygments-shell-console 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygments_shell_console/__init__.py +5 -0
- pygments_shell_console/git.py +291 -0
- pygments_shell_console/shell.py +213 -0
- pygments_shell_console/style.py +38 -0
- pygments_shell_console-0.9.0.dist-info/METADATA +87 -0
- pygments_shell_console-0.9.0.dist-info/RECORD +8 -0
- pygments_shell_console-0.9.0.dist-info/WHEEL +4 -0
- pygments_shell_console-0.9.0.dist-info/entry_points.txt +13 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""Lexers for the output of git commands."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from pygments.lexer import Lexer, RegexLexer, bygroups, default, line_re, \
|
|
6
|
+
using
|
|
7
|
+
from pygments.lexers.diff import DiffLexer
|
|
8
|
+
from pygments.token import STANDARD_TYPES, Comment, Generic, Token, Whitespace
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
'GitLexer',
|
|
12
|
+
'GitStatusLexer',
|
|
13
|
+
'GitShortStatusLexer',
|
|
14
|
+
'GitLogLexer',
|
|
15
|
+
'GitPrettyLogLexer',
|
|
16
|
+
'GitShowLexer',
|
|
17
|
+
'GitBranchLexer',
|
|
18
|
+
'GitMergeLexer',
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
STANDARD_TYPES.update({
|
|
22
|
+
Token.Git: 'git',
|
|
23
|
+
Token.Git.BranchLine: 'git-bl',
|
|
24
|
+
Token.Git.CommitHash: 'git-ch',
|
|
25
|
+
Token.Git.CommitDate: 'git-cd',
|
|
26
|
+
Token.Git.CommitMessage: 'git-cm',
|
|
27
|
+
Token.Git.CommitAuthor: 'git-ca',
|
|
28
|
+
Token.Git.Hint: 'git-hint',
|
|
29
|
+
Token.Git.Warning: 'git-warning',
|
|
30
|
+
Token.Git.Error: 'git-error',
|
|
31
|
+
Token.Git.Fatal: 'git-fatal',
|
|
32
|
+
Token.Git.Refs: 'git-r',
|
|
33
|
+
Token.Git.Untracked: 'git-untr',
|
|
34
|
+
Token.Git.Modified: 'git-mod',
|
|
35
|
+
Token.Git.Unmerged: 'git-unm',
|
|
36
|
+
Token.Git.Staged: 'git-stg',
|
|
37
|
+
Token.Git.Show: 'git-show',
|
|
38
|
+
Token.Git.Show.Header: 'git-show-h',
|
|
39
|
+
Token.Git.Refs.RemoteHead: 'git-rh',
|
|
40
|
+
Token.Git.Refs.Head: 'git-h',
|
|
41
|
+
Token.Git.Refs.Tag: 'git-t',
|
|
42
|
+
Token.Git.Refs.RemoteBranch: 'git-rb',
|
|
43
|
+
Token.Git.Refs.Branch: 'git-b',
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
# Remote names recognised in ref decorations, e.g. "origin/main".
|
|
47
|
+
_REMOTES = r'(?:origin|upstream)'
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class GitLexer(RegexLexer):
|
|
51
|
+
"""Generic git output: highlights error, fatal, hint and warning lines."""
|
|
52
|
+
|
|
53
|
+
name = 'Git'
|
|
54
|
+
aliases = ['git']
|
|
55
|
+
filenames = ['*.git']
|
|
56
|
+
version_added = '1.0'
|
|
57
|
+
|
|
58
|
+
tokens = {
|
|
59
|
+
'root': [
|
|
60
|
+
(r'\n', Whitespace),
|
|
61
|
+
(r'^error:.*\n', Token.Git.Error),
|
|
62
|
+
(r'^fatal:.*\n', Token.Git.Fatal),
|
|
63
|
+
(r'^hint:.*\n', Token.Git.Hint),
|
|
64
|
+
(r'^warning:.*\n', Token.Git.Warning),
|
|
65
|
+
(r'^.*\n', Generic.Output),
|
|
66
|
+
(r'[^\n]+', Generic.Output),
|
|
67
|
+
],
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _status_section(token):
|
|
72
|
+
"""Rules for a `git status` file list, colouring each entry with `token`."""
|
|
73
|
+
return [
|
|
74
|
+
(r'^\s*\n', Whitespace, '#pop'),
|
|
75
|
+
(r'^\s+\(.*\)\n', Generic.Output),
|
|
76
|
+
(r'^(\s*)([^\n]+)(\n)', bygroups(Whitespace, token, Whitespace)),
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class GitStatusLexer(RegexLexer):
|
|
81
|
+
"""Output of `git status`."""
|
|
82
|
+
|
|
83
|
+
name = 'Git Status'
|
|
84
|
+
aliases = ['git-status']
|
|
85
|
+
version_added = '1.0'
|
|
86
|
+
|
|
87
|
+
tokens = {
|
|
88
|
+
'root': [
|
|
89
|
+
(r'^\n', Whitespace),
|
|
90
|
+
(r'\s*Untracked files:\n', Generic.Output, 'untracked'),
|
|
91
|
+
(r'\s*Changes not staged for commit:\n', Generic.Output, 'modified'),
|
|
92
|
+
(r'\s*Changes to be committed:\n', Generic.Output, 'staged'),
|
|
93
|
+
(r'\s*Unmerged paths:\n', Generic.Output, 'unmerged'),
|
|
94
|
+
(r'^.*\n', using(GitLexer)),
|
|
95
|
+
],
|
|
96
|
+
'untracked': _status_section(Token.Git.Untracked),
|
|
97
|
+
'modified': _status_section(Token.Git.Modified),
|
|
98
|
+
'staged': _status_section(Token.Git.Staged),
|
|
99
|
+
'unmerged': _status_section(Token.Git.Unmerged),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class GitShortStatusLexer(RegexLexer):
|
|
104
|
+
"""Output of `git status --short` / `--porcelain`."""
|
|
105
|
+
|
|
106
|
+
name = 'Git Short Status'
|
|
107
|
+
aliases = ['git-short-status']
|
|
108
|
+
version_added = '1.0'
|
|
109
|
+
|
|
110
|
+
tokens = {
|
|
111
|
+
'root': [
|
|
112
|
+
(r'^\n', Whitespace),
|
|
113
|
+
(r'^(## )(No commits yet on )(\S+)(.*\n)', bygroups(
|
|
114
|
+
Generic.Output, Generic.Output,
|
|
115
|
+
Token.Git.Refs.Branch, Generic.Output,
|
|
116
|
+
)),
|
|
117
|
+
(r'^(## )(\S+?)(?:(\.\.\.)(\S+))?(\s.*\n|\n)', bygroups(
|
|
118
|
+
Generic.Output, Token.Git.Refs.Branch,
|
|
119
|
+
Generic.Output, Token.Git.Refs.RemoteBranch, Generic.Output,
|
|
120
|
+
)),
|
|
121
|
+
(r'^(\?\?)( )(.*)(\n)', bygroups(
|
|
122
|
+
Token.Git.Untracked, Whitespace, Generic.Output, Whitespace,
|
|
123
|
+
)),
|
|
124
|
+
(r'^(DD|AU|UD|UA|DU|AA|UU)( )(.*)(\n)', bygroups(
|
|
125
|
+
Token.Git.Unmerged, Whitespace, Generic.Output, Whitespace,
|
|
126
|
+
)),
|
|
127
|
+
(r'^([MTADRC ])([MTADRC ])( )(.*)(\n)', bygroups(
|
|
128
|
+
Token.Git.Staged, Token.Git.Modified,
|
|
129
|
+
Whitespace, Generic.Output, Whitespace,
|
|
130
|
+
)),
|
|
131
|
+
(r'^.*\n', using(GitLexer)),
|
|
132
|
+
],
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class GitLogLexer(RegexLexer):
|
|
137
|
+
"""Output of `git log`, including `--oneline` and `-p`."""
|
|
138
|
+
|
|
139
|
+
name = 'Git Log'
|
|
140
|
+
aliases = ['git-log']
|
|
141
|
+
version_added = '1.0'
|
|
142
|
+
|
|
143
|
+
tokens = {
|
|
144
|
+
'root': [
|
|
145
|
+
(r'^\n', Whitespace),
|
|
146
|
+
(r'^commit [0-9a-f]+', Token.Git.Show.Header, 'header'),
|
|
147
|
+
(r'^[0-9a-f]{7,40}(?= )', Token.Git.CommitHash, 'oneline'),
|
|
148
|
+
(r'^diff .*\n', using(DiffLexer), 'diff'),
|
|
149
|
+
(r'^.*\n', using(GitLexer)),
|
|
150
|
+
],
|
|
151
|
+
'header': [
|
|
152
|
+
(r' +', Whitespace),
|
|
153
|
+
(r'\(', Token.Git.Show.Header, 'refs'),
|
|
154
|
+
(r'\n', Whitespace, '#pop'),
|
|
155
|
+
],
|
|
156
|
+
'oneline': [
|
|
157
|
+
(r'( )(\()', bygroups(Whitespace, Token.Git.Show.Header), 'refs'),
|
|
158
|
+
(r'( )(.*)(\n)', bygroups(
|
|
159
|
+
Whitespace, Token.Git.CommitMessage, Whitespace,
|
|
160
|
+
), '#pop'),
|
|
161
|
+
(r'\n', Whitespace, '#pop'),
|
|
162
|
+
],
|
|
163
|
+
'refs': [
|
|
164
|
+
(r' +', Whitespace),
|
|
165
|
+
(r'->|,', Token.Git.Show.Header),
|
|
166
|
+
(r'\)', Token.Git.Show.Header, '#pop'),
|
|
167
|
+
(_REMOTES + r'/HEAD\b', Token.Git.Refs.RemoteHead),
|
|
168
|
+
(r'HEAD\b', Token.Git.Refs.Head),
|
|
169
|
+
(r'tag: [^\s,()]+', Token.Git.Refs.Tag),
|
|
170
|
+
(_REMOTES + r'/[^\s,()]+', Token.Git.Refs.RemoteBranch),
|
|
171
|
+
(r'[^\s,()]+', Token.Git.Refs.Branch),
|
|
172
|
+
],
|
|
173
|
+
# Patch output (`git show`, `git log -p`) lasts until the next commit.
|
|
174
|
+
'diff': [
|
|
175
|
+
(r'^(?!commit [0-9a-f]+).*\n', using(DiffLexer)),
|
|
176
|
+
default('#pop'),
|
|
177
|
+
],
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class GitShowLexer(GitLogLexer):
|
|
182
|
+
"""Output of `git show`. Same format as `git log -p`."""
|
|
183
|
+
|
|
184
|
+
name = 'Git Show'
|
|
185
|
+
aliases = ['git-show']
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class GitPrettyLogLexer(Lexer):
|
|
189
|
+
"""Output of `git log --graph` using the format described in the README."""
|
|
190
|
+
|
|
191
|
+
name = 'Git Pretty Log'
|
|
192
|
+
aliases = ['git-pretty-log']
|
|
193
|
+
version_added = '1.0'
|
|
194
|
+
|
|
195
|
+
_branch_line = r'([\|\\\/ _]*)'
|
|
196
|
+
_letters = r'0-9A-Za-zÀ-ÖØ-öø-ÿ'
|
|
197
|
+
_log_re = re.compile(''.join([
|
|
198
|
+
_branch_line, # 1. Branch line
|
|
199
|
+
r'(\*)', # 2. Commit asterisk
|
|
200
|
+
_branch_line, # 3. Branch line
|
|
201
|
+
r'( +)', # 4. Space
|
|
202
|
+
r'([a-f0-9]+)', # 5. Commit hash
|
|
203
|
+
r'( +)', # 6. Space
|
|
204
|
+
r'(-)', # 7. Commit separator
|
|
205
|
+
r'( +)', # 8. Space
|
|
206
|
+
rf'(\([{_letters} ]+\))', # 9. Date
|
|
207
|
+
r'(\s+)', # 10. Space
|
|
208
|
+
r'([^\n]+)', # 11. Commit message
|
|
209
|
+
r'( +)', # 12. Space
|
|
210
|
+
r'(-)', # 13. Commit separator
|
|
211
|
+
r'( +)', # 14. Space
|
|
212
|
+
rf'([{_letters} ]*[{_letters}]+)', # 15. Author
|
|
213
|
+
r'( *)', # 16. Space
|
|
214
|
+
r'(\([\w ->,:/.]+\))?', # 17. Refs
|
|
215
|
+
r'( *)', # 18. Space
|
|
216
|
+
r'(#[^\n]*)?', # 19. Comment
|
|
217
|
+
r'\n',
|
|
218
|
+
]))
|
|
219
|
+
_branch_line_re = re.compile(_branch_line + r'\n?$')
|
|
220
|
+
|
|
221
|
+
_log_tokens = {
|
|
222
|
+
1: Token.Git.BranchLine,
|
|
223
|
+
3: Token.Git.BranchLine,
|
|
224
|
+
4: Whitespace,
|
|
225
|
+
5: Token.Git.CommitHash,
|
|
226
|
+
6: Whitespace,
|
|
227
|
+
8: Whitespace,
|
|
228
|
+
9: Token.Git.CommitDate,
|
|
229
|
+
10: Whitespace,
|
|
230
|
+
11: Token.Git.CommitMessage,
|
|
231
|
+
12: Whitespace,
|
|
232
|
+
14: Whitespace,
|
|
233
|
+
15: Token.Git.CommitAuthor,
|
|
234
|
+
16: Whitespace,
|
|
235
|
+
17: Token.Git.Refs,
|
|
236
|
+
18: Whitespace,
|
|
237
|
+
19: Comment,
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
def get_tokens_unprocessed(self, text):
|
|
241
|
+
for match in line_re.finditer(text):
|
|
242
|
+
line = match.group()
|
|
243
|
+
start = match.start()
|
|
244
|
+
|
|
245
|
+
match_log = self._log_re.match(line)
|
|
246
|
+
if match_log:
|
|
247
|
+
for i in range(1, self._log_re.groups + 1):
|
|
248
|
+
if match_log.group(i):
|
|
249
|
+
token = self._log_tokens.get(i, Generic.Output)
|
|
250
|
+
yield start + match_log.start(i), token, match_log.group(i)
|
|
251
|
+
yield start + match_log.end() - 1, Whitespace, '\n'
|
|
252
|
+
elif self._branch_line_re.match(line):
|
|
253
|
+
yield start, Token.Git.BranchLine, line
|
|
254
|
+
else:
|
|
255
|
+
yield start, Generic.Output, line
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class GitBranchLexer(RegexLexer):
|
|
259
|
+
"""Output of `git branch`: highlights the current branch."""
|
|
260
|
+
|
|
261
|
+
name = 'Git Branch'
|
|
262
|
+
aliases = ['git-branch']
|
|
263
|
+
version_added = '1.0'
|
|
264
|
+
|
|
265
|
+
tokens = {
|
|
266
|
+
'root': [
|
|
267
|
+
(r'^\n', Whitespace),
|
|
268
|
+
(r'^(\* )(.*?)(\n)', bygroups(
|
|
269
|
+
using(GitLexer), Token.Git.Refs.Branch, Whitespace,
|
|
270
|
+
)),
|
|
271
|
+
(r'^.*\n', using(GitLexer)),
|
|
272
|
+
],
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class GitMergeLexer(RegexLexer):
|
|
277
|
+
"""Diffstat output, as printed by `git merge`, `git pull`, `--stat`..."""
|
|
278
|
+
|
|
279
|
+
name = 'Git Merge'
|
|
280
|
+
aliases = ['git-merge', 'git-stat']
|
|
281
|
+
version_added = '1.0'
|
|
282
|
+
|
|
283
|
+
tokens = {
|
|
284
|
+
'root': [
|
|
285
|
+
(r'^\n', Whitespace),
|
|
286
|
+
(r'^( \S+ +\| +\d+ )(\+*)(\-*)(\n)', bygroups(
|
|
287
|
+
Generic.Output, Generic.Inserted, Generic.Deleted, Whitespace,
|
|
288
|
+
)),
|
|
289
|
+
(r'^.*\n', using(GitLexer)),
|
|
290
|
+
],
|
|
291
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Lexer for interactive shell sessions with highlighted prompts and output."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from pygments.lexer import Lexer, do_insertions, line_re
|
|
6
|
+
from pygments.lexers.diff import DiffLexer
|
|
7
|
+
from pygments.lexers.shell import BashLexer
|
|
8
|
+
from pygments.token import STANDARD_TYPES, Generic, Token
|
|
9
|
+
|
|
10
|
+
from .git import GitBranchLexer, GitLexer, GitLogLexer, GitMergeLexer, \
|
|
11
|
+
GitPrettyLogLexer, GitShortStatusLexer, GitShowLexer, GitStatusLexer
|
|
12
|
+
|
|
13
|
+
__all__ = ['ShellConsoleLexer']
|
|
14
|
+
|
|
15
|
+
STANDARD_TYPES.update({
|
|
16
|
+
Token.Generic.Prompt.Whitespace: 'gp-w',
|
|
17
|
+
Token.Generic.Prompt.UserHost: 'gp-uh',
|
|
18
|
+
Token.Generic.Prompt.Directory: 'gp-d',
|
|
19
|
+
Token.Generic.Prompt.GitBranch: 'gp-gb',
|
|
20
|
+
Token.Generic.Prompt.VirtualEnv: 'gp-ve',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
# Git global options that take a separate value, e.g. `git -C repo status`.
|
|
24
|
+
_GIT_OPTIONS_WITH_VALUE = {'-C', '-c', '--git-dir', '--work-tree', '--namespace'}
|
|
25
|
+
|
|
26
|
+
# Unterminated here-document, e.g. `cat <<EOF` (but not `<<<` here-strings).
|
|
27
|
+
_HEREDOC_RE = re.compile(r"(?<!<)<<(?!<)-?\s*(['\"]?)([A-Za-z_]\w*)\1")
|
|
28
|
+
|
|
29
|
+
# Line ending in a pipe or a list operator, so bash keeps reading.
|
|
30
|
+
_TRAILING_OPERATOR_RE = re.compile(r'(?:\|\|?|&&)\s*\n\Z')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _git_args(words):
|
|
34
|
+
"""Return the arguments after `git` and its global options."""
|
|
35
|
+
args = words[1:]
|
|
36
|
+
while args and args[0].startswith('-'):
|
|
37
|
+
option = args.pop(0)
|
|
38
|
+
if option in _GIT_OPTIONS_WITH_VALUE and args:
|
|
39
|
+
args.pop(0)
|
|
40
|
+
return args
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _output_lexer_class(command):
|
|
44
|
+
"""Choose the lexer for the output of `command`, or None for plain output."""
|
|
45
|
+
words = command.split()
|
|
46
|
+
if words and words[0] == 'sudo':
|
|
47
|
+
words = words[1:]
|
|
48
|
+
if not words:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
if words[0] in ('diff', 'colordiff'):
|
|
52
|
+
return DiffLexer
|
|
53
|
+
if words[0] == 'cat' and any(w.endswith(('.diff', '.patch')) for w in words):
|
|
54
|
+
return DiffLexer
|
|
55
|
+
if words[0] != 'git':
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
args = _git_args(words)
|
|
59
|
+
if not args:
|
|
60
|
+
return GitLexer
|
|
61
|
+
subcommand, options = args[0], args[1:]
|
|
62
|
+
|
|
63
|
+
if subcommand == 'diff':
|
|
64
|
+
return GitMergeLexer if '--stat' in options else DiffLexer
|
|
65
|
+
if subcommand == 'show':
|
|
66
|
+
return GitShowLexer
|
|
67
|
+
if subcommand.startswith('lg') or (subcommand == 'log' and '--graph' in options):
|
|
68
|
+
return GitPrettyLogLexer
|
|
69
|
+
if subcommand == 'log':
|
|
70
|
+
return GitLogLexer
|
|
71
|
+
if subcommand == 'status':
|
|
72
|
+
short = any(o in ('--short', '--porcelain') or re.fullmatch(r'-[a-z]*s[a-z]*', o)
|
|
73
|
+
for o in options)
|
|
74
|
+
return GitShortStatusLexer if short else GitStatusLexer
|
|
75
|
+
if subcommand == 'stash' and options:
|
|
76
|
+
if options[0] in ('apply', 'pop'):
|
|
77
|
+
return GitStatusLexer
|
|
78
|
+
if options[0] == 'show':
|
|
79
|
+
return DiffLexer if {'-p', '--patch'} & set(options) else GitMergeLexer
|
|
80
|
+
if subcommand == 'branch':
|
|
81
|
+
return GitBranchLexer
|
|
82
|
+
if subcommand in ('merge', 'pull'):
|
|
83
|
+
return GitMergeLexer
|
|
84
|
+
return GitLexer
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _continues(code):
|
|
88
|
+
"""Whether bash would print PS2 and keep reading after `code`."""
|
|
89
|
+
if code.endswith('\\\n') or _TRAILING_OPERATOR_RE.search(code):
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
pending = []
|
|
93
|
+
for line in code.splitlines():
|
|
94
|
+
if pending:
|
|
95
|
+
if line.strip() == pending[0]:
|
|
96
|
+
pending.pop(0)
|
|
97
|
+
else:
|
|
98
|
+
pending.extend(m.group(2) for m in _HEREDOC_RE.finditer(line))
|
|
99
|
+
return bool(pending)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ShellConsoleLexer(Lexer):
|
|
103
|
+
"""
|
|
104
|
+
Shell session where prompts are split into their parts (virtualenv,
|
|
105
|
+
user@host, directory, git branch) and the output of known commands
|
|
106
|
+
(diff, git) is highlighted.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
name = 'Shell Console'
|
|
110
|
+
aliases = ['shellconsole', 'shell-console']
|
|
111
|
+
filenames = ['*.sh-session', '*.shell-session']
|
|
112
|
+
mimetypes = ['application/x-shell-session', 'application/x-sh-session']
|
|
113
|
+
url = 'https://en.wikipedia.org/wiki/Unix_shell'
|
|
114
|
+
version_added = '1.0'
|
|
115
|
+
|
|
116
|
+
_ps1_re = re.compile(r'''
|
|
117
|
+
(?P<venv>\([^)]*\))?
|
|
118
|
+
(?P<ws1>\s*)
|
|
119
|
+
(?P<open>\[)?
|
|
120
|
+
(?:
|
|
121
|
+
(?P<user_host>[^\n$%\#]+@[^\s$%\#]+?)
|
|
122
|
+
(?:(?P<ws2>\s*)(?P<colon>:)(?P<ws3>\s*)|(?P<ws4>\s+))
|
|
123
|
+
(?P<current_dir>[^\s\]:$%\#]+)
|
|
124
|
+
)?
|
|
125
|
+
(?:(?P<ws5>\s+)(?P<git_branch>\([^)]+\)))?
|
|
126
|
+
(?P<close>\])?
|
|
127
|
+
(?P<ws6>\s*)
|
|
128
|
+
# `#` (root) is only accepted after user@host, so comments aren't prompts.
|
|
129
|
+
(?P<sigil>(?:[$%]|(?(user_host)\#|(?!)))\n?)
|
|
130
|
+
(?P<ws7>\s*)
|
|
131
|
+
(?P<command>.*\n?)
|
|
132
|
+
''', re.VERBOSE)
|
|
133
|
+
|
|
134
|
+
_ps1_tokens = {
|
|
135
|
+
'venv': Generic.Prompt.VirtualEnv,
|
|
136
|
+
'user_host': Generic.Prompt.UserHost,
|
|
137
|
+
'current_dir': Generic.Prompt.Directory,
|
|
138
|
+
'git_branch': Generic.Prompt.GitBranch,
|
|
139
|
+
'open': Generic.Prompt,
|
|
140
|
+
'close': Generic.Prompt,
|
|
141
|
+
'colon': Generic.Prompt,
|
|
142
|
+
'sigil': Generic.Prompt,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
_ps2 = '> '
|
|
146
|
+
|
|
147
|
+
def get_tokens_unprocessed(self, text):
|
|
148
|
+
innerlexer = BashLexer(**self.options)
|
|
149
|
+
output_lexers = {}
|
|
150
|
+
|
|
151
|
+
# Command being collected: start position, code and prompt insertions.
|
|
152
|
+
command_pos = 0
|
|
153
|
+
curcode = ''
|
|
154
|
+
insertions = []
|
|
155
|
+
# Output of the last command: start position, text and lexer class.
|
|
156
|
+
output_pos = 0
|
|
157
|
+
output = ''
|
|
158
|
+
output_lexer_cls = None
|
|
159
|
+
|
|
160
|
+
def flush_command():
|
|
161
|
+
for i, t, v in do_insertions(
|
|
162
|
+
insertions, innerlexer.get_tokens_unprocessed(curcode)):
|
|
163
|
+
yield command_pos + i, t, v
|
|
164
|
+
|
|
165
|
+
def flush_output():
|
|
166
|
+
if output_lexer_cls is None:
|
|
167
|
+
yield output_pos, Generic.Output, output
|
|
168
|
+
return
|
|
169
|
+
if output_lexer_cls not in output_lexers:
|
|
170
|
+
output_lexers[output_lexer_cls] = output_lexer_cls()
|
|
171
|
+
lexer = output_lexers[output_lexer_cls]
|
|
172
|
+
for i, t, v in lexer.get_tokens_unprocessed(output):
|
|
173
|
+
yield output_pos + i, t, v
|
|
174
|
+
|
|
175
|
+
for match in line_re.finditer(text):
|
|
176
|
+
line = match.group()
|
|
177
|
+
|
|
178
|
+
# Continuation of a multi-line command (backslash, heredoc...)
|
|
179
|
+
if insertions and _continues(curcode):
|
|
180
|
+
if line.startswith(self._ps2):
|
|
181
|
+
insertions.append((len(curcode),
|
|
182
|
+
[(0, Generic.Prompt, self._ps2)]))
|
|
183
|
+
line = line[len(self._ps2):]
|
|
184
|
+
curcode += line
|
|
185
|
+
|
|
186
|
+
elif m := self._ps1_re.match(line):
|
|
187
|
+
if output:
|
|
188
|
+
yield from flush_output()
|
|
189
|
+
output = ''
|
|
190
|
+
|
|
191
|
+
command_pos = match.start()
|
|
192
|
+
curcode = m.group('command')
|
|
193
|
+
prompt = [
|
|
194
|
+
(0, self._ps1_tokens.get(name, Generic.Prompt.Whitespace), value)
|
|
195
|
+
for name, value in m.groupdict().items()
|
|
196
|
+
if value and name != 'command'
|
|
197
|
+
]
|
|
198
|
+
insertions = [(0, prompt)]
|
|
199
|
+
|
|
200
|
+
else:
|
|
201
|
+
if not output:
|
|
202
|
+
output_pos = match.start()
|
|
203
|
+
output_lexer_cls = _output_lexer_class(curcode)
|
|
204
|
+
output += line
|
|
205
|
+
|
|
206
|
+
if insertions and not _continues(curcode):
|
|
207
|
+
yield from flush_command()
|
|
208
|
+
insertions = []
|
|
209
|
+
|
|
210
|
+
if insertions:
|
|
211
|
+
yield from flush_command()
|
|
212
|
+
if output:
|
|
213
|
+
yield from flush_output()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Pygments style for the shell console and git tokens."""
|
|
2
|
+
|
|
3
|
+
from pygments.style import Style
|
|
4
|
+
from pygments.styles.default import DefaultStyle
|
|
5
|
+
from pygments.token import Generic, Token
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ShellConsoleStyle(Style):
|
|
9
|
+
styles = DefaultStyle.styles.copy()
|
|
10
|
+
styles.update({
|
|
11
|
+
Generic.Prompt.UserHost: "#008000",
|
|
12
|
+
Generic.Prompt.Directory: "#19177C",
|
|
13
|
+
Generic.Prompt.GitBranch: "#767600",
|
|
14
|
+
Generic.Prompt.VirtualEnv: "#800000",
|
|
15
|
+
|
|
16
|
+
Token.Git.BranchLine: "#f00",
|
|
17
|
+
Token.Git.CommitHash: "#00f",
|
|
18
|
+
Token.Git.CommitDate: "#008000",
|
|
19
|
+
Token.Git.CommitAuthor: "#aaaaaa",
|
|
20
|
+
Token.Git.Hint: "#aaaa00",
|
|
21
|
+
Token.Git.Warning: "#aaaa00",
|
|
22
|
+
Token.Git.Error: "#f00",
|
|
23
|
+
Token.Git.Fatal: "bold #f00",
|
|
24
|
+
Token.Git.Show.Header: "#aaaa00",
|
|
25
|
+
|
|
26
|
+
Token.Git.Refs: "#00ffff",
|
|
27
|
+
Token.Git.Refs.Tag: "#ffff00",
|
|
28
|
+
Token.Git.Refs.Head: "#00ffff",
|
|
29
|
+
Token.Git.Refs.Branch: "#00ff00",
|
|
30
|
+
Token.Git.Refs.RemoteHead: "#ffcc00",
|
|
31
|
+
Token.Git.Refs.RemoteBranch: "#ffcc00",
|
|
32
|
+
|
|
33
|
+
Token.Git.Untracked: "#f00",
|
|
34
|
+
Token.Git.Modified: "#f00",
|
|
35
|
+
Token.Git.Unmerged: "#f00",
|
|
36
|
+
Token.Git.Staged: "#008000",
|
|
37
|
+
})
|
|
38
|
+
del styles[Token.Generic.Prompt]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pygments-shell-console
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Pygments lexers for shell console sessions, with highlighted prompts and git output
|
|
5
|
+
Project-URL: Homepage, https://github.com/joapuiib/pygments-shell-console
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Requires-Dist: pygments
|
|
8
|
+
Provides-Extra: test
|
|
9
|
+
Requires-Dist: pytest; extra == 'test'
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Pygments Shell Console
|
|
13
|
+
[](https://www.python.org/)
|
|
14
|
+
[](https://pypi.org/project/pygments-shell-console/)
|
|
15
|
+
|
|
16
|
+
Pygments lexers for shell console sessions: highlighted prompts and git output.
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
pip install pygments-shell-console
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
Use the `shellconsole` lexer with the `shellconsole-style` style:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
pygmentize -l shellconsole -O style=shellconsole-style session.txt
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
In Markdown (MkDocs, Sphinx...), use `shellconsole` as the code block language:
|
|
35
|
+
|
|
36
|
+
````
|
|
37
|
+
```shellconsole
|
|
38
|
+
user@host:~/project (main) $ git status
|
|
39
|
+
```
|
|
40
|
+
````
|
|
41
|
+
|
|
42
|
+
## Prompt
|
|
43
|
+
|
|
44
|
+
The prompt elements are highlighted separately:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
(venv) user@host:~/project (main) $
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Virtualenv, `user@host`, directory and git branch. Also supported:
|
|
51
|
+
|
|
52
|
+
- Root prompts (`root@host:~#`) and `[user@host ~]` style prompts.
|
|
53
|
+
- Continuation lines: backslash, trailing `|`, `&&` or `||`, and here-documents.
|
|
54
|
+
|
|
55
|
+
## Output
|
|
56
|
+
|
|
57
|
+
The output lexer is chosen from the command. `sudo` and git global options
|
|
58
|
+
(`-C dir`, `--no-pager`...) are skipped.
|
|
59
|
+
|
|
60
|
+
| Command | Lexer | Alias |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `diff`, `git diff`, `git stash show -p` | `DiffLexer` | |
|
|
63
|
+
| `git status` | `GitStatusLexer` | `git-status` |
|
|
64
|
+
| `git status -s`, `--porcelain` | `GitShortStatusLexer` | `git-short-status` |
|
|
65
|
+
| `git show` | `GitShowLexer` | `git-show` |
|
|
66
|
+
| `git log`, `--oneline`, `-p` | `GitLogLexer` | `git-log` |
|
|
67
|
+
| `git lg*`, `git log --graph` | `GitPrettyLogLexer` | `git-pretty-log` |
|
|
68
|
+
| `git branch` | `GitBranchLexer` | `git-branch` |
|
|
69
|
+
| `git merge`, `pull`, `stash show`, `diff --stat` | `GitMergeLexer` | `git-merge` |
|
|
70
|
+
| any other `git` command | `GitLexer` | `git` |
|
|
71
|
+
|
|
72
|
+
The git lexers can also be used on their own through their alias.
|
|
73
|
+
|
|
74
|
+
`git lg*` expects this format:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
More examples: [mkdocs-material-joapuiib](https://joapuiib.github.io/mkdocs-material-joapuiib/features/blocs_de_codi/#bloc-de-consola).
|
|
81
|
+
|
|
82
|
+
## Development
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
pip install -e '.[test]'
|
|
86
|
+
pytest
|
|
87
|
+
```
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pygments_shell_console/__init__.py,sha256=8Z_8TLhJoEHyDGEF47CLP64A-Cmwq0LSZ9-CwbD_goE,228
|
|
2
|
+
pygments_shell_console/git.py,sha256=6OQqV4L_Y4IzvW1l0StXVu3zjiM79ZxW0jd_MZpqIxc,9619
|
|
3
|
+
pygments_shell_console/shell.py,sha256=Cs8kKo6ssTXKSEz0QI-tOnv9N1amAk0Uqzq4JHqW-uU,7341
|
|
4
|
+
pygments_shell_console/style.py,sha256=ZIU2dzyHEUTCUiN-ayHb4qsFe5QSRfH0NVev4NWRJ-w,1247
|
|
5
|
+
pygments_shell_console-0.9.0.dist-info/METADATA,sha256=TjvkDHtQ4k5R_xKsQtP7njSN_jIrew-1YCLpIM5_Je8,2766
|
|
6
|
+
pygments_shell_console-0.9.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
7
|
+
pygments_shell_console-0.9.0.dist-info/entry_points.txt,sha256=buFWIELViJ-euWM8pJwmN9B_aXom1f4DcFz6v_E_918,600
|
|
8
|
+
pygments_shell_console-0.9.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[pygments.lexers]
|
|
2
|
+
git = pygments_shell_console.git:GitLexer
|
|
3
|
+
git-branch = pygments_shell_console.git:GitBranchLexer
|
|
4
|
+
git-log = pygments_shell_console.git:GitLogLexer
|
|
5
|
+
git-merge = pygments_shell_console.git:GitMergeLexer
|
|
6
|
+
git-pretty-log = pygments_shell_console.git:GitPrettyLogLexer
|
|
7
|
+
git-short-status = pygments_shell_console.git:GitShortStatusLexer
|
|
8
|
+
git-show = pygments_shell_console.git:GitShowLexer
|
|
9
|
+
git-status = pygments_shell_console.git:GitStatusLexer
|
|
10
|
+
shellconsole = pygments_shell_console.shell:ShellConsoleLexer
|
|
11
|
+
|
|
12
|
+
[pygments.styles]
|
|
13
|
+
shellconsole-style = pygments_shell_console.style:ShellConsoleStyle
|