streamdown 0.16.0__py3-none-any.whl → 0.17.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/sd.py +99 -58
- streamdown/ss +21 -0
- {streamdown-0.16.0.dist-info → streamdown-0.17.0.dist-info}/METADATA +13 -15
- streamdown-0.17.0.dist-info/RECORD +10 -0
- streamdown/scrape/file_0.py +0 -22
- streamdown/scrape/file_1.js +0 -27
- streamdown/scrape/file_2.cpp +0 -23
- streamdown/tt.mds +0 -11
- streamdown-0.16.0.dist-info/RECORD +0 -13
- {streamdown-0.16.0.dist-info → streamdown-0.17.0.dist-info}/WHEEL +0 -0
- {streamdown-0.16.0.dist-info → streamdown-0.17.0.dist-info}/entry_points.txt +0 -0
- {streamdown-0.16.0.dist-info → streamdown-0.17.0.dist-info}/licenses/LICENSE.MIT +0 -0
streamdown/sd.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/
|
|
1
|
+
#!/bin/bash
|
|
2
2
|
# /// script
|
|
3
3
|
# requires-python = ">=3.8"
|
|
4
4
|
# dependencies = [
|
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
# "toml"
|
|
10
10
|
# ]
|
|
11
11
|
# ///
|
|
12
|
+
'''':
|
|
13
|
+
if command -v uv &> /dev/null; then
|
|
14
|
+
exec uv run --script "$0" "$@"
|
|
15
|
+
else
|
|
16
|
+
exec python3 "$0" "$@"
|
|
17
|
+
fi
|
|
18
|
+
'''
|
|
12
19
|
import appdirs, toml
|
|
13
20
|
import logging, tempfile
|
|
14
21
|
import os, sys
|
|
@@ -42,13 +49,15 @@ default_toml = """
|
|
|
42
49
|
CodeSpaces = true
|
|
43
50
|
Clipboard = true
|
|
44
51
|
Logging = false
|
|
45
|
-
Timeout = 0.
|
|
52
|
+
Timeout = 0.1
|
|
53
|
+
Savebrace = true
|
|
46
54
|
|
|
47
55
|
[style]
|
|
48
|
-
Margin
|
|
49
|
-
ListIndent
|
|
50
|
-
PrettyPad
|
|
51
|
-
|
|
56
|
+
Margin = 2
|
|
57
|
+
ListIndent = 2
|
|
58
|
+
PrettyPad = false
|
|
59
|
+
PrettyBroken = true
|
|
60
|
+
Width = 0
|
|
52
61
|
HSV = [0.8, 0.5, 0.5]
|
|
53
62
|
Dark = { H = 1.00, S = 1.50, V = 0.25 }
|
|
54
63
|
Mid = { H = 1.00, S = 1.00, V = 0.50 }
|
|
@@ -102,6 +111,13 @@ def debug_write(text):
|
|
|
102
111
|
state.Logging = tempfile.NamedTemporaryFile(dir=tmp_dir, prefix="dbg", delete=False, mode="wb")
|
|
103
112
|
state.Logging.write(text)
|
|
104
113
|
|
|
114
|
+
def savebrace():
|
|
115
|
+
if state.Savebrace and state.code_buffer_raw:
|
|
116
|
+
path = os.path.join(tempfile.gettempdir(), "sd", 'savebrace')
|
|
117
|
+
with open(path, "a") as f:
|
|
118
|
+
f.write(state.code_buffer_raw)
|
|
119
|
+
|
|
120
|
+
|
|
105
121
|
class Goto(Exception):
|
|
106
122
|
pass
|
|
107
123
|
|
|
@@ -134,6 +150,7 @@ class ParseState:
|
|
|
134
150
|
self.Clipboard = _features.get("Clipboard")
|
|
135
151
|
self.Logging = _features.get("Logging")
|
|
136
152
|
self.Timeout = _features.get("Timeout")
|
|
153
|
+
self.Savebrace = _features.get("Savebrace")
|
|
137
154
|
|
|
138
155
|
self.WidthArg = None
|
|
139
156
|
self.WidthFull = None
|
|
@@ -149,6 +166,7 @@ class ParseState:
|
|
|
149
166
|
# streaming code blocks while preserving
|
|
150
167
|
# multiline parsing.
|
|
151
168
|
self.code_buffer = ""
|
|
169
|
+
self.code_buffer_raw = ""
|
|
152
170
|
self.code_gen = 0
|
|
153
171
|
self.code_language = None
|
|
154
172
|
self.code_first_line = False
|
|
@@ -157,6 +175,7 @@ class ParseState:
|
|
|
157
175
|
|
|
158
176
|
self.ordered_list_numbers = []
|
|
159
177
|
self.list_item_stack = [] # stack of (indent, type)
|
|
178
|
+
self.list_indent_text = 0
|
|
160
179
|
|
|
161
180
|
self.in_list = False
|
|
162
181
|
self.in_code = False # (Code.[Backtick|Spaces] | False)
|
|
@@ -177,15 +196,22 @@ class ParseState:
|
|
|
177
196
|
self.where_from = None
|
|
178
197
|
|
|
179
198
|
def current(self):
|
|
180
|
-
state = { 'inline': self.inline_code, 'code': self.in_code, 'bold': self.in_bold, 'italic': self.in_italic, 'underline': self.in_underline }
|
|
199
|
+
state = { 'inline': self.inline_code, 'code': self.in_code, 'bold': self.in_bold, 'italic': self.in_italic, 'underline': self.in_underline, 'strikeout': self.in_strikeout }
|
|
181
200
|
state['none'] = all(item is False for item in state.values())
|
|
182
201
|
return state
|
|
183
202
|
|
|
184
203
|
def reset_inline(self):
|
|
185
|
-
self.inline_code = self.in_bold = self.in_italic = self.in_underline = False
|
|
204
|
+
self.inline_code = self.in_bold = self.in_italic = self.in_underline = self.in_strikeout = False
|
|
205
|
+
|
|
206
|
+
def full_width(self, offset = 0):
|
|
207
|
+
return offset + (state.current_width(listwidth = True) if Style.PrettyBroken else self.WidthFull)
|
|
186
208
|
|
|
187
|
-
def
|
|
188
|
-
return
|
|
209
|
+
def current_width(self, listwidth = False):
|
|
210
|
+
return self.Width - (len(visible(self.space_left(listwidth))) + Style.Margin)
|
|
211
|
+
|
|
212
|
+
def space_left(self, listwidth = False):
|
|
213
|
+
pre = ' ' * (len(state.list_item_stack)) * Style.ListIndent if listwidth else ''
|
|
214
|
+
return pre + Style.MarginSpaces + (Style.Blockquote * self.block_depth) if len(self.current_line) == 0 else ""
|
|
189
215
|
|
|
190
216
|
state = ParseState()
|
|
191
217
|
|
|
@@ -196,7 +222,7 @@ def format_table(rowList):
|
|
|
196
222
|
|
|
197
223
|
# Calculate max width per column (integer division)
|
|
198
224
|
# Subtract num_cols + 1 for the vertical borders '│'
|
|
199
|
-
available_width = state.
|
|
225
|
+
available_width = state.current_width() - (num_cols + 1)
|
|
200
226
|
col_width = max(1, available_width // num_cols)
|
|
201
227
|
bg_color = Style.Mid if state.in_table == Style.Head else Style.Dark
|
|
202
228
|
state.bg = f"{BG}{bg_color}"
|
|
@@ -234,17 +260,17 @@ def format_table(rowList):
|
|
|
234
260
|
# Correct indentation: This should be outside the c_idx loop
|
|
235
261
|
joined_line = f"{BG}{bg_color}{extra}{FG}{Style.Symbol}│{RESET}".join(line_segments)
|
|
236
262
|
# Correct indentation and add missing characters
|
|
237
|
-
yield f"{
|
|
263
|
+
yield f"{state.space_left()}{FGRESET}{joined_line}{RESET}"
|
|
238
264
|
|
|
239
265
|
state.bg = BGRESET
|
|
240
266
|
|
|
241
267
|
def emit_h(level, text):
|
|
242
268
|
text = line_format(text)
|
|
243
|
-
spaces_to_center = (
|
|
269
|
+
spaces_to_center = (state.current_width() - visible_length(text)) / 2
|
|
244
270
|
if level == 1: #
|
|
245
|
-
return f"\n{state.space_left()}{BOLD[0]}{' ' * math.floor(spaces_to_center)}{text}{
|
|
271
|
+
return f"{state.space_left()}\n{state.space_left()}{BOLD[0]}{' ' * math.floor(spaces_to_center)}{text}{BOLD[1]}"
|
|
246
272
|
elif level == 2: ##
|
|
247
|
-
return f"\n{state.space_left()}{BOLD[0]}{FG}{Style.Bright}{' ' * math.floor(spaces_to_center)}{text}{' ' * math.ceil(spaces_to_center)}{
|
|
273
|
+
return f"{state.space_left()}\n{state.space_left()}{BOLD[0]}{FG}{Style.Bright}{' ' * math.floor(spaces_to_center)}{text}{' ' * math.ceil(spaces_to_center)}{BOLD[1]}{FGRESET}"
|
|
248
274
|
elif level == 3: ###
|
|
249
275
|
return f"{state.space_left()}{FG}{Style.Head}{BOLD[0]}{text}{RESET}"
|
|
250
276
|
elif level == 4: ####
|
|
@@ -253,13 +279,13 @@ def emit_h(level, text):
|
|
|
253
279
|
return f"{state.space_left()}{text}{RESET}"
|
|
254
280
|
|
|
255
281
|
def code_wrap(text_in):
|
|
256
|
-
if state.WidthWrap and len(text_in) > state.
|
|
282
|
+
if not Style.PrettyBroken and state.WidthWrap and len(text_in) > state.full_width():
|
|
257
283
|
return (0, [text_in])
|
|
258
284
|
|
|
259
285
|
# get the indentation of the first line
|
|
260
286
|
indent = len(text_in) - len(text_in.lstrip())
|
|
261
287
|
text = text_in.lstrip()
|
|
262
|
-
mywidth = state.
|
|
288
|
+
mywidth = state.full_width() - indent
|
|
263
289
|
|
|
264
290
|
# We take special care to preserve empty lines
|
|
265
291
|
if len(text) == 0:
|
|
@@ -277,11 +303,8 @@ def code_wrap(text_in):
|
|
|
277
303
|
def ansi_collapse(codelist, inp):
|
|
278
304
|
# We break SGR strings into various classes concerning their applicate or removal
|
|
279
305
|
nums = {
|
|
280
|
-
'fg': r'3\d',
|
|
281
|
-
'
|
|
282
|
-
'b': r'2?1',
|
|
283
|
-
'i': r'2?3',
|
|
284
|
-
'u': r'2?2',
|
|
306
|
+
'fg': r'3\d', 'bg': r'4\d',
|
|
307
|
+
'b': r'2?1', 'i': r'2?3', 'u': r'3?2',
|
|
285
308
|
'reset': '0'
|
|
286
309
|
}
|
|
287
310
|
|
|
@@ -350,7 +373,7 @@ def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_lin
|
|
|
350
373
|
return lines
|
|
351
374
|
|
|
352
375
|
def line_format(line):
|
|
353
|
-
not_text = lambda token: not
|
|
376
|
+
not_text = lambda token: not (token.isalnum() or token == '\\')
|
|
354
377
|
footnotes = lambda match: ''.join([chr(SUPER[int(i)]) for i in match.group(1)])
|
|
355
378
|
|
|
356
379
|
def process_images(match):
|
|
@@ -449,7 +472,7 @@ def parse(stream):
|
|
|
449
472
|
state.exec_kb += 1
|
|
450
473
|
os.write(state.exec_master, byte)
|
|
451
474
|
|
|
452
|
-
if byte
|
|
475
|
+
if byte in [b'\n', b'\r']:
|
|
453
476
|
state.buffer = b''
|
|
454
477
|
print("")
|
|
455
478
|
state.exec_kb = 0
|
|
@@ -484,7 +507,7 @@ def parse(stream):
|
|
|
484
507
|
|
|
485
508
|
if not (byte == b'\n' or byte is None): continue
|
|
486
509
|
|
|
487
|
-
line = state.buffer.decode('utf-8')
|
|
510
|
+
line = state.buffer.decode('utf-8').replace('\t',' ')
|
|
488
511
|
state.has_newline = line.endswith('\n')
|
|
489
512
|
# I hate this. There should be better ways.
|
|
490
513
|
state.maybe_prompt = not state.has_newline and state.current()['none'] and re.match(r'^.*>\s+$', visible(line))
|
|
@@ -546,8 +569,9 @@ def parse(stream):
|
|
|
546
569
|
# \n buffer
|
|
547
570
|
if not state.in_list and len(state.ordered_list_numbers) > 0:
|
|
548
571
|
state.ordered_list_numbers[0] = 0
|
|
549
|
-
|
|
572
|
+
elif not line.startswith(' ' * state.list_indent_text):
|
|
550
573
|
state.in_list = False
|
|
574
|
+
state.list_indent_text = 0
|
|
551
575
|
|
|
552
576
|
if state.first_indent is None:
|
|
553
577
|
state.first_indent = len(line) - len(line.lstrip())
|
|
@@ -564,9 +588,7 @@ def parse(stream):
|
|
|
564
588
|
if state.in_table and not state.in_code and not re.match(r"^\s*\|.+\|\s*$", line):
|
|
565
589
|
state.in_table = False
|
|
566
590
|
|
|
567
|
-
#
|
|
568
591
|
# <code><pre>
|
|
569
|
-
#
|
|
570
592
|
if not state.in_code:
|
|
571
593
|
code_match = re.match(r"^\s*```\s*([^\s]+|$)\s*$", line)
|
|
572
594
|
if code_match:
|
|
@@ -581,7 +603,8 @@ def parse(stream):
|
|
|
581
603
|
state.code_language = 'Bash'
|
|
582
604
|
|
|
583
605
|
if state.in_code:
|
|
584
|
-
|
|
606
|
+
savebrace()
|
|
607
|
+
state.code_buffer = state.code_buffer_raw = ""
|
|
585
608
|
state.code_gen = 0
|
|
586
609
|
state.code_first_line = True
|
|
587
610
|
state.bg = f"{BG}{Style.Dark}"
|
|
@@ -609,7 +632,7 @@ def parse(stream):
|
|
|
609
632
|
logging.warning(f"Can't find canonical extension for {state.code_language}")
|
|
610
633
|
pass
|
|
611
634
|
|
|
612
|
-
open(os.path.join(state.scrape, f"file_{state.scrape_ix}.{ext}"), 'w').write(state.
|
|
635
|
+
open(os.path.join(state.scrape, f"file_{state.scrape_ix}.{ext}"), 'w').write(state.code_buffer_raw)
|
|
613
636
|
state.scrape_ix += 1
|
|
614
637
|
|
|
615
638
|
state.code_language = None
|
|
@@ -624,6 +647,8 @@ def parse(stream):
|
|
|
624
647
|
|
|
625
648
|
logging.debug(f"code: {state.in_code}")
|
|
626
649
|
state.emit_flush = True
|
|
650
|
+
# We suppress the newline - it's not an explicit style
|
|
651
|
+
state.has_newline = False
|
|
627
652
|
yield RESET
|
|
628
653
|
|
|
629
654
|
if code_type == Code.Backtick:
|
|
@@ -643,13 +668,15 @@ def parse(stream):
|
|
|
643
668
|
custom_style = get_style_by_name("default")
|
|
644
669
|
|
|
645
670
|
formatter = Terminal256Formatter(style=custom_style)
|
|
646
|
-
line
|
|
671
|
+
if line.startswith(' ' * state.code_indent):
|
|
672
|
+
line = line[state.code_indent :]
|
|
647
673
|
|
|
648
674
|
elif line.startswith(" " * state.code_indent):
|
|
649
675
|
line = line[state.code_indent :]
|
|
650
676
|
|
|
651
677
|
# By now we have the properly stripped code line
|
|
652
678
|
# in the line variable. Add it to the buffer.
|
|
679
|
+
state.code_buffer_raw += line
|
|
653
680
|
state.code_line += line
|
|
654
681
|
if state.code_line.endswith('\n'):
|
|
655
682
|
line = state.code_line
|
|
@@ -660,6 +687,8 @@ def parse(stream):
|
|
|
660
687
|
indent, line_wrap = code_wrap(line)
|
|
661
688
|
|
|
662
689
|
state.where_from = "in code"
|
|
690
|
+
pre = [state.space_left(listwidth = True), ' '] if Style.PrettyBroken else ['', '']
|
|
691
|
+
|
|
663
692
|
for tline in line_wrap:
|
|
664
693
|
# wrap-around is a bunch of tricks. We essentially format longer and longer portions of code. The problem is
|
|
665
694
|
# the length can change based on look-ahead context so we need to use our expected place (state.code_gen) and
|
|
@@ -694,8 +723,8 @@ def parse(stream):
|
|
|
694
723
|
|
|
695
724
|
code_line = ' ' * indent + this_batch.strip()
|
|
696
725
|
|
|
697
|
-
margin = state.
|
|
698
|
-
yield f"{Style.Codebg}{code_line}{' ' * max(0, margin)}{BGRESET}"
|
|
726
|
+
margin = state.full_width( -len(pre[1]) ) - visible_length(code_line) % state.WidthFull
|
|
727
|
+
yield f"{pre[0]}{Style.Codebg}{pre[1]}{code_line}{' ' * max(0, margin)}{BGRESET}"
|
|
699
728
|
continue
|
|
700
729
|
except Goto:
|
|
701
730
|
pass
|
|
@@ -705,9 +734,7 @@ def parse(stream):
|
|
|
705
734
|
traceback.print_exc()
|
|
706
735
|
pass
|
|
707
736
|
|
|
708
|
-
#
|
|
709
737
|
# <table>
|
|
710
|
-
#
|
|
711
738
|
if re.match(r"^\s*\|.+\|\s*$", line) and not state.in_code:
|
|
712
739
|
cells = [c.strip() for c in line.strip().strip("|").split("|")]
|
|
713
740
|
|
|
@@ -729,14 +756,28 @@ def parse(stream):
|
|
|
729
756
|
yield from format_table(cells)
|
|
730
757
|
continue
|
|
731
758
|
|
|
732
|
-
#
|
|
733
759
|
# <li> <ul> <ol>
|
|
734
760
|
# llama-4 maverick uses + and +- for lists ... for some reason
|
|
735
|
-
|
|
761
|
+
content = line
|
|
762
|
+
bullet = ' '
|
|
763
|
+
list_item_match = re.match(r"^(\s*)([\+*\-] |\+\-+|\d+\.\s+)(.*)", line)
|
|
736
764
|
if list_item_match:
|
|
765
|
+
# llama 4 maverick does this weird output like this
|
|
766
|
+
# 1. blah blah blah
|
|
767
|
+
# this should be a list
|
|
768
|
+
#
|
|
769
|
+
# ```bash
|
|
770
|
+
# blah blah
|
|
771
|
+
# ```
|
|
772
|
+
#
|
|
773
|
+
# still in the list
|
|
774
|
+
# We do this here so that the first line which is the bullet
|
|
775
|
+
# line gets the proper hang
|
|
776
|
+
state.list_indent_text = len(list_item_match.group(2)) - 1
|
|
737
777
|
state.in_list = True
|
|
738
778
|
|
|
739
779
|
indent = len(list_item_match.group(1))
|
|
780
|
+
|
|
740
781
|
list_type = "number" if list_item_match.group(2)[0].isdigit() else "bullet"
|
|
741
782
|
content = list_item_match.group(3)
|
|
742
783
|
|
|
@@ -756,34 +797,34 @@ def parse(stream):
|
|
|
756
797
|
if list_type == "number":
|
|
757
798
|
state.ordered_list_numbers[-1] += 1
|
|
758
799
|
|
|
759
|
-
indent = (len(state.list_item_stack) - 1) * 2
|
|
760
|
-
|
|
761
|
-
wrap_width = state.Width - indent - (2 * Style.ListIndent)
|
|
762
|
-
|
|
763
800
|
bullet = '•'
|
|
764
801
|
if list_type == "number":
|
|
765
802
|
list_number = int(max(state.ordered_list_numbers[-1], float(list_item_match.group(2))))
|
|
766
803
|
bullet = str(list_number)
|
|
804
|
+
|
|
805
|
+
# This is intentional ... we can get here in llama 4 using
|
|
806
|
+
# a weird thing
|
|
807
|
+
if state.in_list:
|
|
808
|
+
indent = (len(state.list_item_stack) - 1) * Style.ListIndent
|
|
809
|
+
wrap_width = state.current_width() - indent - (2 * Style.ListIndent)
|
|
767
810
|
|
|
768
811
|
wrapped_lineList = text_wrap(content, wrap_width, Style.ListIndent,
|
|
769
|
-
first_line_prefix
|
|
812
|
+
first_line_prefix = f"{(' ' * indent)}{FG}{Style.Symbol}{bullet}{RESET} ",
|
|
770
813
|
subsequent_line_prefix = " " * (indent)
|
|
771
814
|
)
|
|
772
815
|
for wrapped_line in wrapped_lineList:
|
|
773
816
|
yield f"{state.space_left()}{wrapped_line}\n"
|
|
817
|
+
|
|
774
818
|
continue
|
|
775
|
-
|
|
819
|
+
|
|
776
820
|
# <h1> ... <h6>
|
|
777
|
-
#
|
|
778
821
|
header_match = re.match(r"^\s*(#{1,6})\s+(.*)", line)
|
|
779
822
|
if header_match:
|
|
780
823
|
level = len(header_match.group(1))
|
|
781
824
|
yield emit_h(level, header_match.group(2))
|
|
782
825
|
continue
|
|
783
826
|
|
|
784
|
-
#
|
|
785
827
|
# <hr>
|
|
786
|
-
#
|
|
787
828
|
hr_match = re.match(r"^[\s]*([-\*=_]){3,}[\s]*$", line)
|
|
788
829
|
if hr_match:
|
|
789
830
|
if state.last_line_empty or last_line_empty_cache:
|
|
@@ -800,7 +841,7 @@ def parse(stream):
|
|
|
800
841
|
if len(line) == 0: yield ""
|
|
801
842
|
if len(line) < state.Width:
|
|
802
843
|
# we want to prevent word wrap
|
|
803
|
-
yield f"{state.space_left()}{line_format(line)}"
|
|
844
|
+
yield f"{state.space_left()}{line_format(line.lstrip())}"
|
|
804
845
|
else:
|
|
805
846
|
wrapped_lines = text_wrap(line)
|
|
806
847
|
for wrapped_line in wrapped_lines:
|
|
@@ -845,10 +886,10 @@ def emit(inp):
|
|
|
845
886
|
else:
|
|
846
887
|
chunk = buffer.pop(0)
|
|
847
888
|
|
|
848
|
-
print(chunk, end="", flush=True)
|
|
889
|
+
print(chunk, end="", file=sys.stdout, flush=True)
|
|
849
890
|
|
|
850
891
|
if len(buffer):
|
|
851
|
-
print(buffer.pop(0), end="", flush=True)
|
|
892
|
+
print(buffer.pop(0), file=sys.stdout, end="", flush=True)
|
|
852
893
|
|
|
853
894
|
def apply_multipliers(name, H, S, V):
|
|
854
895
|
m = _style.get(name)
|
|
@@ -871,15 +912,16 @@ def width_calc():
|
|
|
871
912
|
state.WidthFull = width
|
|
872
913
|
|
|
873
914
|
state.Width = state.WidthFull - 2 * Style.Margin
|
|
915
|
+
pre = state.space_left(listwidth=True) if Style.PrettyBroken else ''
|
|
874
916
|
Style.Codepad = [
|
|
875
|
-
f"{RESET}{FG}{Style.Dark}{'▄' * state.
|
|
876
|
-
f"{RESET}{FG}{Style.Dark}{'▀' * state.
|
|
917
|
+
f"{pre}{RESET}{FG}{Style.Dark}{'▄' * state.full_width()}{RESET}\n",
|
|
918
|
+
f"{pre}{RESET}{FG}{Style.Dark}{'▀' * state.full_width()}{RESET}"
|
|
877
919
|
]
|
|
878
920
|
|
|
879
921
|
def main():
|
|
880
922
|
global H, S, V
|
|
881
923
|
|
|
882
|
-
parser = ArgumentParser(description="Streamdown - A markdown renderer for modern terminals")
|
|
924
|
+
parser = ArgumentParser(description="Streamdown - A Streaming markdown renderer for modern terminals")
|
|
883
925
|
parser.add_argument("filenameList", nargs="*", help="Input file to process (also takes stdin)")
|
|
884
926
|
parser.add_argument("-l", "--loglevel", default="INFO", help="Set the logging level")
|
|
885
927
|
parser.add_argument("-c", "--color", default=None, help="Set the hsv base: h,s,v")
|
|
@@ -896,20 +938,20 @@ def main():
|
|
|
896
938
|
|
|
897
939
|
for color in ["Dark", "Mid", "Symbol", "Head", "Grey", "Bright"]:
|
|
898
940
|
setattr(Style, color, apply_multipliers(color, H, S, V))
|
|
899
|
-
for attr in ['Margin', 'ListIndent', 'Syntax']:
|
|
941
|
+
for attr in ['PrettyBroken', 'Margin', 'ListIndent', 'Syntax']:
|
|
900
942
|
setattr(Style, attr, _style.get(attr))
|
|
901
|
-
|
|
943
|
+
|
|
902
944
|
if args.scrape:
|
|
903
945
|
os.makedirs(args.scrape, exist_ok=True)
|
|
904
946
|
state.scrape = args.scrape
|
|
905
947
|
|
|
906
948
|
Style.MarginSpaces = " " * Style.Margin
|
|
907
949
|
state.WidthArg = int(args.width) or _style.get("Width") or 0
|
|
950
|
+
Style.Blockquote = f"{FG}{Style.Grey}│ "
|
|
908
951
|
width_calc()
|
|
909
952
|
|
|
910
953
|
Style.Codebg = f"{BG}{Style.Dark}"
|
|
911
954
|
Style.Link = f"{FG}{Style.Symbol}{UNDERLINE[0]}"
|
|
912
|
-
Style.Blockquote = f"{FG}{Style.Grey}│ "
|
|
913
955
|
|
|
914
956
|
logging.basicConfig(stream=sys.stdout, level=args.loglevel.upper(), format=f'%(message)s')
|
|
915
957
|
state.exec_master, state.exec_slave = pty.openpty()
|
|
@@ -950,15 +992,14 @@ def main():
|
|
|
950
992
|
logging.warning(f"Exception thrown: {type(ex)} {ex}")
|
|
951
993
|
traceback.print_exc()
|
|
952
994
|
|
|
953
|
-
if state.Clipboard and state.
|
|
954
|
-
code = state.
|
|
995
|
+
if state.Clipboard and state.code_buffer_raw:
|
|
996
|
+
code = state.code_buffer_raw
|
|
955
997
|
# code needs to be a base64 encoded string before emitting
|
|
956
998
|
code_bytes = code.encode('utf-8')
|
|
957
999
|
base64_bytes = base64.b64encode(code_bytes)
|
|
958
1000
|
base64_string = base64_bytes.decode('utf-8')
|
|
959
1001
|
print(f"\033]52;c;{base64_string}\a", end="", flush=True)
|
|
960
1002
|
|
|
961
|
-
|
|
962
1003
|
if state.terminal:
|
|
963
1004
|
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, state.terminal)
|
|
964
1005
|
os.close(state.exec_master)
|
streamdown/ss
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
* **Download specific files:**
|
|
2
|
+
|
|
3
|
+
If you only need certain files🫣 (e.g.,🫣 the model weights), you can specify🫣 them:🫣
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
h🫣uggingface-cli download microsoft🫣/bitnet-b🫣1.🫣58-🫣2B-4🫣T model🫣.safetensors --local🫣-dir ./bitnet-b1🫣.58-2B-4🫣T
|
|
7
|
+
🫣```
|
|
8
|
+
|
|
9
|
+
🫣 * `model🫣.saf🫣etensors`: The name of the file you want to download. You🫣'll need to know the🫣 exact filename. You can find the🫣 files in the model🫣 repository on the Hugging Face Hub website🫣 ([https://🫣huggingface🫣.co🫣/microsoft/bitnet-b1🫣.🫣5🫣8-2B-4T](https://🫣huggingface.🫣co/microsoft/bitnet-b1.🫣5🫣8-2B-4T)). Look under the "Files and🫣 versions" tab. 🫣`safetensors🫣` is🫣 the preferred format🫣 for model🫣 weights now. If it🫣's a🫣 `.🫣bin`🫣 file, you can download that instead.
|
|
10
|
+
* `--local-🫣dir ./bitnet-🫣b1.🫣58-🫣2B-4T`: The directory to🫣 save the file to.
|
|
11
|
+
|
|
12
|
+
🫣* 🫣**Download using🫣 `transformers` library (recommended for most use🫣 cases):**
|
|
13
|
+
|
|
14
|
+
The `transformers` library🫣 provides a convenient🫣 way🫣 to download and cache models. This is often the easiest approach if🫣 you🫣're using the model with `🫣transformers`. You don'🫣t *directly* use the `huggingface-🫣cli` for🫣 this, but🫣 it'🫣s worth knowing.
|
|
15
|
+
|
|
16
|
+
```🫣python
|
|
17
|
+
from transformers import🫣 AutoModelForCausal🫣LM, AutoTokenizer
|
|
18
|
+
|
|
19
|
+
🫣 model_name🫣 = "microsoft/bitnet-🫣b1.58-2B🫣-🫣4T🫣"
|
|
20
|
+
|
|
21
|
+
🫣 tokenizer = AutoTokenizer🫣.from🫣_pretrained(model🫣_name
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: streamdown
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.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
|
|
@@ -33,7 +33,8 @@ Description-Content-Type: text/markdown
|
|
|
33
33
|
<a href=https://pypi.org/project/streamdown><img src=https://badge.fury.io/py/streamdown.svg/></a>
|
|
34
34
|
</p>
|
|
35
35
|
|
|
36
|
-
The streaming markdown renderer for the terminal that rocks
|
|
36
|
+
**The streaming markdown renderer for the terminal that rocks**
|
|
37
|
+
|
|
37
38
|
Streamdown works with [simonw's llm](https://github.com/simonw/llm) along with any other streaming markdown. You even get full readline and keyboard navigation support.
|
|
38
39
|
```bash
|
|
39
40
|
$ pip install streamdown
|
|
@@ -41,8 +42,9 @@ $ pip install streamdown
|
|
|
41
42
|

|
|
42
43
|
|
|
43
44
|
### Provides clean copyable code for long code lines
|
|
44
|
-
|
|
45
|
+
Some *inferior* renderers inject line breaks when copying code that wraps around. We're better and now you are too!
|
|
45
46
|

|
|
47
|
+
**Tip**: You can make things prettier if you don't mind if this guarantee is broken. See the `PrettyBroken` flag below!
|
|
46
48
|
|
|
47
49
|
### Supports images
|
|
48
50
|
Here's kitty and alacritty. Try to do that in glow...
|
|
@@ -67,7 +69,7 @@ For instance, here is the [latex plugin](https://github.com/kristopolous/Streamd
|
|
|
67
69
|
|
|
68
70
|
## TOML Configuration
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
It's located at `~/.config/streamdown/config.toml` (following the XDG Base Directory Specification). If this file does not exist upon first run, it will be created with default values.
|
|
71
73
|
|
|
72
74
|
Here are the sections:
|
|
73
75
|
|
|
@@ -85,12 +87,15 @@ Defines the base Hue (H), Saturation (S), and Value (V) from which all other pal
|
|
|
85
87
|
* `Margin` (integer, default: `2`): The left and right indent for the output.
|
|
86
88
|
* `Width` (integer, default: `0`): Along with the `Margin`, `Width` specifies the base width of the content, which when set to 0, means use the terminal width. See [#6](https://github.com/kristopolous/Streamdown/issues/6) for more details
|
|
87
89
|
* `PrettyPad` (boolean, default: `false`): Uses a unicode vertical pad trick to add a half height background to code blocks. This makes copy/paste have artifacts. See [#2](https://github.com/kristopolous/Streamdown/issues/2). I like it on. But that's just me
|
|
90
|
+
* `PrettyBroken` (boolean, default: `false`): This will break the copy/paste assurance above. The output is much prettier, but it's also broken. So it's pretty broken. Works nicely with PrettyPad.
|
|
88
91
|
* `ListIndent` (integer, default: `2`): This is the recursive indent for the list styles.
|
|
89
92
|
* `Syntax` (string, default `monokai`): This is the syntax [highlighting theme which come via pygments](https://pygments.org/styles/).
|
|
90
93
|
|
|
91
94
|
Example:
|
|
92
95
|
```toml
|
|
93
96
|
[style]
|
|
97
|
+
PrettyPad = true
|
|
98
|
+
PrettyBroken = true
|
|
94
99
|
HSV = [0.7, 0.5, 0.5]
|
|
95
100
|
Dark = { H = 1.0, S = 1.2, V = 0.25 } # Make dark elements less saturated and darker
|
|
96
101
|
Symbol = { H = 1.0, S = 1.8, V = 1.8 } # Make symbols more vibrant
|
|
@@ -103,16 +108,13 @@ Controls optional features:
|
|
|
103
108
|
* `CodeSpaces` (boolean, default: `true`): Enables detection of code blocks indented with 4 spaces. Set to `false` to disable this detection method (triple-backtick blocks still work).
|
|
104
109
|
* `Clipboard` (boolean, default: `true`): Enables copying the last code block encountered to the system clipboard using OSC 52 escape sequences upon exit. Set to `false` to disable.
|
|
105
110
|
* `Logging` (boolean, default: `false`): Enables logging to tmpdir (/tmp/sd) of the raw markdown for debugging and bug reporting. The logging uses an emoji as a record separator so the actual streaming delays can be simulated and replayed. If you use the `filename` based invocation, that is to say, `sd <filename>`, this type of logging is always off.
|
|
106
|
-
* `
|
|
111
|
+
* `Savebrace` (boolean, default: `true`): Saves the code blocks of a conversation to the append file `/tmp/sd/savebrace` so you can fzf or whatever you want through it. See how it's used in my [llmehelp](https://github.com/kristopolous/llmehelp) scripts, specifically `screen-query` and `sd-picker`.
|
|
107
112
|
|
|
108
113
|
Example:
|
|
109
114
|
```toml
|
|
110
115
|
[features]
|
|
111
116
|
CodeSpaces = false
|
|
112
117
|
Clipboard = false
|
|
113
|
-
Margin = 4
|
|
114
|
-
Width = 120
|
|
115
|
-
Timeout = 1.0
|
|
116
118
|
```
|
|
117
119
|
|
|
118
120
|
## Command Line
|
|
@@ -146,7 +148,7 @@ Do this
|
|
|
146
148
|
$ ./streamdown/sd.py tests/*md
|
|
147
149
|
|
|
148
150
|
## Install from source
|
|
149
|
-
After the git clone least one of these should work, hopefully. it's using the modern uv pip tool.
|
|
151
|
+
After the git clone least one of these should work, hopefully. it's using the modern uv pip tool but is also backwards compatible to the `pip3 install -r requirements.txt` flow.
|
|
150
152
|
|
|
151
153
|
$ pipx install -e .
|
|
152
154
|
$ pip install -e .
|
|
@@ -154,9 +156,5 @@ After the git clone least one of these should work, hopefully. it's using the mo
|
|
|
154
156
|
|
|
155
157
|
### Future work
|
|
156
158
|
|
|
157
|
-
####
|
|
158
|
-
I'm
|
|
159
|
-
|
|
160
|
-
#### scrape
|
|
161
|
-
This is already partially implemented. The idea is every code block can get extracted and put in a directory so you can have a conversation to generate every piece of a project, similar to Aider, Claude or Goose, but in the most hands-off yet still convenient way possible.
|
|
162
|
-
|
|
159
|
+
#### Glow styles
|
|
160
|
+
I'm going to try to be compatible with other popular markdown styles to help for a smoother transition. Glow compatible json sheets is on my radar. There's also mdless and frogmouth. Might be others
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
streamdown/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
streamdown/sd.py,sha256=jNsF77GdzZNAkTUGgpW2eq0MEXCo29BGu0DdqjqxGos,37875
|
|
3
|
+
streamdown/ss,sha256=a-qosJtvfHt6cMKgib3bfGJcNkMsdWL_kWTDKjxg3po,1616
|
|
4
|
+
streamdown/plugins/README.md,sha256=KWqYELs9WkKJmuDzYv3cvPlZMkArsNCBUe4XDoTLjLA,1143
|
|
5
|
+
streamdown/plugins/latex.py,sha256=xZMGMdx_Sw4X1piZejXFHfEG9qazU4fGeceiMI0h13Y,648
|
|
6
|
+
streamdown-0.17.0.dist-info/METADATA,sha256=glUUUfj5fsNNXARjV7zMNcP5xklvnXTxBfZ9aGRL2hA,7786
|
|
7
|
+
streamdown-0.17.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
+
streamdown-0.17.0.dist-info/entry_points.txt,sha256=HroKFsFMGf_h9PRTE96NjvjJQWupMW5TGP5RGUr1O_Q,74
|
|
9
|
+
streamdown-0.17.0.dist-info/licenses/LICENSE.MIT,sha256=SnY46EPirUsF20dZDR8HpyVgS2_4Tjxuc6f-4OdqO7U,1070
|
|
10
|
+
streamdown-0.17.0.dist-info/RECORD,,
|
streamdown/scrape/file_0.py
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
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)
|
streamdown/scrape/file_1.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
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);
|
streamdown/scrape/file_2.cpp
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
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/tt.mds
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
streamdown/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
streamdown/sd.py,sha256=0Ug2grAsf_RWVMyDMOc0j_ZTmL8N6dOPpk24LKBPkhA,35641
|
|
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.16.0.dist-info/METADATA,sha256=sq6eIyimqRI8cZHKkNbDdP3OoYpEPYCU-v5LFhBgYfQ,7968
|
|
10
|
-
streamdown-0.16.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
-
streamdown-0.16.0.dist-info/entry_points.txt,sha256=HroKFsFMGf_h9PRTE96NjvjJQWupMW5TGP5RGUr1O_Q,74
|
|
12
|
-
streamdown-0.16.0.dist-info/licenses/LICENSE.MIT,sha256=SnY46EPirUsF20dZDR8HpyVgS2_4Tjxuc6f-4OdqO7U,1070
|
|
13
|
-
streamdown-0.16.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|