diff --git a/.pdm-python b/.pdm-python deleted file mode 100644 index cf1cebf..0000000 --- a/.pdm-python +++ /dev/null @@ -1 +0,0 @@ -/home/runner/work/paste.py/paste.py/.venv/bin/python \ No newline at end of file diff --git a/src/paste/main.py b/src/paste/main.py index 18a2130..91270ec 100644 --- a/src/paste/main.py +++ b/src/paste/main.py @@ -36,7 +36,7 @@ from . import __version__, __author__, __contact__, __url__ from .schema import PasteCreate, PasteResponse, PasteDetails -description: str = "paste.py 🐍 - A pastebin written in python." +DESCRIPTION: str = "paste.py 🐍 - A pastebin written in python." limiter = Limiter(key_func=get_remote_address) app: FastAPI = FastAPI( @@ -54,16 +54,13 @@ app.state.limiter = limiter -def rate_limit_exceeded_handler(request: Request, exc: Exception) -> Union[Response, Awaitable[Response]]: +def rate_limit_exceeded_handler( + request: Request, exc: Exception +) -> Union[Response, Awaitable[Response]]: if isinstance(exc, RateLimitExceeded): - return Response( - content="Rate limit exceeded", - status_code=429 - ) - return Response( - content="An error occurred", - status_code=500 - ) + return Response(content="Rate limit exceeded", status_code=429) + return Response(content="An error occurred", status_code=500) + app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler) @@ -84,13 +81,14 @@ def rate_limit_exceeded_handler(request: Request, exc: Exception) -> Union[Respo BASE_DIR: Path = Path(__file__).resolve().parent -templates: Jinja2Templates = Jinja2Templates( - directory=str(Path(BASE_DIR, "templates"))) +templates: Jinja2Templates = Jinja2Templates(directory=str(Path(BASE_DIR, "templates"))) @app.post("/file") @limiter.limit("100/minute") -async def post_as_a_file(request: Request, file: UploadFile = File(...)) -> PlainTextResponse: +async def post_as_a_file( + request: Request, file: UploadFile = File(...) +) -> PlainTextResponse: try: uuid: str = generate_uuid() if uuid in large_uuid_storage: @@ -119,7 +117,9 @@ async def post_as_a_file(request: Request, file: UploadFile = File(...)) -> Plai @app.get("/paste/{uuid}") -async def get_paste_data(uuid: str, user_agent: Optional[str] = Header(None)) -> Response: +async def get_paste_data( + request: Request, uuid: str, user_agent: Optional[str] = Header(None) +) -> Response: if not "." in uuid: uuid = _find_without_extension(uuid) path: str = f"data/{uuid}" @@ -143,104 +143,26 @@ async def get_paste_data(uuid: str, user_agent: Optional[str] = Header(None)) -> try: lexer = get_lexer_by_name(file_extension, stripall=True) except ClassNotFound: - lexer = get_lexer_by_name( - "text", stripall=True) # Default lexer + lexer = get_lexer_by_name("text", stripall=True) # Default lexer + formatter = HtmlFormatter( - style="colorful", full=True, linenos="inline", cssclass="code") + style="monokai", # Dark theme base + linenos="inline", + cssclass="highlight", + nowrap=False, + ) + highlighted_code: str = highlight(content, lexer, formatter) - # print(highlighted_code) - custom_style = """ - .code pre span.linenos { - color: #999; - padding-right: 10px; - -webkit-user-select: none; - -webkit-touch-callout: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - } - - span { - font-size: 1.1em !important; - } - - pre { - line-height: 1.4 !important; - } - - .code pre span.linenos::after { - content: ""; - border-right: 1px solid #999; - height: 100%; - margin-left: 10px; - } - - .code { - background-color: #fff; - border: 1.5px solid #ddd; - border-radius: 5px; - margin-bottom: 20px; - overflow: auto; - } - - pre { - font-family: 'Consolas','Monaco','Andale Mono','Ubuntu Mono','monospace;' !important; - } - .copy-button { - position: fixed; - top: 10px; - right: 10px; - padding: 10px; - background-color: #4CAF50; - color: #fff; - cursor: pointer; - border: none; - border-radius: 5px; - outline: none; - } - """ - custom_script = """ - function copyAllText() { - // Create a range object to select the entire document - const range = document.createRange(); - range.selectNode(document.body); - - // Create a selection object and add the range to it - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); - - // Copy the selected text to the clipboard - document.execCommand('copy'); - - // Clear the selection to avoid interfering with the user's selection - selection.removeAllRanges(); - - // You can customize the copied message - alert('All text copied to clipboard!'); - } - - """ - response_content: str = f""" - - - {uuid} | paste.py 🐍 - - - - - -
- -
- {highlighted_code} - - - - """ - return HTMLResponse(content=response_content) + + return templates.TemplateResponse( + "paste.html", + { + "request": request, + "uuid": uuid, + "highlighted_code": highlighted_code, + "pygments_css": formatter.get_style_defs(".highlight"), + }, + ) except Exception: raise HTTPException( detail="404: The Requested Resource is not found", @@ -261,11 +183,13 @@ async def delete_paste(uuid: str) -> PlainTextResponse: os.remove(path) return PlainTextResponse(f"File successfully deleted {uuid}") except FileNotFoundError: - raise HTTPException(detail="File Not Found", - status_code=status.HTTP_404_NOT_FOUND) + raise HTTPException( + detail="File Not Found", status_code=status.HTTP_404_NOT_FOUND + ) except Exception as e: raise HTTPException( - detail=f"The exception is {e}", status_code=status.HTTP_409_CONFLICT) + detail=f"The exception is {e}", status_code=status.HTTP_409_CONFLICT + ) @app.get("/web", response_class=HTMLResponse) @@ -276,8 +200,9 @@ async def web(request: Request) -> Response: @app.post("/web", response_class=PlainTextResponse) @limiter.limit("100/minute") -async def web_post(request: Request, content: str = Form(...), - extension: Optional[str] = Form(None)) -> RedirectResponse: +async def web_post( + request: Request, content: str = Form(...), extension: Optional[str] = Form(None) +) -> RedirectResponse: try: file_content: bytes = content.encode() uuid: str = generate_uuid() @@ -297,7 +222,9 @@ async def web_post(request: Request, content: str = Form(...), status_code=status.HTTP_403_FORBIDDEN, ) - return RedirectResponse(f"{BASE_URL}/paste/{uuid_}", status_code=status.HTTP_303_SEE_OTHER) + return RedirectResponse( + f"{BASE_URL}/paste/{uuid_}", status_code=status.HTTP_303_SEE_OTHER + ) @app.get("/health", status_code=status.HTTP_200_OK) @@ -322,6 +249,7 @@ async def get_languages() -> JSONResponse: status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, ) + # apis to create and get a paste which returns uuid and url (to be used by SDK) @app.post("/api/paste", response_model=PasteResponse) async def create_paste(paste: PasteCreate) -> JSONResponse: @@ -329,21 +257,20 @@ async def create_paste(paste: PasteCreate) -> JSONResponse: uuid: str = generate_uuid() if uuid in large_uuid_storage: uuid = generate_uuid() - + uuid_with_extension: str = f"{uuid}.{paste.extension}" path: str = f"data/{uuid_with_extension}" - + with open(path, "w", encoding="utf-8") as f: f.write(paste.content) - + large_uuid_storage.append(uuid_with_extension) - + return JSONResponse( content=PasteResponse( - uuid=uuid_with_extension, - url=f"{BASE_URL}/paste/{uuid_with_extension}" + uuid=uuid_with_extension, url=f"{BASE_URL}/paste/{uuid_with_extension}" ).dict(), - status_code=status.HTTP_201_CREATED + status_code=status.HTTP_201_CREATED, ) except Exception as e: raise HTTPException( @@ -351,25 +278,24 @@ async def create_paste(paste: PasteCreate) -> JSONResponse: status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, ) + @app.get("/api/paste/{uuid}", response_model=PasteDetails) async def get_paste_details(uuid: str) -> JSONResponse: if not "." in uuid: uuid = _find_without_extension(uuid) path: str = f"data/{uuid}" - + try: with open(path, "r", encoding="utf-8") as f: content: str = f.read() - + extension: str = Path(path).suffix[1:] - + return JSONResponse( content=PasteDetails( - uuid=uuid, - content=content, - extension=extension + uuid=uuid, content=content, extension=extension ).dict(), - status_code=status.HTTP_200_OK + status_code=status.HTTP_200_OK, ) except FileNotFoundError: raise HTTPException( @@ -381,4 +307,3 @@ async def get_paste_details(uuid: str) -> JSONResponse: detail=f"Error retrieving paste: {str(e)}", status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, ) - diff --git a/src/paste/schema.py b/src/paste/schema.py index e5848cc..6706fa9 100644 --- a/src/paste/schema.py +++ b/src/paste/schema.py @@ -1,17 +1,21 @@ from typing import Optional from pydantic import BaseModel + class Data(BaseModel): input_data: str + class PasteCreate(BaseModel): content: str extension: Optional[str] = None + class PasteResponse(BaseModel): uuid: str url: str + class PasteDetails(BaseModel): uuid: str content: str diff --git a/src/paste/templates/base.html b/src/paste/templates/base.html new file mode 100644 index 0000000..0c42d7f --- /dev/null +++ b/src/paste/templates/base.html @@ -0,0 +1,30 @@ + + + + {% block title %} {% endblock %} + {% block headlinks %} {% endblock %} + + + + + + + + + + + + {% block content %} + {% endblock %} + + + diff --git a/src/paste/templates/index.html b/src/paste/templates/index.html index d6412d2..ef54cc9 100644 --- a/src/paste/templates/index.html +++ b/src/paste/templates/index.html @@ -1,120 +1,399 @@ - - - - paste.py 🐍 - - - - - - - - - - - -
-  

ABOUT

+ } +{% endblock %} + +{% block content %} + +
+
+
+
+
+
+
+
paste.py 🐍
+
+
+
+

ABOUT

+

A simple pastebin powered by FastAPI.

+

paste.py 🐍 is Fully Free and Open-Source Source Code

+
    +
  • Simple API
  • +
  • CLI
  • +
  • Web form
  • +
+ +

> Web Form: https://paste.fosscu.org/web

+ +

API USAGE

+

> POST: https://paste.fosscu.org/paste

+

Send the raw data along. Will respond with a link to the paste.

+ +
    +
  • 201 (CREATED): entire paste uploaded
  • +
  • 206 (PARTIAL): exceeded server limit
  • +
  • Other codes: error
  • +
-

A simple pastebin powered by FastAPI.

-

paste is Fully Free and Open-Source Source Code.

-
    -
  • Simple API
  • -
  • CLI
  • -
  • Web form
  • -
+

Pasting is heavily rate limited.

- Web Form: https://paste.fosscu.org/web +

> GET: https://paste.fosscu.org/paste/<id>

+

Retrieve the paste with the given id as plain-text.

-

API USAGE

+

> DELETE: https://paste.fosscu.org/paste/<id>

+

Delete the paste with the given id.

-

POST: https://paste.fosscu.org/paste

-

Send the raw data along. Will respond with a link to the paste.

+

EXAMPLES

+

> cURL: Paste a file named 'file.txt'

+
curl -X POST -F "file=@file.txt" https://paste.fosscu.org/file
-
    -
  • 201 (CREATED): entire paste uploaded
  • -
  • 206 (PARTIAL): exceeded server limit
  • -
  • Other codes: error
  • -
+

> cURL: Paste from stdin

+
echo "Hello, world." | curl -X POST -F "file=@-" https://paste.fosscu.org/file
-

Pasting is heavily rate limited.

+

> cURL: Delete an existing paste

+
curl -X DELETE https://paste.fosscu.org/paste/<id>
-

GET: https://paste.fosscu.org/paste/<id>

-

Retrieve the paste with the given id as plain-text.

+

> Shell function:

+
function paste() {
+    local file=${1:-/dev/stdin}
+    curl -X POST -F "file=@${file}" https://paste.fosscu.org/file
+}
-

DELETE: https://paste.fosscu.org/paste/<id>

-

Delete the paste with the given id.

+

A shell function that can be added to .bashrc or .bash_profile or .zshrc for + quick pasting from the command line. The command takes a filename or reads + from stdin if none was supplied and outputs the URL of the paste to stdout:

-

EXAMPLES

+
paste file.txt
+
+
+{% endblock %} -

cURL: Paste a file named 'file.txt'

+{% block script %} +document.addEventListener('DOMContentLoaded', function() { + // Matrix rain effect + const canvas = document.getElementById('matrix-bg'); + const ctx = canvas.getContext('2d'); -
curl -X POST -F "file=@file.txt" https://paste.fosscu.org/file
+ canvas.width = window.innerWidth; + canvas.height = window.innerHeight; -

cURL: Paste from stdin

+ const characters = "ヲアウエオカキケコサシスセソタツテナニヌネハヒホマミムメモヤユラリワ0123456789".split(""); + const fontSize = 16; + const columns = canvas.width / fontSize; + const drops = []; -
echo "Hello, world." | curl -X POST -F "file=@-" https://paste.fosscu.org/file
+ for (let i = 0; i < columns; i++) { + drops[i] = 1; + } -

cURL: Delete an existing paste

+ function draw() { + ctx.fillStyle = "rgba(0, 0, 0, 0.05)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); -
curl -X DELETE https://paste.fosscu.org/paste/<id>
+ ctx.fillStyle = "#0F0"; + ctx.font = fontSize + "px monospace"; -

Shell function: + for (let i = 0; i < drops.length; i++) { + const text = characters[Math.floor(Math.random() * characters.length)]; + ctx.fillText(text, i * fontSize, drops[i] * fontSize); -

function paste() {
-      local file=${1:-/dev/stdin}
-      curl -X POST -F "file=@${file}" https://paste.fosscu.org/file
-}
+ if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) { + drops[i] = 0; + } - A shell function that can be added to .bashrc or .bash_profle or .zshrc for - quick pasting from the command line. The command takes a filename or reads - from stdin if none was supplied and outputs the URL of the paste to - stdout:
paste file.txt
+ drops[i]++; + } + } + + window.addEventListener('resize', () => { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + }); + + setInterval(draw, 35); + + // Terminal boot sequence effect + const container = document.querySelector('.terminal-container'); + container.style.opacity = '0'; + + setTimeout(() => { + container.style.transition = 'opacity 0.5s'; + container.style.opacity = '1'; + }, 300); + + // Add typing effect to the first paragraph + const firstPara = document.querySelector('.terminal-content p'); + const originalText = firstPara.innerHTML; + firstPara.innerHTML = ''; + let i = 0; + + function typeWriter() { + if (i < originalText.length) { + firstPara.innerHTML += originalText.charAt(i); + i++; + setTimeout(typeWriter, 50); + } + } - -
- - + setTimeout(typeWriter, 800); +}); +{% endblock %} \ No newline at end of file diff --git a/src/paste/templates/paste.html b/src/paste/templates/paste.html new file mode 100644 index 0000000..b6c0187 --- /dev/null +++ b/src/paste/templates/paste.html @@ -0,0 +1,393 @@ +{% extends 'base.html' %} + +{% block title %} {{ uuid }} | paste.py 🐍 {% endblock %} + +{% block headlinks %} + +{% endblock %} + +{% block style %} + +@import url('https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/fira_code.min.css'); +@import url('https://fonts.cdnfonts.com/css/vt323'); + +:root { + --terminal-green: #00ff00; + --terminal-dark: #0c0c0c; + --terminal-shadow: rgba(0, 255, 0, 0.2); + --terminal-font: 'VT323', 'Fira Code', monospace; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + background-color: var(--terminal-dark); + margin: 0; + padding: 20px; + font-family: var(--terminal-font); + line-height: 1.6; + font-size: 20px; + color: var(--terminal-green); + position: relative; + overflow-x: hidden; +} + +/* Matrix Rain Effect */ +#matrix-bg { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: -1; + opacity: 0.15; +} + +.container { + max-width: 90%; + margin: 20px auto; + background-color: rgba(12, 12, 12, 0.95); + border: 1px solid var(--terminal-green); + border-radius: 0; + box-shadow: 0 0 20px var(--terminal-shadow); + position: relative; + backdrop-filter: blur(5px); +} + +/* Terminal Window Header */ +.terminal-header { + background: var(--terminal-green); + color: var(--terminal-dark); + padding: 12px; + font-size: 22px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.terminal-header .controls { + display: flex; + gap: 8px; +} + +.terminal-header .control { + width: 14px; + height: 14px; + border-radius: 50%; + border: 1px solid var(--terminal-dark); +} + +.terminal-header .control.close { + background: #ff5f56; +} + +.terminal-header .control.minimize { + background: #ffbd2e; +} + +.terminal-header .control.maximize { + background: #27c93f; +} + +.glitch-text { + position: relative; + display: inline-block; +} + +.glitch-text::before, +.glitch-text::after { + content: attr(data-text); + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.glitch-text::before { + left: 2px; + text-shadow: -2px 0 #ff00ff; + animation: glitch-1 2s infinite linear alternate-reverse; +} + +.glitch-text::after { + left: -2px; + text-shadow: 2px 0 #00ffff; + animation: glitch-2 2s infinite linear alternate-reverse; +} + +/* Code Display Styles */ +.code { + background-color: rgba(0, 0, 0, 0.7); + border: 1px solid var(--terminal-green); + margin: 20px; + overflow: auto; + position: relative; +} + +.code pre { + padding: 20px; + font-family: 'Fira Code', monospace !important; + font-size: 18px !important; + line-height: 1.5 !important; +} + +.code pre span.linenos { + color: rgba(0, 255, 0, 0.6); + padding-right: 20px; + user-select: none; + border-right: 1px solid rgba(0, 255, 0, 0.3); + margin-right: 20px; +} + +/* Syntax Highlighting Override */ +.highlight, +.highlight pre, +.highlight span { + background-color: transparent !important; + color: var(--terminal-green) !important; +} + +.highlight .k, +.highlight .kd { + color: #ff79c6 !important; +} + +.highlight .s, +.highlight .s1, +.highlight .s2 { + color: #f1fa8c !important; +} + +.highlight .nb, +.highlight .bp { + color: #8be9fd !important; +} + +.highlight .c, +.highlight .c1 { + color: #6272a4 !important; +} + +.highlight .o { + color: #ff79c6 !important; +} + +.highlight .n { + color: #f8f8f2 !important; +} + +.highlight .mi { + color: #bd93f9 !important; +} + +/* Copy Button */ +.copy-button { + position: fixed; + top: 90px; + right: 7%; + padding: 12px 20px; + background-color: rgba(0, 255, 0, 0.2); + color: var(--terminal-green); + cursor: pointer; + border: 1px solid var(--terminal-green); + font-family: var(--terminal-font); + font-size: 18px; + outline: none; + transition: all 0.3s ease; + z-index: 100; + display: flex; + align-items: center; + gap: 8px; +} + +.copy-button:hover { + background-color: var(--terminal-green); + color: var(--terminal-dark); + box-shadow: 0 0 15px var(--terminal-shadow); +} + +/* Animation Keyframes */ +@keyframes glitch-1 { + 0% { + clip-path: inset(20% 0 30% 0); + } + + 20% { + clip-path: inset(65% 0 1% 0); + } + + 40% { + clip-path: inset(43% 0 1% 0); + } + + 60% { + clip-path: inset(25% 0 58% 0); + } + + 80% { + clip-path: inset(75% 0 5% 0); + } + + 100% { + clip-path: inset(10% 0 85% 0); + } +} + +@keyframes glitch-2 { + 0% { + clip-path: inset(25% 0 58% 0); + } + + 20% { + clip-path: inset(75% 0 5% 0); + } + + 40% { + clip-path: inset(10% 0 85% 0); + } + + 60% { + clip-path: inset(20% 0 30% 0); + } + + 80% { + clip-path: inset(65% 0 1% 0); + } + + 100% { + clip-path: inset(43% 0 1% 0); + } +} + +@media only screen and (max-width: 768px) { + body { + padding: 10px; + font-size: 16px; + } + + .container { + max-width: 95%; + margin: 10px auto; + } + + .code pre { + font-size: 16px !important; + padding: 15px; + } + + .copy-button { + top: 80px; + right: 5%; + padding: 10px 15px; + font-size: 16px; + } +} + +{{ pygments_css }} + +{% endblock %} + +{% block content %} + + +
+
+
+
+
+
+
+
paste.py 🐍
+
+
+ +
+ {{ highlighted_code | safe }} +
+
+ +{% endblock %} + +{% block script %} + +document.addEventListener('DOMContentLoaded', function () { + const canvas = document.getElementById('matrix-bg'); + const ctx = canvas.getContext('2d'); + + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + + const characters = "ヲアウエオカキケコサシスセソタツテナニヌネハヒホマミムメモヤユラリワ0123456789".split(""); + const fontSize = 16; + const columns = canvas.width / fontSize; + const drops = []; + + for (let i = 0; i < columns; i++) { + drops[i] = 1; + } + + function draw() { + ctx.fillStyle = "rgba(0, 0, 0, 0.05)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + ctx.fillStyle = "#0F0"; + ctx.font = fontSize + "px monospace"; + + for (let i = 0; i < drops.length; i++) { + const text = characters[Math.floor(Math.random() * characters.length)]; + ctx.fillText(text, i * fontSize, drops[i] * fontSize); + + if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) { + drops[i] = 0; + } + + drops[i]++; + } + } + + window.addEventListener('resize', () => { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + }); + + setInterval(draw, 35); + + // Add boot sequence effect + const container = document.querySelector('.container'); + container.style.opacity = '0'; + + setTimeout(() => { + container.style.transition = 'opacity 0.5s'; + container.style.opacity = '1'; + }, 300); + }); + + function copyAllText() { + const codeElement = document.querySelector('.code pre'); + const range = document.createRange(); + range.selectNode(codeElement); + + const selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + + document.execCommand('copy'); + selection.removeAllRanges(); + + const copyButton = document.getElementById('copyButton'); + const originalText = copyButton.innerHTML; + copyButton.innerHTML = ' COPIED!'; + + setTimeout(() => { + copyButton.innerHTML = originalText; + }, 2000); + } + +{% endblock %} \ No newline at end of file diff --git a/src/paste/templates/web.html b/src/paste/templates/web.html index c760036..4c62b44 100644 --- a/src/paste/templates/web.html +++ b/src/paste/templates/web.html @@ -1,173 +1,430 @@ - - - - paste.py 🐍 - - - - - - - - - - - + + textarea { + height: 200px; + font-size: 16px; + } + + select, .terminal-prompt { + font-size: 16px; + } + + input[type="submit"] { + font-size: 18px; + padding: 12px; + } + } +{% endblock %} + +{% block content %} +
-
- - -
- -
- -
+
+
+
+
+
+
+
paste.py 🐍
+
+
+
+
$ SELECT LANGUAGE:
+ + +
$ EXTENSION:
+ +
+
$ ENTER CODE:
+ +
+ +
- - - + fetchLanguages(); +}); +{% endblock %} \ No newline at end of file diff --git a/src/paste/utils.py b/src/paste/utils.py index ad465b4..f38e01a 100644 --- a/src/paste/utils.py +++ b/src/paste/utils.py @@ -23,12 +23,13 @@ def extract_extension(file_name: Path) -> str: def _find_without_extension(file_name: str) -> str: file_list: list = os.listdir("data") - pattern_with_dot: Pattern[str] = re.compile( - r"^(" + re.escape(file_name) + r")\.") - pattern_without_dot: Pattern[str] = re.compile( - r"^" + file_name + "$") + pattern_with_dot: Pattern[str] = re.compile(r"^(" + re.escape(file_name) + r")\.") + pattern_without_dot: Pattern[str] = re.compile(r"^" + file_name + "$") math_pattern: list = [ - x for x in file_list if pattern_with_dot.match(x) or pattern_without_dot.match(x)] + x + for x in file_list + if pattern_with_dot.match(x) or pattern_without_dot.match(x) + ] if len(math_pattern) == 0: return str() else: