streamdown 0.13.0__tar.gz → 0.15.0__tar.gz
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-0.13.0 → streamdown-0.15.0}/PKG-INFO +19 -16
- {streamdown-0.13.0 → streamdown-0.15.0}/README.md +17 -15
- {streamdown-0.13.0 → streamdown-0.15.0}/pyproject.toml +2 -1
- streamdown-0.15.0/python-go.png +0 -0
- streamdown-0.15.0/pythonvgo.md +187 -0
- streamdown-0.15.0/streamdown/scrape/file_0.py +22 -0
- streamdown-0.15.0/streamdown/scrape/file_1.js +27 -0
- streamdown-0.15.0/streamdown/scrape/file_2.cpp +23 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/streamdown/sd.py +123 -78
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/chunk-buffer.sh +1 -1
- {streamdown-0.13.0 → streamdown-0.15.0}/.gitignore +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/.vimrc +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/24-bit-color.sh +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/LICENSE.MIT +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/configurable.png +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/copyable.png +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/dunder.png +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/error.txt +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/newdir/file_0.py +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/newdir/file_1.rb +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/newdir/file_2.jl +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/passthrough.py +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/somelog.txt +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/streamdown/__init__.py +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/streamdown/plugins/README.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/streamdown/plugins/latex.py +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/streamdown/tt.mds +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/table.png +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/temp.py +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/test.py +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/test_input.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/README.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/block.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/code.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/example.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/fizzbuzz.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/inline.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/line-buffer.sh +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/line-wrap.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/line.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/links.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/longer-example.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/mandlebrot.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/markdown.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/nested-example.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/new.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/outline.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/sd.log +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/table-break.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/table.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/table_test.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/test.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/test_input.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/white-space-code.md +0 -0
- {streamdown-0.13.0 → streamdown-0.15.0}/tests/wm.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: streamdown
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.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
|
|
@@ -23,6 +23,7 @@ Requires-Python: >=3.8
|
|
|
23
23
|
Requires-Dist: appdirs
|
|
24
24
|
Requires-Dist: pygments
|
|
25
25
|
Requires-Dist: pylatexenc
|
|
26
|
+
Requires-Dist: term-image
|
|
26
27
|
Requires-Dist: toml
|
|
27
28
|
Description-Content-Type: text/markdown
|
|
28
29
|
|
|
@@ -30,26 +31,30 @@ Description-Content-Type: text/markdown
|
|
|
30
31
|
|
|
31
32
|
[](https://badge.fury.io/py/streamdown)
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
Streamdown is the streaming markdown renderer for the terminal that rocks.
|
|
35
|
+
This will work with [simonw's llm](https://github.com/simonw/llm). You even get full readline and keyboard navigation support.
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
It's fully streaming and does not block
|
|
38
|
+

|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
### Provides clean copyable code for long code lines
|
|
41
|
+
You may have noticed that other, *inferior* renderers will inject line breaks when copying code that wraps around. We're better and now, you can be as well.
|
|
42
|
+

|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
### Supports images
|
|
45
|
+
Here's kitty and alacritty. Try to do that in glow...
|
|
46
|
+

|
|
40
47
|
|
|
41
|
-
###
|
|
42
|
-

|
|
43
|
-
|
|
44
|
-
### Does OSC 8 links for modern terminals.
|
|
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
|
-
### Doesn't consume characters like _ and * as style when they are in `blocks like this` because `_they_can_be_varaiables_`
|
|
48
|
-

|
|
49
|
-
|
|
50
51
|
### Tables are carefully supported
|
|
51
52
|

|
|
52
53
|
|
|
54
|
+
As well as everything else...
|
|
55
|
+
|
|
56
|
+

|
|
57
|
+
|
|
53
58
|
### Colors are highly (and quickly) configurable for people who care a lot, or just a little.
|
|
54
59
|

|
|
55
60
|
|
|
@@ -80,7 +85,7 @@ Defines the base Hue (H), Saturation (S), and Value (V) from which all other pal
|
|
|
80
85
|
* `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
|
|
81
86
|
* `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
|
|
82
87
|
* `ListIndent` (integer, default: `2`): This is the recursive indent for the list styles.
|
|
83
|
-
* `Syntax` (string, default `monokai`): This the syntax [highlighting theme which come via pygments](https://pygments.org/styles/).
|
|
88
|
+
* `Syntax` (string, default `monokai`): This is the syntax [highlighting theme which come via pygments](https://pygments.org/styles/).
|
|
84
89
|
|
|
85
90
|
Example:
|
|
86
91
|
```toml
|
|
@@ -139,10 +144,8 @@ Do this
|
|
|
139
144
|
|
|
140
145
|
$ ./streamdown/sd.py tests/*md
|
|
141
146
|
|
|
142
|
-
Certainly room for improvement and I'll probably continue to make them
|
|
143
|
-
|
|
144
147
|
## Install from source
|
|
145
|
-
|
|
148
|
+
After the git clone least one of these should work, hopefully. it's using the modern uv pip tool.
|
|
146
149
|
|
|
147
150
|
$ pipx install -e .
|
|
148
151
|
$ pip install -e .
|
|
@@ -2,26 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/py/streamdown)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Streamdown is the streaming markdown renderer for the terminal that rocks.
|
|
6
|
+
This will work with [simonw's llm](https://github.com/simonw/llm). You even get full readline and keyboard navigation support.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
It's fully streaming and does not block
|
|
9
|
+

|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
### Provides clean copyable code for long code lines
|
|
12
|
+
You may have noticed that other, *inferior* renderers will inject line breaks when copying code that wraps around. We're better and now, you can be as well.
|
|
13
|
+

|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
### Supports images
|
|
16
|
+
Here's kitty and alacritty. Try to do that in glow...
|
|
17
|
+

|
|
12
18
|
|
|
13
|
-
###
|
|
14
|
-

|
|
15
|
-
|
|
16
|
-
### Does OSC 8 links for modern terminals.
|
|
19
|
+
### Does OSC 8 links for modern terminals (and optionally OSC 52 for clipboard)
|
|
17
20
|
[links.webm](https://github.com/user-attachments/assets/a5f71791-7c58-4183-ad3b-309f470c08a3)
|
|
18
21
|
|
|
19
|
-
### Doesn't consume characters like _ and * as style when they are in `blocks like this` because `_they_can_be_varaiables_`
|
|
20
|
-

|
|
21
|
-
|
|
22
22
|
### Tables are carefully supported
|
|
23
23
|

|
|
24
24
|
|
|
25
|
+
As well as everything else...
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
25
29
|
### Colors are highly (and quickly) configurable for people who care a lot, or just a little.
|
|
26
30
|

|
|
27
31
|
|
|
@@ -52,7 +56,7 @@ Defines the base Hue (H), Saturation (S), and Value (V) from which all other pal
|
|
|
52
56
|
* `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
|
|
53
57
|
* `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
|
|
54
58
|
* `ListIndent` (integer, default: `2`): This is the recursive indent for the list styles.
|
|
55
|
-
* `Syntax` (string, default `monokai`): This the syntax [highlighting theme which come via pygments](https://pygments.org/styles/).
|
|
59
|
+
* `Syntax` (string, default `monokai`): This is the syntax [highlighting theme which come via pygments](https://pygments.org/styles/).
|
|
56
60
|
|
|
57
61
|
Example:
|
|
58
62
|
```toml
|
|
@@ -111,10 +115,8 @@ Do this
|
|
|
111
115
|
|
|
112
116
|
$ ./streamdown/sd.py tests/*md
|
|
113
117
|
|
|
114
|
-
Certainly room for improvement and I'll probably continue to make them
|
|
115
|
-
|
|
116
118
|
## Install from source
|
|
117
|
-
|
|
119
|
+
After the git clone least one of these should work, hopefully. it's using the modern uv pip tool.
|
|
118
120
|
|
|
119
121
|
$ pipx install -e .
|
|
120
122
|
$ pip install -e .
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "streamdown"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.15.0"
|
|
8
8
|
description = "A streaming markdown renderer for modern terminals with syntax highlighting"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -31,6 +31,7 @@ dependencies = [
|
|
|
31
31
|
"appdirs",
|
|
32
32
|
"toml",
|
|
33
33
|
"pylatexenc",
|
|
34
|
+
'term-image'
|
|
34
35
|
]
|
|
35
36
|
|
|
36
37
|
[project.urls]
|
|
Binary file
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
🫣# Python vs. Go: Let's get ready to rumble!
|
|
2
|
+

|
|
3
|
+
🫣
|
|
4
|
+
Python and Go (Golang) are both popular programming languages, but they cater to different needs and philosophies. Here's a detailed comparison:
|
|
5
|
+
🫣
|
|
6
|
+
|
|
7
|
+
**Python:** 🫣
|
|
8
|
+
|
|
9
|
+
* **Strengths:** 🫣
|
|
10
|
+
* **Readability:** Known for its clean and easy-to-understand syntax, emphasizing code readability. 🫣
|
|
11
|
+
* **Large Ecosystem:** Vast library support for data science, machine learning, web development, scripting, and more. Packages like NumPy, Pandas, Django, and Flask make complex tasks simpler. 🫣
|
|
12
|
+
* **Rapid Development:** Its dyna🫣mic typing and interpreted nature allow for quick prototyping and development.
|
|
13
|
+
* **Dyn🫣amic Typing:** The interpreter infers data types at runtime, simplifying code (but potentially hiding errors). 🫣
|
|
14
|
+
* **Versatility:** Can be used for a wide range of applications – web backends, data analysis, machine learning, automation, scripting, testing, and more. 🫣
|
|
15
|
+
* **Community:** Huge and active community providing ample support, tutorials, and resources. 🫣
|
|
16
|
+
* **Weaknesses:**
|
|
17
|
+
* **Performance:** Being an interpreted language, Python is generally slower than compiled languages like Go. 🫣
|
|
18
|
+
* **Global Interpreter Lock (GIL):** Limits true multi-threading in CPU-bound operations. 🫣
|
|
19
|
+
* **Error Handling:** Runtime errors can be more common because of dynamic typing. 🫣
|
|
20
|
+
* **Memory Consumption:** Typically has higher memory overhead compared to Go due to its object model and dynamic typing.
|
|
21
|
+
|
|
22
|
+
**Go:**
|
|
23
|
+
|
|
24
|
+
* **Strengths:**
|
|
25
|
+
* **Performance:** Compiled language that produces efficient, native machine code. Generally much faster than Python.
|
|
26
|
+
* **Concurrency:** Built-in support for concurrency through Goroutines (lightweight threads) and Channels, making it easy to write concurrent and parallel programs.
|
|
27
|
+
🫣 * **Static Typing:** Helps catch errors at compile-time.
|
|
28
|
+
* **Garbage Collection:** Automatic memory management reduces the risk of memory leaks.
|
|
29
|
+
* **Simplicity:** Designed 🫣to be a simple language with a relatively small number of keywords. Focuses on getting things done efficiently.
|
|
30
|
+
* **Scalability:** Well-suited for building scalable network services and distributed systems.
|
|
31
|
+
* **Tooling:** E🫣xcellent built-in tooling for testing, formatting, and dependency management.
|
|
32
|
+
* **Weaknesses:**
|
|
33
|
+
* **Learning Curve:** Can be slightly steeper than Python initially, particularly regarding concurre🫣ncy concepts.
|
|
34
|
+
* **Error Handling:** Explicit error handling (returning errors as values) can lead to verbose code. (While necessary, it's less concise than Python's `try...except`)
|
|
35
|
+
* **Generics (Relatively New):** Generics were only added in Go 1.18 (released in 2022). Prior to that, code reusability for different types was more challenging.
|
|
36
|
+
* **Smalle🫣r Ecosystem:** While rapidly growing, Go’s ecosystem is still smaller than Python's, particularly in specialized areas like data science.
|
|
37
|
+
|
|
38
|
+
**Use Cases:**
|
|
39
|
+
|
|
40
|
+
* **Python:** Data science, machine learning, web development (Django, Flask), scripting, automation, prototyping, and educational purposes.
|
|
41
|
+
* **Go:** Cloud infrastructure (Docker, Kubernetes), network services, distributed systems, command-line tools, DevOps, and high-performance backend services.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Code Examples:
|
|
46
|
+
|
|
47
|
+
**1. Hello World:**
|
|
48
|
+
|
|
49
|
+
**Python:**
|
|
50
|
+
```🫣python
|
|
51
|
+
print("Hello, World!")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Go:**
|
|
55
|
+
```go
|
|
56
|
+
package main
|
|
57
|
+
|
|
58
|
+
import "fmt"
|
|
59
|
+
|
|
60
|
+
func main() {
|
|
61
|
+
fmt.Println("Hello, World!")
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**2. Simple Web Server:**
|
|
66
|
+
|
|
67
|
+
**Python (using Flask🫣):**
|
|
68
|
+
```python
|
|
69
|
+
from flask import Flask
|
|
70
|
+
|
|
71
|
+
app = Flask(__name__)
|
|
72
|
+
|
|
73
|
+
@app.route("/")
|
|
74
|
+
def hello_world():
|
|
75
|
+
return "<p>Hello, World!</p>"
|
|
76
|
+
|
|
77
|
+
if __name__ == '__main__':
|
|
78
|
+
app.run(debug=True)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Go (using net/http):**
|
|
82
|
+
```go
|
|
83
|
+
package main
|
|
84
|
+
|
|
85
|
+
import (
|
|
86
|
+
"fmt"
|
|
87
|
+
"net/http"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
func handler(w http.ResponseWriter, r *http.Request) {
|
|
91
|
+
fmt.Fprintf(w, "Hello, World!")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
func main() {
|
|
95
|
+
http.HandleFunc("/", handler)
|
|
96
|
+
fmt.Println("Server listening on port 8080")
|
|
97
|
+
http.ListenAndServe(":8080", nil)
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**3. Concurrent Processing (Simple):**
|
|
102
|
+
|
|
103
|
+
**Python (using threading - limited by GIL):**
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import threading
|
|
107
|
+
|
|
108
|
+
def process_task(task_id):
|
|
109
|
+
print(f"Task {task_id} started")
|
|
110
|
+
# Simulate some work
|
|
111
|
+
import time
|
|
112
|
+
time.sleep(2)
|
|
113
|
+
print(f"Task {task_id} completed")
|
|
114
|
+
|
|
115
|
+
tasks = [1, 2, 3]
|
|
116
|
+
threads = []
|
|
117
|
+
|
|
118
|
+
for task in tasks:
|
|
119
|
+
thread = threading.Thread(🫣target=process_task, args=(task,))
|
|
120
|
+
threads.append(thread)
|
|
121
|
+
thread.start()
|
|
122
|
+
|
|
123
|
+
for thread in threads:
|
|
124
|
+
thread.join()
|
|
125
|
+
|
|
126
|
+
print("All tasks comple🫣ted.")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Go (using Goroutines and Channels):**
|
|
130
|
+
|
|
131
|
+
```go
|
|
132
|
+
package main
|
|
133
|
+
|
|
134
|
+
import (
|
|
135
|
+
"fmt"
|
|
136
|
+
"sync"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
func processTask(taskID int, wg *sync.WaitGroup) {
|
|
140
|
+
defer wg.Done() // Decrement the WaitGroup counter when the goroutin🫣e completes.
|
|
141
|
+
fmt.Printf("Task %d started🫣\n", taskID)
|
|
142
|
+
// Simulate some work
|
|
143
|
+
//time.Sleep(2 * time🫣.Second) // Go uses time.Second, etc.
|
|
144
|
+
fmt.Printf("Task %d completed\n", taskID)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
func main() {
|
|
148
|
+
var wg sync.WaitGroup
|
|
149
|
+
tasks := []int{1, 2, 3}
|
|
150
|
+
|
|
151
|
+
for _, task := range tasks {
|
|
152
|
+
wg.Add(1) // Increment the WaitGroup counter for each🫣 goroutine.
|
|
153
|
+
go processTask(task, &wg)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
wg.Wait()
|
|
157
|
+
fmt.Prin🫣tln("All tasks completed.")
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
## Comparative Table o🫣f Features:
|
|
163
|
+
|
|
164
|
+
| Feature | Python | Go (Golang) |
|
|
165
|
+
|----------------------|----------------------------------|-------------------------------|
|
|
166
|
+
| **Typi🫣ng** | Dynamic, strong 🫣 | Static, strong |
|
|
167
|
+
| **Compilation** | Interpreted | Compiled |
|
|
168
|
+
| **Perfo🫣rmance** | Generally slower | Generally faster |
|
|
169
|
+
| **Concurrency** | Through threads (GIL limited) | Goroutines & Chan🫣nels 🫣(built-in) |
|
|
170
|
+
| **Error Handling** | Exceptions (try-except) | Explicit error val🫣ues |
|
|
171
|
+
| **Memory Management**| Automatic (Garbage Collection) | Automatic (Garbage Collection) |
|
|
172
|
+
| **Syntax** | Readable, concise | Simple, explicit |
|
|
173
|
+
| **Ecosystem**🫣 | Huge, mature | Growing, focused |
|
|
174
|
+
| **Learning Curve** | Easier | Moderate |
|
|
175
|
+
| **Generics** | Present | Added in 1.18 (relatively new)|
|
|
176
|
+
| **Typical Use Cases**| Data science, web dev, scripting | Cloud, networking, system programming |
|
|
177
|
+
| **Community** | Very large, active | Growing, dedicated |
|
|
178
|
+
| **Object Orientation**| Full support | Structs with methods, interfaces|
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
**In Summary:**
|
|
183
|
+
|
|
184
|
+
* **Choose Python 🫣if:** You need rapid development, a large ecosystem of libraries, or are focused on data science, machine learning, or scripting. Readability and ease of use are priorities.
|
|
185
|
+
|
|
186
|
+
* **Choose Go if:** You need high performance, concurrency, scalability, and are building infrastructure, network services, or command-line tools. Deterministic error handling and a simple, efficient language are key.
|
|
187
|
+
🫣
|
|
@@ -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
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
# "pygments",
|
|
6
6
|
# "pylatexenc",
|
|
7
7
|
# "appdirs",
|
|
8
|
+
# "term-image",
|
|
8
9
|
# "toml"
|
|
9
10
|
# ]
|
|
10
11
|
# ///
|
|
@@ -23,7 +24,9 @@ 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
|
|
29
|
+
from functools import reduce
|
|
27
30
|
from argparse import ArgumentParser
|
|
28
31
|
from pygments import highlight
|
|
29
32
|
from pygments.lexers import get_lexer_by_name
|
|
@@ -52,7 +55,7 @@ Dark = { H = 1.00, S = 1.50, V = 0.25 }
|
|
|
52
55
|
Mid = { H = 1.00, S = 1.00, V = 0.50 }
|
|
53
56
|
Symbol = { H = 1.00, S = 1.00, V = 1.50 }
|
|
54
57
|
Head = { H = 1.00, S = 2.00, V = 1.50 }
|
|
55
|
-
Grey = { H = 1.00, S = 0.
|
|
58
|
+
Grey = { H = 1.00, S = 0.25, V = 1.37 }
|
|
56
59
|
Bright = { H = 1.00, S = 2.00, V = 2.00 }
|
|
57
60
|
Syntax = "monokai"
|
|
58
61
|
"""
|
|
@@ -80,16 +83,17 @@ BGRESET = "\033[49m"
|
|
|
80
83
|
BOLD = ["\033[1m", "\033[22m"]
|
|
81
84
|
UNDERLINE = ["\033[4m", "\033[24m"]
|
|
82
85
|
ITALIC = ["\033[3m", "\033[23m"]
|
|
86
|
+
STRIKEOUT = ["\033[9m", "\033[29m"]
|
|
87
|
+
SUPER = [ 0x2070, 0x00B9, 0x00B2, 0x00B3, 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, 0x2079 ]
|
|
83
88
|
|
|
84
89
|
ESCAPE = r"\033\[[0-9;]*[mK]"
|
|
85
90
|
ANSIESCAPE = r'\033(?:\[[0-9;?]*[a-zA-Z]|][0-9]*;;.*?\\|\\)'
|
|
86
|
-
#r"\033(\[[0-9;]*[mK]|][0-9]*;;.*?\\|\\)"
|
|
87
91
|
KEYCODE_RE = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
|
88
92
|
|
|
89
93
|
visible = lambda x: re.sub(ANSIESCAPE, "", x)
|
|
90
94
|
visible_length = lambda x: len(visible(x))
|
|
91
|
-
|
|
92
95
|
extract_ansi_codes = lambda text: re.findall(ESCAPE, text)
|
|
96
|
+
remove_ansi = lambda line, codeList: reduce(lambda line, code: line.replace(code, ''), codeList, line)
|
|
93
97
|
|
|
94
98
|
def debug_write(text):
|
|
95
99
|
if state.Logging:
|
|
@@ -131,7 +135,10 @@ class ParseState:
|
|
|
131
135
|
self.Clipboard = _features.get("Clipboard")
|
|
132
136
|
self.Logging = _features.get("Logging")
|
|
133
137
|
self.Timeout = _features.get("Timeout")
|
|
138
|
+
|
|
134
139
|
self.WidthArg = None
|
|
140
|
+
self.WidthFull = None
|
|
141
|
+
self.WidthWrap = False
|
|
135
142
|
|
|
136
143
|
# If the entire block is indented this will
|
|
137
144
|
# tell us what that is
|
|
@@ -159,24 +166,27 @@ class ParseState:
|
|
|
159
166
|
self.in_italic = False
|
|
160
167
|
self.in_table = False # (Code.[Header|Body] | False)
|
|
161
168
|
self.in_underline = False
|
|
169
|
+
self.in_strikeout = False
|
|
162
170
|
self.block_depth = 0
|
|
163
171
|
|
|
164
172
|
self.exec_sub = None
|
|
165
173
|
self.exec_master = None
|
|
166
174
|
self.exec_slave = None
|
|
167
175
|
self.exec_kb = 0
|
|
168
|
-
self.exec_israw = False
|
|
169
176
|
|
|
170
177
|
self.exit = 0
|
|
171
178
|
self.where_from = None
|
|
172
179
|
|
|
173
180
|
def current(self):
|
|
174
|
-
state = { 'code': self.in_code, 'bold': self.in_bold, 'italic': self.in_italic, 'underline': self.in_underline }
|
|
181
|
+
state = { 'inline': self.inline_code, 'code': self.in_code, 'bold': self.in_bold, 'italic': self.in_italic, 'underline': self.in_underline }
|
|
175
182
|
state['none'] = all(item is False for item in state.values())
|
|
176
183
|
return state
|
|
177
184
|
|
|
185
|
+
def reset_inline(self):
|
|
186
|
+
self.inline_code = self.in_bold = self.in_italic = self.in_underline = False
|
|
187
|
+
|
|
178
188
|
def space_left(self):
|
|
179
|
-
return
|
|
189
|
+
return Style.MarginSpaces + (Style.Blockquote * self.block_depth) if len(self.current_line) == 0 else ""
|
|
180
190
|
|
|
181
191
|
state = ParseState()
|
|
182
192
|
|
|
@@ -233,17 +243,20 @@ def emit_h(level, text):
|
|
|
233
243
|
text = line_format(text)
|
|
234
244
|
spaces_to_center = ((state.Width - visible_length(text)) / 2)
|
|
235
245
|
if level == 1: #
|
|
236
|
-
return f"\n{
|
|
246
|
+
return f"\n{state.space_left()}{BOLD[0]}{' ' * math.floor(spaces_to_center)}{text}{' ' * math.ceil(spaces_to_center)}{BOLD[1]}\n"
|
|
237
247
|
elif level == 2: ##
|
|
238
|
-
return f"\n{
|
|
248
|
+
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
249
|
elif level == 3: ###
|
|
240
|
-
return f"{
|
|
250
|
+
return f"{state.space_left()}{FG}{Style.Head}{BOLD[0]}{text}{RESET}"
|
|
241
251
|
elif level == 4: ####
|
|
242
|
-
return f"{
|
|
252
|
+
return f"{state.space_left()}{FG}{Style.Symbol}{text}{RESET}"
|
|
243
253
|
else: # level 5 or 6
|
|
244
|
-
return f"{
|
|
254
|
+
return f"{state.space_left()}{text}{RESET}"
|
|
245
255
|
|
|
246
256
|
def code_wrap(text_in):
|
|
257
|
+
if state.WidthWrap and len(text_in) > state.WidthFull:
|
|
258
|
+
return (0, [text_in])
|
|
259
|
+
|
|
247
260
|
# get the indentation of the first line
|
|
248
261
|
indent = len(text_in) - len(text_in.lstrip())
|
|
249
262
|
text = text_in.lstrip()
|
|
@@ -259,6 +272,7 @@ def code_wrap(text_in):
|
|
|
259
272
|
|
|
260
273
|
return (indent, res)
|
|
261
274
|
|
|
275
|
+
|
|
262
276
|
# This marvelously obscure code "compacts" long lines of repetitive ANSI format strings by
|
|
263
277
|
# removing duplicates. Here's how it works
|
|
264
278
|
def ansi_collapse(codelist, inp):
|
|
@@ -337,8 +351,20 @@ def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_lin
|
|
|
337
351
|
return lines
|
|
338
352
|
|
|
339
353
|
def line_format(line):
|
|
340
|
-
|
|
341
|
-
|
|
354
|
+
not_text = lambda token: not token or len(token.rstrip()) != len(token)
|
|
355
|
+
footnotes = lambda match: ''.join([chr(SUPER[int(i)]) for i in match.group(1)])
|
|
356
|
+
|
|
357
|
+
def process_images(match):
|
|
358
|
+
url = match.group(2)
|
|
359
|
+
try:
|
|
360
|
+
if re.match(r"https://", url.lower()):
|
|
361
|
+
image = from_url(url)
|
|
362
|
+
else:
|
|
363
|
+
image = from_file(url)
|
|
364
|
+
image.height = 20
|
|
365
|
+
print(f"{image:|.-1#}")
|
|
366
|
+
except:
|
|
367
|
+
return match.group(2)
|
|
342
368
|
|
|
343
369
|
# Apply OSC 8 hyperlink formatting after other formatting
|
|
344
370
|
def process_links(match):
|
|
@@ -346,8 +372,11 @@ def line_format(line):
|
|
|
346
372
|
url = match.group(2)
|
|
347
373
|
return f'\033]8;;{url}\033\\{Style.Link}{description}{UNDERLINE[1]}\033]8;;\033\\{FGRESET}'
|
|
348
374
|
|
|
375
|
+
line = re.sub(r"\!\[([^\]]*)\]\(([^\)]+)\)", process_images, line)
|
|
349
376
|
line = re.sub(r"\[([^\]]+)\]\(([^\)]+)\)", process_links, line)
|
|
350
|
-
|
|
377
|
+
line = re.sub(r"\[\^(\d+)\]:?", footnotes, line)
|
|
378
|
+
|
|
379
|
+
tokenList = re.finditer(r"((~~|\*\*_|_\*\*|\*{1,3}|_{1,3}|`+)|[^~_*`]+)", line)
|
|
351
380
|
result = ""
|
|
352
381
|
|
|
353
382
|
for match in tokenList:
|
|
@@ -355,8 +384,13 @@ def line_format(line):
|
|
|
355
384
|
next_token = line[match.end()] if match.end() < len(line) else ""
|
|
356
385
|
prev_token = line[match.start()-1] if match.start() > 0 else ""
|
|
357
386
|
|
|
358
|
-
|
|
359
|
-
|
|
387
|
+
# This trick makes sure that things like `` ` `` render right.
|
|
388
|
+
if "`" in token and (not state.inline_code or state.inline_code == token):
|
|
389
|
+
if state.inline_code:
|
|
390
|
+
state.inline_code = False
|
|
391
|
+
else:
|
|
392
|
+
state.inline_code = token
|
|
393
|
+
|
|
360
394
|
if state.inline_code:
|
|
361
395
|
result += f'{BG}{Style.Mid}'
|
|
362
396
|
else:
|
|
@@ -367,7 +401,17 @@ def line_format(line):
|
|
|
367
401
|
elif state.inline_code:
|
|
368
402
|
result += token
|
|
369
403
|
|
|
370
|
-
elif token ==
|
|
404
|
+
elif token == '~~' and (state.in_strikeout or not_text(prev_token)):
|
|
405
|
+
state.in_strikeout = not state.in_strikeout
|
|
406
|
+
result += STRIKEOUT[0] if state.in_strikeout else STRIKEOUT[1]
|
|
407
|
+
|
|
408
|
+
elif token in ['**_','_**','___','***'] and (state.in_bold or not_text(prev_token)):
|
|
409
|
+
state.in_bold = not state.in_bold
|
|
410
|
+
result += BOLD[0] if state.in_bold else BOLD[1]
|
|
411
|
+
state.in_italic = not state.in_italic
|
|
412
|
+
result += ITALIC[0] if state.in_italic else ITALIC[1]
|
|
413
|
+
|
|
414
|
+
elif (token == '__' or token == "**") and (state.in_bold or not_text(prev_token)):
|
|
371
415
|
state.in_bold = not state.in_bold
|
|
372
416
|
result += BOLD[0] if state.in_bold else BOLD[1]
|
|
373
417
|
|
|
@@ -380,7 +424,7 @@ def line_format(line):
|
|
|
380
424
|
else:
|
|
381
425
|
result += token
|
|
382
426
|
|
|
383
|
-
elif token == "_" and (state.in_underline or not_text(prev_token)):
|
|
427
|
+
elif token == "_" and (state.in_underline or (not_text(prev_token) and next_token.isalnum())):
|
|
384
428
|
state.in_underline = not state.in_underline
|
|
385
429
|
result += UNDERLINE[0] if state.in_underline else UNDERLINE[1]
|
|
386
430
|
else:
|
|
@@ -422,8 +466,6 @@ def parse(stream):
|
|
|
422
466
|
|
|
423
467
|
if len(ready_in) == 0:
|
|
424
468
|
TimeoutIx += 1
|
|
425
|
-
|
|
426
|
-
|
|
427
469
|
|
|
428
470
|
elif stream.fileno() in ready_in:
|
|
429
471
|
byte = os.read(stream.fileno(), 1)
|
|
@@ -462,13 +504,30 @@ def parse(stream):
|
|
|
462
504
|
# Run through the plugins first
|
|
463
505
|
res = latex.Plugin(line, state, Style)
|
|
464
506
|
if res is True:
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
continue
|
|
468
|
-
elif res is not None:
|
|
469
|
-
for row in res:
|
|
470
|
-
yield row
|
|
507
|
+
# This means everything was consumed by our plugin and
|
|
508
|
+
# we should continue
|
|
471
509
|
continue
|
|
510
|
+
elif res is not None:
|
|
511
|
+
for row in res:
|
|
512
|
+
yield row
|
|
513
|
+
continue
|
|
514
|
+
|
|
515
|
+
# running this here avoids stray |
|
|
516
|
+
block_match = re.match(r"^\s*((>\s*)+|<.?think>)", line)
|
|
517
|
+
if not state.in_code and block_match:
|
|
518
|
+
if block_match.group(1) == '</think>':
|
|
519
|
+
state.block_depth = 0
|
|
520
|
+
yield RESET
|
|
521
|
+
elif block_match.group(1) == '<think>':
|
|
522
|
+
state.block_depth = 1
|
|
523
|
+
else:
|
|
524
|
+
state.block_depth = block_match.group(0).count('>')
|
|
525
|
+
# we also need to consume those tokens
|
|
526
|
+
line = line[len(block_match.group(0)):]
|
|
527
|
+
else:
|
|
528
|
+
if state.block_depth > 0:
|
|
529
|
+
line = FGRESET + line
|
|
530
|
+
state.block_depth = 0
|
|
472
531
|
|
|
473
532
|
# --- Collapse Multiple Empty Lines if not in code blocks ---
|
|
474
533
|
if not state.in_code:
|
|
@@ -483,7 +542,7 @@ def parse(stream):
|
|
|
483
542
|
else:
|
|
484
543
|
last_line_empty_cache = state.last_line_empty
|
|
485
544
|
state.last_line_empty = False
|
|
486
|
-
|
|
545
|
+
|
|
487
546
|
# This is to reset our top-level line-based systems
|
|
488
547
|
# \n buffer
|
|
489
548
|
if not state.in_list and len(state.ordered_list_numbers) > 0:
|
|
@@ -506,28 +565,11 @@ def parse(stream):
|
|
|
506
565
|
if state.in_table and not state.in_code and not re.match(r"^\s*\|.+\|\s*$", line):
|
|
507
566
|
state.in_table = False
|
|
508
567
|
|
|
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
568
|
#
|
|
526
569
|
# <code><pre>
|
|
527
570
|
#
|
|
528
|
-
# This needs to be first
|
|
529
571
|
if not state.in_code:
|
|
530
|
-
code_match = re.match(r"\s*```\s*([^\s]+|$)", line)
|
|
572
|
+
code_match = re.match(r"\s*```\s*([^\s]+|$)$", line)
|
|
531
573
|
if code_match:
|
|
532
574
|
state.in_code = Code.Backtick
|
|
533
575
|
state.code_language = code_match.group(1) or 'Bash'
|
|
@@ -563,6 +605,7 @@ def parse(stream):
|
|
|
563
605
|
try:
|
|
564
606
|
ext = get_lexer_by_name(state.code_language).filenames[0].split('.')[-1]
|
|
565
607
|
except:
|
|
608
|
+
logging.warning(f"Can't find canonical extension for {state.code_language}")
|
|
566
609
|
pass
|
|
567
610
|
|
|
568
611
|
open(os.path.join(state.scrape, f"file_{state.scrape_ix}.{ext}"), 'w').write(state.code_buffer)
|
|
@@ -584,7 +627,8 @@ def parse(stream):
|
|
|
584
627
|
|
|
585
628
|
|
|
586
629
|
if code_type == Code.Backtick:
|
|
587
|
-
|
|
630
|
+
state.code_indent = len(line) - len(line.lstrip())
|
|
631
|
+
continue
|
|
588
632
|
else:
|
|
589
633
|
# otherwise we don't want to consume
|
|
590
634
|
# nor do we want to be here.
|
|
@@ -600,11 +644,6 @@ def parse(stream):
|
|
|
600
644
|
custom_style = get_style_by_name("default")
|
|
601
645
|
|
|
602
646
|
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
647
|
line = line[state.code_indent :]
|
|
609
648
|
|
|
610
649
|
elif line.startswith(" " * state.code_indent):
|
|
@@ -628,6 +667,10 @@ def parse(stream):
|
|
|
628
667
|
# then naively search back until our visible_lengths() match. This is not fast and there's certainly smarter
|
|
629
668
|
# ways of doing it but this thing is way trickery than you think
|
|
630
669
|
highlighted_code = highlight(state.code_buffer + tline, lexer, formatter)
|
|
670
|
+
|
|
671
|
+
# Sometimes the highlighter will do things like a full reset or a background reset.
|
|
672
|
+
# This is not what we want
|
|
673
|
+
highlighted_code = re.sub(r"\033\[39(;00|)m", '', highlighted_code)
|
|
631
674
|
|
|
632
675
|
# Since we are streaming we ignore the resets and newlines at the end
|
|
633
676
|
if highlighted_code.endswith(FGRESET + "\n"):
|
|
@@ -652,7 +695,7 @@ def parse(stream):
|
|
|
652
695
|
|
|
653
696
|
code_line = ' ' * indent + this_batch.strip()
|
|
654
697
|
|
|
655
|
-
margin = state.WidthFull - visible_length(code_line)
|
|
698
|
+
margin = state.WidthFull - visible_length(code_line) % state.WidthFull
|
|
656
699
|
yield f"{Style.Codebg}{code_line}{' ' * max(0, margin)}{BGRESET}"
|
|
657
700
|
continue
|
|
658
701
|
except Goto:
|
|
@@ -714,18 +757,18 @@ def parse(stream):
|
|
|
714
757
|
if list_type == "number":
|
|
715
758
|
state.ordered_list_numbers[-1] += 1
|
|
716
759
|
|
|
717
|
-
indent = len(state.list_item_stack) * 2
|
|
760
|
+
indent = (len(state.list_item_stack) - 1) * 2
|
|
718
761
|
|
|
719
762
|
wrap_width = state.Width - indent - (2 * Style.ListIndent)
|
|
720
763
|
|
|
721
764
|
bullet = '•'
|
|
722
765
|
if list_type == "number":
|
|
723
766
|
list_number = int(max(state.ordered_list_numbers[-1], float(list_item_match.group(2))))
|
|
724
|
-
bullet =
|
|
767
|
+
bullet = str(list_number)
|
|
725
768
|
|
|
726
769
|
wrapped_lineList = text_wrap(content, wrap_width, Style.ListIndent,
|
|
727
|
-
first_line_prefix = f"{(' ' * (indent
|
|
728
|
-
subsequent_line_prefix = " " * (indent
|
|
770
|
+
first_line_prefix = f"{(' ' * (indent ))}{FG}{Style.Symbol}{bullet}{RESET} ",
|
|
771
|
+
subsequent_line_prefix = " " * (indent)
|
|
729
772
|
)
|
|
730
773
|
for wrapped_line in wrapped_lineList:
|
|
731
774
|
yield f"{state.space_left()}{wrapped_line}\n"
|
|
@@ -742,7 +785,7 @@ def parse(stream):
|
|
|
742
785
|
#
|
|
743
786
|
# <hr>
|
|
744
787
|
#
|
|
745
|
-
hr_match = re.match(r"^[\s]*([
|
|
788
|
+
hr_match = re.match(r"^[\s]*([-\*=_]){3,}[\s]*$", line)
|
|
746
789
|
if hr_match:
|
|
747
790
|
if state.last_line_empty or last_line_empty_cache:
|
|
748
791
|
# print a horizontal rule using a unicode midline
|
|
@@ -764,12 +807,6 @@ def parse(stream):
|
|
|
764
807
|
for wrapped_line in wrapped_lines:
|
|
765
808
|
yield f"{state.space_left()}{wrapped_line}\n"
|
|
766
809
|
|
|
767
|
-
def get_terminal_width():
|
|
768
|
-
try:
|
|
769
|
-
return shutil.get_terminal_size().columns
|
|
770
|
-
except (AttributeError, OSError):
|
|
771
|
-
return 80
|
|
772
|
-
|
|
773
810
|
def emit(inp):
|
|
774
811
|
buffer = []
|
|
775
812
|
flush = False
|
|
@@ -795,6 +832,9 @@ def emit(inp):
|
|
|
795
832
|
state.current_line += chunk
|
|
796
833
|
|
|
797
834
|
buffer.append(chunk)
|
|
835
|
+
# This *might* be dangerous
|
|
836
|
+
state.reset_inline()
|
|
837
|
+
|
|
798
838
|
if flush:
|
|
799
839
|
chunk = "\n".join(buffer)
|
|
800
840
|
buffer = []
|
|
@@ -806,17 +846,10 @@ def emit(inp):
|
|
|
806
846
|
else:
|
|
807
847
|
chunk = buffer.pop(0)
|
|
808
848
|
|
|
809
|
-
|
|
810
|
-
print(chunk, end="", flush=True)
|
|
811
|
-
else:
|
|
812
|
-
sys.stdout.write(chunk)
|
|
849
|
+
print(chunk, end="", flush=True)
|
|
813
850
|
|
|
814
851
|
if len(buffer):
|
|
815
|
-
|
|
816
|
-
if state.is_pty:
|
|
817
|
-
print(chunk, end="", flush=True)
|
|
818
|
-
else:
|
|
819
|
-
sys.stdout.write(chunk)
|
|
852
|
+
print(buffer.pop(0), end="", flush=True)
|
|
820
853
|
|
|
821
854
|
def apply_multipliers(name, H, S, V):
|
|
822
855
|
m = _style.get(name)
|
|
@@ -824,7 +857,20 @@ def apply_multipliers(name, H, S, V):
|
|
|
824
857
|
return ';'.join([str(int(x * 256)) for x in [r, g, b]]) + "m"
|
|
825
858
|
|
|
826
859
|
def width_calc():
|
|
827
|
-
state.WidthFull
|
|
860
|
+
if not state.WidthFull or not state.WidthArg:
|
|
861
|
+
if state.WidthArg:
|
|
862
|
+
state.WidthFull = state.WidthArg
|
|
863
|
+
else:
|
|
864
|
+
width = 80
|
|
865
|
+
|
|
866
|
+
try:
|
|
867
|
+
width = shutil.get_terminal_size().columns
|
|
868
|
+
state.WidthWrap = True
|
|
869
|
+
except (AttributeError, OSError):
|
|
870
|
+
pass
|
|
871
|
+
|
|
872
|
+
state.WidthFull = width
|
|
873
|
+
|
|
828
874
|
state.Width = state.WidthFull - 2 * Style.Margin
|
|
829
875
|
Style.Codepad = [
|
|
830
876
|
f"{RESET}{FG}{Style.Dark}{'▄' * state.WidthFull}{RESET}\n",
|
|
@@ -838,9 +884,9 @@ def main():
|
|
|
838
884
|
parser.add_argument("filenameList", nargs="*", help="Input file to process (also takes stdin)")
|
|
839
885
|
parser.add_argument("-l", "--loglevel", default="INFO", help="Set the logging level")
|
|
840
886
|
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")
|
|
887
|
+
parser.add_argument("-w", "--width", default="0", help="Set the width WIDTH")
|
|
888
|
+
parser.add_argument("-e", "--exec", help="Wrap a program EXEC for more 'proper' i/o handling")
|
|
889
|
+
parser.add_argument("-s", "--scrape", help="Scrape code snippets to a directory SCRAPE")
|
|
844
890
|
args = parser.parse_args()
|
|
845
891
|
|
|
846
892
|
if args.color:
|
|
@@ -864,8 +910,7 @@ def main():
|
|
|
864
910
|
|
|
865
911
|
Style.Codebg = f"{BG}{Style.Dark}"
|
|
866
912
|
Style.Link = f"{FG}{Style.Symbol}{UNDERLINE[0]}"
|
|
867
|
-
Style.Blockquote = f"{FG}{Style.Grey}
|
|
868
|
-
|
|
913
|
+
Style.Blockquote = f"{FG}{Style.Grey}│ "
|
|
869
914
|
|
|
870
915
|
logging.basicConfig(stream=sys.stdout, level=args.loglevel.upper(), format=f'%(message)s')
|
|
871
916
|
state.exec_master, state.exec_slave = pty.openpty()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|