streamdown 0.13.0__py3-none-any.whl → 0.14.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.
- streamdown/scrape/file_0.py +22 -0
- streamdown/scrape/file_1.js +27 -0
- streamdown/scrape/file_2.cpp +23 -0
- streamdown/sd.py +116 -77
- {streamdown-0.13.0.dist-info → streamdown-0.14.0.dist-info}/METADATA +6 -2
- streamdown-0.14.0.dist-info/RECORD +13 -0
- streamdown-0.13.0.dist-info/RECORD +0 -10
- {streamdown-0.13.0.dist-info → streamdown-0.14.0.dist-info}/WHEEL +0 -0
- {streamdown-0.13.0.dist-info → streamdown-0.14.0.dist-info}/entry_points.txt +0 -0
- {streamdown-0.13.0.dist-info → streamdown-0.14.0.dist-info}/licenses/LICENSE.MIT +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
def fizzbuzz(n):
|
|
2
|
+
for i in range(1, n + 1):
|
|
3
|
+
if i % 3 == 0 and i % 5 == 0:
|
|
4
|
+
print("FizzBuzz")
|
|
5
|
+
elif i % 3 == 0:
|
|
6
|
+
print("Fizz")
|
|
7
|
+
elif i % 5 == 0:
|
|
8
|
+
print("Buzz")
|
|
9
|
+
else:
|
|
10
|
+
print(i)
|
|
11
|
+
|
|
12
|
+
# Example usage: Print FizzBuzz up to 100 Example usage: Print FizzBuzz up to 100 Example usage: Print FizzBuzz up to 100 Example usage: Print FizzBuzz up to 100
|
|
13
|
+
fizzbuzz(100)
|
|
14
|
+
|
|
15
|
+
# Example usage: different range:
|
|
16
|
+
fizzbuzz(20)
|
|
17
|
+
|
|
18
|
+
#Example usage: one line output (list comprehension)
|
|
19
|
+
def fizzbuzz_oneline(n):
|
|
20
|
+
print(["FizzBuzz" if i%3==0 and i%5==0 else "Fizz" if i%3==0 else "Buzz" if i%5==0 else i for i in range(1,n+1)])
|
|
21
|
+
|
|
22
|
+
fizzbuzz_oneline(30)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function fizzBuzz(n) {
|
|
2
|
+
for (let i = 1; i <= n; i++) {
|
|
3
|
+
if (i % 3 === 0 && i % 5 === 0) {
|
|
4
|
+
console.log("FizzBuzz");
|
|
5
|
+
} else if (i % 3 === 0) {
|
|
6
|
+
console.log("Fizz");
|
|
7
|
+
} else if (i % 5 === 0) {
|
|
8
|
+
console.log("Buzz");
|
|
9
|
+
} else {
|
|
10
|
+
console.log(i);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Example usage:
|
|
16
|
+
fizzBuzz(100);
|
|
17
|
+
|
|
18
|
+
// Example usage: different range
|
|
19
|
+
fizzBuzz(25);
|
|
20
|
+
|
|
21
|
+
// Example one-line output. (arrow function & ternary operator)
|
|
22
|
+
const fizzBuzzOneLine = n => {
|
|
23
|
+
for (let i = 1; i <= n; i++) {
|
|
24
|
+
console.log((i % 3 === 0 ? (i % 5 === 0 ? "FizzBuzz" : "Fizz") : (i % 5 === 0 ? "Buzz" : i)));
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
fizzBuzzOneLine(30);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#include <iostream>
|
|
2
|
+
|
|
3
|
+
void fizzBuzz(int n) {
|
|
4
|
+
for (int i = 1; i <= n; i++) {
|
|
5
|
+
if (i % 3 == 0 && i % 5 == 0) {
|
|
6
|
+
std::cout << "FizzBuzz" << std::endl;
|
|
7
|
+
} else if (i % 3 == 0) {
|
|
8
|
+
std::cout << "Fizz" << std::endl;
|
|
9
|
+
} else if (i % 5 == 0) {
|
|
10
|
+
std::cout << "Buzz" << std::endl;
|
|
11
|
+
} else {
|
|
12
|
+
std::cout << i << std::endl;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
int main() {
|
|
18
|
+
fizzBuzz(100);
|
|
19
|
+
|
|
20
|
+
// Example usage: different range
|
|
21
|
+
fizzBuzz(35);
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
streamdown/sd.py
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
# "pygments",
|
|
6
6
|
# "pylatexenc",
|
|
7
7
|
# "appdirs",
|
|
8
|
+
# "term-image",
|
|
8
9
|
# "toml"
|
|
9
10
|
# ]
|
|
10
11
|
# ///
|
|
@@ -23,6 +24,7 @@ import colorsys
|
|
|
23
24
|
import base64
|
|
24
25
|
import importlib
|
|
25
26
|
from io import BytesIO
|
|
27
|
+
from term_image.image import from_file, from_url
|
|
26
28
|
import pygments.util
|
|
27
29
|
from argparse import ArgumentParser
|
|
28
30
|
from pygments import highlight
|
|
@@ -52,7 +54,7 @@ Dark = { H = 1.00, S = 1.50, V = 0.25 }
|
|
|
52
54
|
Mid = { H = 1.00, S = 1.00, V = 0.50 }
|
|
53
55
|
Symbol = { H = 1.00, S = 1.00, V = 1.50 }
|
|
54
56
|
Head = { H = 1.00, S = 2.00, V = 1.50 }
|
|
55
|
-
Grey = { H = 1.00, S = 0.
|
|
57
|
+
Grey = { H = 1.00, S = 0.25, V = 1.37 }
|
|
56
58
|
Bright = { H = 1.00, S = 2.00, V = 2.00 }
|
|
57
59
|
Syntax = "monokai"
|
|
58
60
|
"""
|
|
@@ -80,10 +82,11 @@ BGRESET = "\033[49m"
|
|
|
80
82
|
BOLD = ["\033[1m", "\033[22m"]
|
|
81
83
|
UNDERLINE = ["\033[4m", "\033[24m"]
|
|
82
84
|
ITALIC = ["\033[3m", "\033[23m"]
|
|
85
|
+
STRIKEOUT = ["\033[9m", "\033[29m"]
|
|
86
|
+
SUPER = [ 0x2070, 0x00B9, 0x00B2, 0x00B3, 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, 0x2079 ]
|
|
83
87
|
|
|
84
88
|
ESCAPE = r"\033\[[0-9;]*[mK]"
|
|
85
89
|
ANSIESCAPE = r'\033(?:\[[0-9;?]*[a-zA-Z]|][0-9]*;;.*?\\|\\)'
|
|
86
|
-
#r"\033(\[[0-9;]*[mK]|][0-9]*;;.*?\\|\\)"
|
|
87
90
|
KEYCODE_RE = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
|
88
91
|
|
|
89
92
|
visible = lambda x: re.sub(ANSIESCAPE, "", x)
|
|
@@ -131,7 +134,10 @@ class ParseState:
|
|
|
131
134
|
self.Clipboard = _features.get("Clipboard")
|
|
132
135
|
self.Logging = _features.get("Logging")
|
|
133
136
|
self.Timeout = _features.get("Timeout")
|
|
137
|
+
|
|
134
138
|
self.WidthArg = None
|
|
139
|
+
self.WidthFull = None
|
|
140
|
+
self.WidthWrap = False
|
|
135
141
|
|
|
136
142
|
# If the entire block is indented this will
|
|
137
143
|
# tell us what that is
|
|
@@ -159,24 +165,27 @@ class ParseState:
|
|
|
159
165
|
self.in_italic = False
|
|
160
166
|
self.in_table = False # (Code.[Header|Body] | False)
|
|
161
167
|
self.in_underline = False
|
|
168
|
+
self.in_strikeout = False
|
|
162
169
|
self.block_depth = 0
|
|
163
170
|
|
|
164
171
|
self.exec_sub = None
|
|
165
172
|
self.exec_master = None
|
|
166
173
|
self.exec_slave = None
|
|
167
174
|
self.exec_kb = 0
|
|
168
|
-
self.exec_israw = False
|
|
169
175
|
|
|
170
176
|
self.exit = 0
|
|
171
177
|
self.where_from = None
|
|
172
178
|
|
|
173
179
|
def current(self):
|
|
174
|
-
state = { 'code': self.in_code, 'bold': self.in_bold, 'italic': self.in_italic, 'underline': self.in_underline }
|
|
180
|
+
state = { 'inline': self.inline_code, 'code': self.in_code, 'bold': self.in_bold, 'italic': self.in_italic, 'underline': self.in_underline }
|
|
175
181
|
state['none'] = all(item is False for item in state.values())
|
|
176
182
|
return state
|
|
177
183
|
|
|
184
|
+
def reset_inline(self):
|
|
185
|
+
self.inline_code = self.in_bold = self.in_italic = self.in_underline = False
|
|
186
|
+
|
|
178
187
|
def space_left(self):
|
|
179
|
-
return
|
|
188
|
+
return Style.MarginSpaces + (Style.Blockquote * self.block_depth) if len(self.current_line) == 0 else ""
|
|
180
189
|
|
|
181
190
|
state = ParseState()
|
|
182
191
|
|
|
@@ -233,17 +242,20 @@ def emit_h(level, text):
|
|
|
233
242
|
text = line_format(text)
|
|
234
243
|
spaces_to_center = ((state.Width - visible_length(text)) / 2)
|
|
235
244
|
if level == 1: #
|
|
236
|
-
return f"\n{
|
|
245
|
+
return f"\n{state.space_left()}{BOLD[0]}{' ' * math.floor(spaces_to_center)}{text}{' ' * math.ceil(spaces_to_center)}{BOLD[1]}\n"
|
|
237
246
|
elif level == 2: ##
|
|
238
|
-
return f"\n{
|
|
247
|
+
return f"\n{state.space_left()}{BOLD[0]}{FG}{Style.Bright}{' ' * math.floor(spaces_to_center)}{text}{' ' * math.ceil(spaces_to_center)}{RESET}\n\n"
|
|
239
248
|
elif level == 3: ###
|
|
240
|
-
return f"{
|
|
249
|
+
return f"{state.space_left()}{FG}{Style.Head}{BOLD[0]}{text}{RESET}"
|
|
241
250
|
elif level == 4: ####
|
|
242
|
-
return f"{
|
|
251
|
+
return f"{state.space_left()}{FG}{Style.Symbol}{text}{RESET}"
|
|
243
252
|
else: # level 5 or 6
|
|
244
|
-
return f"{
|
|
253
|
+
return f"{state.space_left()}{text}{RESET}"
|
|
245
254
|
|
|
246
255
|
def code_wrap(text_in):
|
|
256
|
+
if state.WidthWrap and len(text_in) > state.WidthFull:
|
|
257
|
+
return (0, [text_in])
|
|
258
|
+
|
|
247
259
|
# get the indentation of the first line
|
|
248
260
|
indent = len(text_in) - len(text_in.lstrip())
|
|
249
261
|
text = text_in.lstrip()
|
|
@@ -337,8 +349,20 @@ def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_lin
|
|
|
337
349
|
return lines
|
|
338
350
|
|
|
339
351
|
def line_format(line):
|
|
340
|
-
|
|
341
|
-
|
|
352
|
+
not_text = lambda token: not token or len(token.rstrip()) != len(token)
|
|
353
|
+
footnotes = lambda match: ''.join([chr(SUPER[int(i)]) for i in match.group(1)])
|
|
354
|
+
|
|
355
|
+
def process_images(match):
|
|
356
|
+
url = match.group(2)
|
|
357
|
+
try:
|
|
358
|
+
if re.match(r"https://", url.lower()):
|
|
359
|
+
image = from_url(url)
|
|
360
|
+
else:
|
|
361
|
+
image = from_file(url)
|
|
362
|
+
image.height = 20
|
|
363
|
+
print(f"{image:|.-1#}")
|
|
364
|
+
except:
|
|
365
|
+
return match.group(2)
|
|
342
366
|
|
|
343
367
|
# Apply OSC 8 hyperlink formatting after other formatting
|
|
344
368
|
def process_links(match):
|
|
@@ -346,8 +370,11 @@ def line_format(line):
|
|
|
346
370
|
url = match.group(2)
|
|
347
371
|
return f'\033]8;;{url}\033\\{Style.Link}{description}{UNDERLINE[1]}\033]8;;\033\\{FGRESET}'
|
|
348
372
|
|
|
373
|
+
line = re.sub(r"\!\[([^\]]*)\]\(([^\)]+)\)", process_images, line)
|
|
349
374
|
line = re.sub(r"\[([^\]]+)\]\(([^\)]+)\)", process_links, line)
|
|
350
|
-
|
|
375
|
+
line = re.sub(r"\[\^(\d+)\]:?", footnotes, line)
|
|
376
|
+
|
|
377
|
+
tokenList = re.finditer(r"((~~|\*\*_|_\*\*|\*{1,3}|_{1,3}|`+)|[^~_*`]+)", line)
|
|
351
378
|
result = ""
|
|
352
379
|
|
|
353
380
|
for match in tokenList:
|
|
@@ -355,8 +382,13 @@ def line_format(line):
|
|
|
355
382
|
next_token = line[match.end()] if match.end() < len(line) else ""
|
|
356
383
|
prev_token = line[match.start()-1] if match.start() > 0 else ""
|
|
357
384
|
|
|
358
|
-
|
|
359
|
-
|
|
385
|
+
# This trick makes sure that things like `` ` `` render right.
|
|
386
|
+
if "`" in token and (not state.inline_code or state.inline_code == token):
|
|
387
|
+
if state.inline_code:
|
|
388
|
+
state.inline_code = False
|
|
389
|
+
else:
|
|
390
|
+
state.inline_code = token
|
|
391
|
+
|
|
360
392
|
if state.inline_code:
|
|
361
393
|
result += f'{BG}{Style.Mid}'
|
|
362
394
|
else:
|
|
@@ -367,7 +399,17 @@ def line_format(line):
|
|
|
367
399
|
elif state.inline_code:
|
|
368
400
|
result += token
|
|
369
401
|
|
|
370
|
-
elif token ==
|
|
402
|
+
elif token == '~~' and (state.in_strikeout or not_text(prev_token)):
|
|
403
|
+
state.in_strikeout = not state.in_strikeout
|
|
404
|
+
result += STRIKEOUT[0] if state.in_strikeout else STRIKEOUT[1]
|
|
405
|
+
|
|
406
|
+
elif token in ['**_','_**','___','***'] and (state.in_bold or not_text(prev_token)):
|
|
407
|
+
state.in_bold = not state.in_bold
|
|
408
|
+
result += BOLD[0] if state.in_bold else BOLD[1]
|
|
409
|
+
state.in_italic = not state.in_italic
|
|
410
|
+
result += ITALIC[0] if state.in_italic else ITALIC[1]
|
|
411
|
+
|
|
412
|
+
elif (token == '__' or token == "**") and (state.in_bold or not_text(prev_token)):
|
|
371
413
|
state.in_bold = not state.in_bold
|
|
372
414
|
result += BOLD[0] if state.in_bold else BOLD[1]
|
|
373
415
|
|
|
@@ -380,7 +422,7 @@ def line_format(line):
|
|
|
380
422
|
else:
|
|
381
423
|
result += token
|
|
382
424
|
|
|
383
|
-
elif token == "_" and (state.in_underline or not_text(prev_token)):
|
|
425
|
+
elif token == "_" and (state.in_underline or (not_text(prev_token) and next_token.isalnum())):
|
|
384
426
|
state.in_underline = not state.in_underline
|
|
385
427
|
result += UNDERLINE[0] if state.in_underline else UNDERLINE[1]
|
|
386
428
|
else:
|
|
@@ -422,8 +464,6 @@ def parse(stream):
|
|
|
422
464
|
|
|
423
465
|
if len(ready_in) == 0:
|
|
424
466
|
TimeoutIx += 1
|
|
425
|
-
|
|
426
|
-
|
|
427
467
|
|
|
428
468
|
elif stream.fileno() in ready_in:
|
|
429
469
|
byte = os.read(stream.fileno(), 1)
|
|
@@ -462,13 +502,30 @@ def parse(stream):
|
|
|
462
502
|
# Run through the plugins first
|
|
463
503
|
res = latex.Plugin(line, state, Style)
|
|
464
504
|
if res is True:
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
continue
|
|
468
|
-
elif res is not None:
|
|
469
|
-
for row in res:
|
|
470
|
-
yield row
|
|
505
|
+
# This means everything was consumed by our plugin and
|
|
506
|
+
# we should continue
|
|
471
507
|
continue
|
|
508
|
+
elif res is not None:
|
|
509
|
+
for row in res:
|
|
510
|
+
yield row
|
|
511
|
+
continue
|
|
512
|
+
|
|
513
|
+
# running this here avoids stray |
|
|
514
|
+
block_match = re.match(r"^\s*((>\s*)+|<.?think>)", line)
|
|
515
|
+
if not state.in_code and block_match:
|
|
516
|
+
if block_match.group(1) == '</think>':
|
|
517
|
+
state.block_depth = 0
|
|
518
|
+
yield RESET
|
|
519
|
+
elif block_match.group(1) == '<think>':
|
|
520
|
+
state.block_depth = 1
|
|
521
|
+
else:
|
|
522
|
+
state.block_depth = block_match.group(0).count('>')
|
|
523
|
+
# we also need to consume those tokens
|
|
524
|
+
line = line[len(block_match.group(0)):]
|
|
525
|
+
else:
|
|
526
|
+
if state.block_depth > 0:
|
|
527
|
+
line = FGRESET + line
|
|
528
|
+
state.block_depth = 0
|
|
472
529
|
|
|
473
530
|
# --- Collapse Multiple Empty Lines if not in code blocks ---
|
|
474
531
|
if not state.in_code:
|
|
@@ -483,7 +540,7 @@ def parse(stream):
|
|
|
483
540
|
else:
|
|
484
541
|
last_line_empty_cache = state.last_line_empty
|
|
485
542
|
state.last_line_empty = False
|
|
486
|
-
|
|
543
|
+
|
|
487
544
|
# This is to reset our top-level line-based systems
|
|
488
545
|
# \n buffer
|
|
489
546
|
if not state.in_list and len(state.ordered_list_numbers) > 0:
|
|
@@ -506,28 +563,11 @@ def parse(stream):
|
|
|
506
563
|
if state.in_table and not state.in_code and not re.match(r"^\s*\|.+\|\s*$", line):
|
|
507
564
|
state.in_table = False
|
|
508
565
|
|
|
509
|
-
block_match = re.match(r"^((> )*|<.?think>)", line)
|
|
510
|
-
if block_match:
|
|
511
|
-
if block_match.group(1) == '</think>':
|
|
512
|
-
state.block_depth = 0
|
|
513
|
-
yield(RESET)
|
|
514
|
-
elif block_match.group(1) == '<think>':
|
|
515
|
-
state.block_depth = 1
|
|
516
|
-
else:
|
|
517
|
-
state.block_depth = int(len(block_match.group(0)) / 2)
|
|
518
|
-
# we also need to consume those tokens
|
|
519
|
-
line = line[state.block_depth * 2:]
|
|
520
|
-
else:
|
|
521
|
-
if state.block_depth > 0:
|
|
522
|
-
yield RESET
|
|
523
|
-
state.block_depth = 0
|
|
524
|
-
|
|
525
566
|
#
|
|
526
567
|
# <code><pre>
|
|
527
568
|
#
|
|
528
|
-
# This needs to be first
|
|
529
569
|
if not state.in_code:
|
|
530
|
-
code_match = re.match(r"\s*```\s*([^\s]+|$)", line)
|
|
570
|
+
code_match = re.match(r"\s*```\s*([^\s]+|$)$", line)
|
|
531
571
|
if code_match:
|
|
532
572
|
state.in_code = Code.Backtick
|
|
533
573
|
state.code_language = code_match.group(1) or 'Bash'
|
|
@@ -563,6 +603,7 @@ def parse(stream):
|
|
|
563
603
|
try:
|
|
564
604
|
ext = get_lexer_by_name(state.code_language).filenames[0].split('.')[-1]
|
|
565
605
|
except:
|
|
606
|
+
logging.warning(f"Can't find canonical extension for {state.code_language}")
|
|
566
607
|
pass
|
|
567
608
|
|
|
568
609
|
open(os.path.join(state.scrape, f"file_{state.scrape_ix}.{ext}"), 'w').write(state.code_buffer)
|
|
@@ -584,7 +625,8 @@ def parse(stream):
|
|
|
584
625
|
|
|
585
626
|
|
|
586
627
|
if code_type == Code.Backtick:
|
|
587
|
-
|
|
628
|
+
state.code_indent = len(line) - len(line.lstrip())
|
|
629
|
+
continue
|
|
588
630
|
else:
|
|
589
631
|
# otherwise we don't want to consume
|
|
590
632
|
# nor do we want to be here.
|
|
@@ -600,11 +642,6 @@ def parse(stream):
|
|
|
600
642
|
custom_style = get_style_by_name("default")
|
|
601
643
|
|
|
602
644
|
formatter = Terminal256Formatter(style=custom_style)
|
|
603
|
-
for i, char in enumerate(line):
|
|
604
|
-
if char == " ":
|
|
605
|
-
state.code_indent += 1
|
|
606
|
-
else:
|
|
607
|
-
break
|
|
608
645
|
line = line[state.code_indent :]
|
|
609
646
|
|
|
610
647
|
elif line.startswith(" " * state.code_indent):
|
|
@@ -652,7 +689,7 @@ def parse(stream):
|
|
|
652
689
|
|
|
653
690
|
code_line = ' ' * indent + this_batch.strip()
|
|
654
691
|
|
|
655
|
-
margin = state.WidthFull - visible_length(code_line)
|
|
692
|
+
margin = state.WidthFull - visible_length(code_line) % state.WidthFull
|
|
656
693
|
yield f"{Style.Codebg}{code_line}{' ' * max(0, margin)}{BGRESET}"
|
|
657
694
|
continue
|
|
658
695
|
except Goto:
|
|
@@ -714,18 +751,18 @@ def parse(stream):
|
|
|
714
751
|
if list_type == "number":
|
|
715
752
|
state.ordered_list_numbers[-1] += 1
|
|
716
753
|
|
|
717
|
-
indent = len(state.list_item_stack) * 2
|
|
754
|
+
indent = (len(state.list_item_stack) - 1) * 2
|
|
718
755
|
|
|
719
756
|
wrap_width = state.Width - indent - (2 * Style.ListIndent)
|
|
720
757
|
|
|
721
758
|
bullet = '•'
|
|
722
759
|
if list_type == "number":
|
|
723
760
|
list_number = int(max(state.ordered_list_numbers[-1], float(list_item_match.group(2))))
|
|
724
|
-
bullet =
|
|
761
|
+
bullet = str(list_number)
|
|
725
762
|
|
|
726
763
|
wrapped_lineList = text_wrap(content, wrap_width, Style.ListIndent,
|
|
727
|
-
first_line_prefix = f"{(' ' * (indent
|
|
728
|
-
subsequent_line_prefix = " " * (indent
|
|
764
|
+
first_line_prefix = f"{(' ' * (indent ))}{FG}{Style.Symbol}{bullet}{RESET} ",
|
|
765
|
+
subsequent_line_prefix = " " * (indent)
|
|
729
766
|
)
|
|
730
767
|
for wrapped_line in wrapped_lineList:
|
|
731
768
|
yield f"{state.space_left()}{wrapped_line}\n"
|
|
@@ -742,7 +779,7 @@ def parse(stream):
|
|
|
742
779
|
#
|
|
743
780
|
# <hr>
|
|
744
781
|
#
|
|
745
|
-
hr_match = re.match(r"^[\s]*([
|
|
782
|
+
hr_match = re.match(r"^[\s]*([-\*=_]){3,}[\s]*$", line)
|
|
746
783
|
if hr_match:
|
|
747
784
|
if state.last_line_empty or last_line_empty_cache:
|
|
748
785
|
# print a horizontal rule using a unicode midline
|
|
@@ -764,12 +801,6 @@ def parse(stream):
|
|
|
764
801
|
for wrapped_line in wrapped_lines:
|
|
765
802
|
yield f"{state.space_left()}{wrapped_line}\n"
|
|
766
803
|
|
|
767
|
-
def get_terminal_width():
|
|
768
|
-
try:
|
|
769
|
-
return shutil.get_terminal_size().columns
|
|
770
|
-
except (AttributeError, OSError):
|
|
771
|
-
return 80
|
|
772
|
-
|
|
773
804
|
def emit(inp):
|
|
774
805
|
buffer = []
|
|
775
806
|
flush = False
|
|
@@ -795,6 +826,9 @@ def emit(inp):
|
|
|
795
826
|
state.current_line += chunk
|
|
796
827
|
|
|
797
828
|
buffer.append(chunk)
|
|
829
|
+
# This *might* be dangerous
|
|
830
|
+
state.reset_inline()
|
|
831
|
+
|
|
798
832
|
if flush:
|
|
799
833
|
chunk = "\n".join(buffer)
|
|
800
834
|
buffer = []
|
|
@@ -806,17 +840,10 @@ def emit(inp):
|
|
|
806
840
|
else:
|
|
807
841
|
chunk = buffer.pop(0)
|
|
808
842
|
|
|
809
|
-
|
|
810
|
-
print(chunk, end="", flush=True)
|
|
811
|
-
else:
|
|
812
|
-
sys.stdout.write(chunk)
|
|
843
|
+
print(chunk, end="", flush=True)
|
|
813
844
|
|
|
814
845
|
if len(buffer):
|
|
815
|
-
|
|
816
|
-
if state.is_pty:
|
|
817
|
-
print(chunk, end="", flush=True)
|
|
818
|
-
else:
|
|
819
|
-
sys.stdout.write(chunk)
|
|
846
|
+
print(buffer.pop(0), end="", flush=True)
|
|
820
847
|
|
|
821
848
|
def apply_multipliers(name, H, S, V):
|
|
822
849
|
m = _style.get(name)
|
|
@@ -824,7 +851,20 @@ def apply_multipliers(name, H, S, V):
|
|
|
824
851
|
return ';'.join([str(int(x * 256)) for x in [r, g, b]]) + "m"
|
|
825
852
|
|
|
826
853
|
def width_calc():
|
|
827
|
-
state.WidthFull
|
|
854
|
+
if not state.WidthFull or not state.WidthArg:
|
|
855
|
+
if state.WidthArg:
|
|
856
|
+
state.WidthFull = state.WidthArg
|
|
857
|
+
else:
|
|
858
|
+
width = 80
|
|
859
|
+
|
|
860
|
+
try:
|
|
861
|
+
width = shutil.get_terminal_size().columns
|
|
862
|
+
state.WidthWrap = True
|
|
863
|
+
except (AttributeError, OSError):
|
|
864
|
+
pass
|
|
865
|
+
|
|
866
|
+
state.WidthFull = width
|
|
867
|
+
|
|
828
868
|
state.Width = state.WidthFull - 2 * Style.Margin
|
|
829
869
|
Style.Codepad = [
|
|
830
870
|
f"{RESET}{FG}{Style.Dark}{'▄' * state.WidthFull}{RESET}\n",
|
|
@@ -838,9 +878,9 @@ def main():
|
|
|
838
878
|
parser.add_argument("filenameList", nargs="*", help="Input file to process (also takes stdin)")
|
|
839
879
|
parser.add_argument("-l", "--loglevel", default="INFO", help="Set the logging level")
|
|
840
880
|
parser.add_argument("-c", "--color", default=None, help="Set the hsv base: h,s,v")
|
|
841
|
-
parser.add_argument("-w", "--width", default="0", help="Set the width")
|
|
842
|
-
parser.add_argument("-e", "--exec", help="Wrap a program for more 'proper' i/o handling")
|
|
843
|
-
parser.add_argument("-s", "--scrape", help="Scrape code snippets to a directory")
|
|
881
|
+
parser.add_argument("-w", "--width", default="0", help="Set the width WIDTH")
|
|
882
|
+
parser.add_argument("-e", "--exec", help="Wrap a program EXEC for more 'proper' i/o handling")
|
|
883
|
+
parser.add_argument("-s", "--scrape", help="Scrape code snippets to a directory SCRAPE")
|
|
844
884
|
args = parser.parse_args()
|
|
845
885
|
|
|
846
886
|
if args.color:
|
|
@@ -864,8 +904,7 @@ def main():
|
|
|
864
904
|
|
|
865
905
|
Style.Codebg = f"{BG}{Style.Dark}"
|
|
866
906
|
Style.Link = f"{FG}{Style.Symbol}{UNDERLINE[0]}"
|
|
867
|
-
Style.Blockquote = f"{FG}{Style.Grey}
|
|
868
|
-
|
|
907
|
+
Style.Blockquote = f"{FG}{Style.Grey}│ "
|
|
869
908
|
|
|
870
909
|
logging.basicConfig(stream=sys.stdout, level=args.loglevel.upper(), format=f'%(message)s')
|
|
871
910
|
state.exec_master, state.exec_slave = pty.openpty()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: streamdown
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.0
|
|
4
4
|
Summary: A streaming markdown renderer for modern terminals with syntax highlighting
|
|
5
5
|
Project-URL: Homepage, https://github.com/kristopolous/Streamdown
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/kristopolous/Streamdown/issues
|
|
@@ -41,7 +41,11 @@ This will work with [simonw's llm](https://github.com/simonw/llm) unlike with [r
|
|
|
41
41
|
### Provides clean copyable code for long code blocks and short terminals.
|
|
42
42
|

|
|
43
43
|
|
|
44
|
-
###
|
|
44
|
+
### Supports images, why not?
|
|
45
|
+
Here's kitty and alacritty. Try to do that in glow...
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
### Does OSC 8 links for modern terminals (and optionally OSC 52 for clipboard)
|
|
45
49
|
[links.webm](https://github.com/user-attachments/assets/a5f71791-7c58-4183-ad3b-309f470c08a3)
|
|
46
50
|
|
|
47
51
|
### Doesn't consume characters like _ and * as style when they are in `blocks like this` because `_they_can_be_varaiables_`
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
streamdown/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
streamdown/sd.py,sha256=ZS7iqHme-GG5LzUlLT3dL6Cyw60Fscq6_7fL6kx777A,35250
|
|
3
|
+
streamdown/tt.mds,sha256=srDldQ9KnMJd5P8GdTXTJl4mjTowwV9y58ZIaBVbtFY,359
|
|
4
|
+
streamdown/plugins/README.md,sha256=KWqYELs9WkKJmuDzYv3cvPlZMkArsNCBUe4XDoTLjLA,1143
|
|
5
|
+
streamdown/plugins/latex.py,sha256=xZMGMdx_Sw4X1piZejXFHfEG9qazU4fGeceiMI0h13Y,648
|
|
6
|
+
streamdown/scrape/file_0.py,sha256=OiFxFGGHu2C2iO9LVnhXKCybqCsnw0bu8MmI2E0vs_s,610
|
|
7
|
+
streamdown/scrape/file_1.js,sha256=JnXSvlsk9UmU5LsGOfXkP3sGId8VNEJRJo8-uRohRCM,569
|
|
8
|
+
streamdown/scrape/file_2.cpp,sha256=4hbT9TJzDNmrU7BVwaIuCMlI2BvUEVeTKoH6wUJRkrI,397
|
|
9
|
+
streamdown-0.14.0.dist-info/METADATA,sha256=NLbNcNtHs7PNXAi6V4TcbxUtYZq_FU23KnPbIYllPoY,8054
|
|
10
|
+
streamdown-0.14.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
streamdown-0.14.0.dist-info/entry_points.txt,sha256=HroKFsFMGf_h9PRTE96NjvjJQWupMW5TGP5RGUr1O_Q,74
|
|
12
|
+
streamdown-0.14.0.dist-info/licenses/LICENSE.MIT,sha256=SnY46EPirUsF20dZDR8HpyVgS2_4Tjxuc6f-4OdqO7U,1070
|
|
13
|
+
streamdown-0.14.0.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
streamdown/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
streamdown/sd.py,sha256=efsejJg_wCaEnbGSoAveIUTojtQ0FPCmSrkupwNvluc,33346
|
|
3
|
-
streamdown/tt.mds,sha256=srDldQ9KnMJd5P8GdTXTJl4mjTowwV9y58ZIaBVbtFY,359
|
|
4
|
-
streamdown/plugins/README.md,sha256=KWqYELs9WkKJmuDzYv3cvPlZMkArsNCBUe4XDoTLjLA,1143
|
|
5
|
-
streamdown/plugins/latex.py,sha256=xZMGMdx_Sw4X1piZejXFHfEG9qazU4fGeceiMI0h13Y,648
|
|
6
|
-
streamdown-0.13.0.dist-info/METADATA,sha256=JxVOmiPebQTidKCv1genLIwlS4j9-mVlCX0R3njk5dA,7841
|
|
7
|
-
streamdown-0.13.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
-
streamdown-0.13.0.dist-info/entry_points.txt,sha256=HroKFsFMGf_h9PRTE96NjvjJQWupMW5TGP5RGUr1O_Q,74
|
|
9
|
-
streamdown-0.13.0.dist-info/licenses/LICENSE.MIT,sha256=SnY46EPirUsF20dZDR8HpyVgS2_4Tjxuc6f-4OdqO7U,1070
|
|
10
|
-
streamdown-0.13.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|