streamdown 0.25.0__py3-none-any.whl → 0.27.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 CHANGED
@@ -206,7 +206,8 @@ class ParseState:
206
206
  return offset + (state.current_width(listwidth = True) if Style.PrettyBroken else self.WidthFull)
207
207
 
208
208
  def current_width(self, listwidth = False):
209
- return self.Width - (len(visible(self.space_left(listwidth))))
209
+ # this will double count the left margin
210
+ return self.Width - (len(visible(self.space_left(listwidth)))) + Style.Margin
210
211
 
211
212
  def space_left(self, listwidth = False):
212
213
  pre = ' ' * (len(state.list_item_stack)) * Style.ListIndent if listwidth else ''
@@ -235,7 +236,7 @@ def format_table(rowList):
235
236
 
236
237
  # Calculate max width per column (integer division)
237
238
  # Subtract num_cols + 1 for the vertical borders '│'
238
- available_width = state.current_width() - (num_cols + 1)
239
+ available_width = state.current_width() - (num_cols * 2)
239
240
 
240
241
  width_base = available_width // num_cols
241
242
  width_mod = available_width % num_cols
@@ -285,17 +286,23 @@ def format_table(rowList):
285
286
 
286
287
  def emit_h(level, text):
287
288
  text = line_format(text)
288
- spaces_to_center = (state.current_width() - visible_length(text)) / 2
289
- if level == 1: #
290
- return f"{state.space_left()}\n{state.space_left()}{BOLD[1]}{' ' * math.floor(spaces_to_center)}{text}{BOLD[1]}"
291
- elif level == 2: ##
292
- 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}"
293
- elif level == 3: ###
294
- return f"{state.space_left()}{FG}{Style.Head}{BOLD[0]}{text}{RESET}"
295
- elif level == 4: ####
296
- return f"{state.space_left()}{FG}{Style.Symbol}{text}{RESET}"
297
- else: # level 5 or 6
298
- return f"{state.space_left()}{text}{RESET}"
289
+ lineList = text_wrap(text)
290
+ res = []
291
+ for text in lineList:
292
+ spaces_to_center = (state.current_width() - visible_length(text)) / 2
293
+ if level == 1: #
294
+ res.append(f"{state.space_left()}\n{state.space_left()}{BOLD[1]}{' ' * math.floor(spaces_to_center)}{text}{BOLD[1]}")
295
+ elif level == 2: ##
296
+ res.append(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}")
297
+ elif level == 3: ###
298
+ res.append(f"{state.space_left()}{FG}{Style.Head}{BOLD[0]}{text}{RESET}")
299
+ elif level == 4: ####
300
+ res.append(f"{state.space_left()}{FG}{Style.Symbol}{text}{RESET}")
301
+ elif level == 5: #####
302
+ res.append(f"{state.space_left()}{text}{RESET}")
303
+ else:
304
+ res.append(f"{state.space_left()}{FG}{Style.Grey}{text}{RESET}")
305
+ return "\n".join(res)
299
306
 
300
307
  def code_wrap(text_in):
301
308
  if not Style.PrettyBroken and state.WidthWrap and len(text_in) > state.full_width():
@@ -379,9 +386,7 @@ def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_lin
379
386
 
380
387
  # The empty word clears the buffer at the end.
381
388
  formatted = line_format(text)
382
- #print(bytes(formatted, 'utf-8'), formatted)
383
389
  words = split_text(formatted) + [""]
384
- #print([bytes(i, 'utf-8') for i in words])
385
390
 
386
391
  lines = []
387
392
  current_line = ""
@@ -432,11 +437,15 @@ def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_lin
432
437
  if len(lines) < 1:
433
438
  return []
434
439
 
440
+ if len(lines) == 1:
441
+ lines[0] = lines[0].rstrip()
442
+
435
443
  return lines
436
444
 
437
445
  def dbl_count(s):
438
446
  dbl_re = re.compile(
439
447
  r'[\u2e80-\u2eff\u3000-\u303f\u3400-\u4dbf'
448
+ r'\uFF00-\uFFEF' # CJK Compatibility Punctuation
440
449
  r'\U00004e00-\U00009fff\U0001f300-\U0001f6ff'
441
450
  r'\U0001f900-\U0001f9ff\U0001fa70-\U0001faff]',
442
451
  re.UNICODE
@@ -448,7 +457,7 @@ def cjk_count(s):
448
457
  r'[\u4E00-\u9FFF' # CJK Unified Ideographs
449
458
  r'\u3400-\u4DBF' # CJK Unified Ideographs Extension A
450
459
  r'\uF900-\uFAFF' # CJK Compatibility Ideographs
451
- r'\uFF00-\uFFEF' # CJK Compatibility Punctuation
460
+ r'\uFF00-\uFFEF' # CJK Compatibility Punctuation
452
461
  r'\u3000-\u303F' # CJK Symbols and Punctuation
453
462
  r'\U0002F800-\U0002FA1F]' # CJK Compatibility Ideographs Supplement
454
463
  )
@@ -456,7 +465,7 @@ def cjk_count(s):
456
465
  return len(cjk_re.findall(visible(s)))
457
466
 
458
467
  def line_format(line):
459
- not_text = lambda token: not (token.isalnum() or token == '\\') or cjk_count(token)
468
+ not_text = lambda token: not (token.isalnum() or token in ['\\','"']) or cjk_count(token)
460
469
  footnotes = lambda match: ''.join([chr(SUPER[int(i)]) for i in match.group(1)])
461
470
 
462
471
  def process_images(match):
@@ -686,7 +695,7 @@ def parse(stream):
686
695
 
687
696
  # <code><pre>
688
697
  if not state.in_code:
689
- code_match = re.match(r"^\s*```\s*([^\s]+|$)\s*$", line)
698
+ code_match = re.match(r"^\s*(```|<pre>)\s*([^\s]+|$)\s*$", line)
690
699
  if code_match:
691
700
  state.in_code = Code.Backtick
692
701
  state.code_indent = len(line) - len(line.lstrip())
@@ -720,10 +729,8 @@ def parse(stream):
720
729
  if state.in_code:
721
730
  try:
722
731
  # This is turning it OFF
723
- if not state.code_first_line and (
724
- ( state.in_code == Code.Backtick and line.strip() == "```" ) or
725
- (state.CodeSpaces and state.in_code == Code.Spaces and not line.startswith(' '))
726
- ):
732
+ if ( ( state.in_code == Code.Backtick and line.strip() in ["</pre>", "```"] ) or
733
+ (state.CodeSpaces and state.in_code == Code.Spaces and not line.startswith(' ')) ):
727
734
  if state.scrape:
728
735
  ext = "sh"
729
736
  try:
@@ -914,7 +921,7 @@ def parse(stream):
914
921
  # a weird thing
915
922
  if state.in_list:
916
923
  indent = (len(state.list_item_stack) - 1) * Style.ListIndent #+ (len(bullet) - 1)
917
- wrap_width = state.current_width() - indent - (2 * Style.ListIndent)
924
+ wrap_width = state.current_width(listwidth = True) - Style.ListIndent
918
925
 
919
926
  wrapped_lineList = text_wrap(content, wrap_width, Style.ListIndent,
920
927
  first_line_prefix = f"{(' ' * indent)}{FG}{Style.Symbol}{bullet}{RESET} ",
@@ -946,6 +953,11 @@ def parse(stream):
946
953
  continue
947
954
 
948
955
  state.where_from = "emit_normal"
956
+
957
+ # if we've gotten to an emit normal then we can assert that our list stack should
958
+ # be empty. This is a hack.
959
+ state.list_item_stack = []
960
+
949
961
  if len(line) == 0: yield ""
950
962
  if len(line) < state.Width:
951
963
  # we want to prevent word wrap
@@ -1010,19 +1022,23 @@ def apply_multipliers(style, name, H, S, V):
1010
1022
  return ';'.join([str(int(x * 256)) for x in [r, g, b]]) + "m"
1011
1023
 
1012
1024
  def width_calc():
1013
- if not state.WidthFull or not state.WidthArg:
1014
- if state.WidthArg:
1015
- state.WidthFull = state.WidthArg
1016
- else:
1017
- width = 80
1025
+ if state.WidthArg:
1026
+ width = state.WidthArg
1027
+ else:
1028
+ try:
1029
+ width = shutil.get_terminal_size().columns
1030
+ state.WidthWrap = True
1031
+ except (AttributeError, OSError):
1032
+ width = 80
1033
+ pass
1018
1034
 
1019
- try:
1020
- width = shutil.get_terminal_size().columns
1021
- state.WidthWrap = True
1022
- except (AttributeError, OSError):
1023
- pass
1024
1035
 
1025
- state.WidthFull = width
1036
+ # This can't be done because our list item stack can change as well so
1037
+ # unless we want to track that too, we're SOL
1038
+ #if state.WidthFull == width:
1039
+ # return
1040
+
1041
+ state.WidthFull = width
1026
1042
 
1027
1043
  state.Width = state.WidthFull - 2 * Style.Margin
1028
1044
  pre = state.space_left(listwidth=True) if Style.PrettyBroken else ''
@@ -1041,8 +1057,24 @@ def main():
1041
1057
  parser.add_argument("-w", "--width", default="0", help="Set the width WIDTH")
1042
1058
  parser.add_argument("-e", "--exec", help="Wrap a program EXEC for more 'proper' i/o handling")
1043
1059
  parser.add_argument("-s", "--scrape", help="Scrape code snippets to a directory SCRAPE")
1060
+ parser.add_argument("-v", "--version", action="store_true", help="Show version information")
1044
1061
  args = parser.parse_args()
1045
1062
 
1063
+ if args.version:
1064
+ try:
1065
+ import importlib.metadata
1066
+ print(importlib.metadata.version("streamdown"))
1067
+ except importlib.metadata.PackageNotFoundError:
1068
+ import subprocess
1069
+ print(subprocess.run(
1070
+ ['git', 'describe', '--always', '--dirty', '--tags'],
1071
+ cwd=os.path.dirname(os.path.abspath(__file__)),
1072
+ stdout=subprocess.PIPE,
1073
+ text=True
1074
+ ).stdout.strip())
1075
+
1076
+ sys.exit(0)
1077
+
1046
1078
  config_toml_path, config_toml_content = ensure_config_file(args.config)
1047
1079
  config = toml.loads(config_toml_content)
1048
1080
  style = toml.loads(default_toml).get('style') | config.get("style", {})
streamdown/ss ADDED
@@ -0,0 +1 @@
1
+ * **Model Card:** Always read the model card on the Hugging Face Hub ([https://huggingface.co/microsoft/bitnet-b1.58-2B-4T](https://huggingface.co/microsoft/bitnet-b1.58-2B-4T)) for important information about the model, its intended use, limitations, and potential biases.
streamdown/ss1 ADDED
@@ -0,0 +1,42 @@
1
+ * `model.safetensors`: 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.58-2B-4T](https://huggingface.co/microsoft/bitnet-b1.58-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.
2
+ * `--local-dir ./bitnet-b1.58-2B-4T`: The directory to save the file to.
3
+
4
+ * **Download using `transformers` library (recommended for most use cases):**
5
+
6
+ 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.
7
+
8
+ ```python
9
+ from transformers import AutoModelForCausalLM, AutoTokenizer
10
+
11
+ model_name = "microsoft/bitnet-b1.58-2B-4T"
12
+
13
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
14
+ model = AutoModelForCausalLM.from_pretrained(model_name)
15
+
16
+ # The model and tokenizer will be downloaded and cached in your
17
+ # transformers cache directory (usually ~/.cache/huggingface/transformers).
18
+ ```
19
+
20
+ This approach automatically handles downloading the necessary files and caching them for future use. It also handles the correct file formats and configurations.
21
+
22
+ **4. Checking the Download**
23
+
24
+ After the download completes, verify that the files are in the specified directory. You can use `ls` (Linux/macOS) or `dir` (Windows) to list the contents of the directory.
25
+
26
+ **Important Considerations:**
27
+
28
+ * **Disk Space:** The `bitnet-b1.58-2B-4T` model is quite large (several gigabytes). Make sure you have enough free disk space before downloading.
29
+ * **Network Connection:** A stable and fast internet connection is essential for a smooth download.
30
+ * **Caching:** The Hugging Face Hub and `transformers` library use caching to avoid re-downloading models unnecessarily. The default cache directory is usually `~/.cache/huggingface/transformers`.
31
+ * **File Formats:** Models are often stored in `safetensors` or `.bin` formats. `safetensors` is generally preferred for security and performance.
32
+ * **Model Card:** Always read the model card on the Hugging Face Hub ([https://huggingface.co/microsoft/bitnet-b1.58-2B-4T](https://huggingface.co/microsoft/bitnet-b1.58-2B-4T)) for important information about the model, its intended use, limitations, and potential biases.
33
+ * **Gated Models:** Some models require you to accept terms of use before you can download them. The `huggingface-cli login` command will guide you through this process if necessary.
34
+
35
+ **Example Workflow (Recommended):**
36
+
37
+ 1. `huggingface-cli login` (if not already logged in)
38
+ 2. Use the `transformers` library in a Python script to download and load the model (as shown in the example above). This is the most convenient and reliable method for most use cases.
39
+
40
+ Let me know if you have any other questions or if you'd like help with a specific task related to this model!
41
+
42
+ >
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: streamdown
3
- Version: 0.25.0
3
+ Version: 0.27.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
@@ -188,7 +188,3 @@ After the git clone least one of these should work, hopefully. it's using the mo
188
188
  $ pip install -e .
189
189
  $ uv pip install -e .
190
190
 
191
- ### Future work
192
-
193
- #### Glow styles
194
- 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,11 @@
1
+ streamdown/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ streamdown/sd.py,sha256=hk9h-gZz9OJw88iHowC6mWk0-ETRMmKmdr3063c8e74,43469
3
+ streamdown/ss,sha256=sel_phpaecrw6WGIHRLROsD7BFShf0rSDHheflwdUn8,277
4
+ streamdown/ss1,sha256=CUVf86_2zeAle2oQCeTfWYqtHBrAFR_UgvptuYMQzFU,3151
5
+ streamdown/plugins/README.md,sha256=KWqYELs9WkKJmuDzYv3cvPlZMkArsNCBUe4XDoTLjLA,1143
6
+ streamdown/plugins/latex.py,sha256=xZMGMdx_Sw4X1piZejXFHfEG9qazU4fGeceiMI0h13Y,648
7
+ streamdown-0.27.0.dist-info/METADATA,sha256=_5zd5q0T0LSEk9kJK2CZitv7HfboKIhLrEsWjUfdmx8,9425
8
+ streamdown-0.27.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
+ streamdown-0.27.0.dist-info/entry_points.txt,sha256=HroKFsFMGf_h9PRTE96NjvjJQWupMW5TGP5RGUr1O_Q,74
10
+ streamdown-0.27.0.dist-info/licenses/LICENSE.MIT,sha256=SnY46EPirUsF20dZDR8HpyVgS2_4Tjxuc6f-4OdqO7U,1070
11
+ streamdown-0.27.0.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- streamdown/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- streamdown/sd.py,sha256=ffr_xygYCNseJ_jkvie88Omiz9aaOhY32_-dDj1LdUY,42326
3
- streamdown/plugins/README.md,sha256=KWqYELs9WkKJmuDzYv3cvPlZMkArsNCBUe4XDoTLjLA,1143
4
- streamdown/plugins/latex.py,sha256=xZMGMdx_Sw4X1piZejXFHfEG9qazU4fGeceiMI0h13Y,648
5
- streamdown-0.25.0.dist-info/METADATA,sha256=H9-T77e6Rs7fN-lJGG07So4uxqv6utRZrN98B8lLPH4,9658
6
- streamdown-0.25.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
- streamdown-0.25.0.dist-info/entry_points.txt,sha256=HroKFsFMGf_h9PRTE96NjvjJQWupMW5TGP5RGUr1O_Q,74
8
- streamdown-0.25.0.dist-info/licenses/LICENSE.MIT,sha256=SnY46EPirUsF20dZDR8HpyVgS2_4Tjxuc6f-4OdqO7U,1070
9
- streamdown-0.25.0.dist-info/RECORD,,