mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-08-04 18:18:14 +00:00
add basic documentation (#42)
* add basic documentation * remove leading slash
This commit is contained in:
parent
757a400a8a
commit
5c821d8591
12 changed files with 1435 additions and 71 deletions
55
docs/editor-setup/neovim.md
Normal file
55
docs/editor-setup/neovim.md
Normal file
|
@ -0,0 +1,55 @@
|
|||
# Neovim
|
||||
|
||||
Using <https://github.com/folke/lazy.nvim> and <https://github.com/neovim/nvim-lspconfig>:
|
||||
|
||||
```lua
|
||||
{
|
||||
"neovim/nvim-lspconfig",
|
||||
opts = {
|
||||
servers = {
|
||||
djls = {},
|
||||
},
|
||||
setup = {
|
||||
djls = function(_, opts)
|
||||
local configs = require("lspconfig.configs")
|
||||
local util = require("lspconfig.util")
|
||||
|
||||
if not configs.djls then
|
||||
configs.djls = {
|
||||
default_config = {
|
||||
cmd = { "djls", "serve" },
|
||||
filetypes = { "htmldjango" },
|
||||
root_dir = function(fname)
|
||||
local root = util.root_pattern("manage.py", "pyproject.toml")(fname)
|
||||
vim.notify("LSP root dir: " .. (root or "nil"))
|
||||
return root or vim.fn.getcwd()
|
||||
end,
|
||||
handlers = {
|
||||
["window/logMessage"] = function(_, params, _)
|
||||
local message_type = {
|
||||
[1] = vim.log.levels.ERROR,
|
||||
[2] = vim.log.levels.WARN,
|
||||
[3] = vim.log.levels.INFO,
|
||||
[4] = vim.log.levels.DEBUG,
|
||||
}
|
||||
vim.notify(params.message, message_type[params.type], {
|
||||
title = "djls",
|
||||
})
|
||||
end,
|
||||
},
|
||||
on_attach = function(client, bufnr)
|
||||
vim.notify("djls attached to buffer: " .. bufnr)
|
||||
end,
|
||||
},
|
||||
}
|
||||
end
|
||||
require("lspconfig").djls.setup({})
|
||||
end,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
This configuration is copied straight from my Neovim setup and includes a logging setup that sends LSP messages to Neovim's notification system. You can remove all the references to `vim.notify` if you don't care about this functionality.
|
168
docs/index.md
Normal file
168
docs/index.md
Normal file
|
@ -0,0 +1,168 @@
|
|||
---
|
||||
title: Home
|
||||
---
|
||||
|
||||
# django-language-server
|
||||
|
||||
A language server for the Django web framework.
|
||||
|
||||
!!! warning
|
||||
|
||||
This project is in early stages. All features are incomplete and missing.
|
||||
|
||||
## Features
|
||||
|
||||
**None.**
|
||||
|
||||
😅
|
||||
|
||||
However, the foundation has been laid:
|
||||
|
||||
- [x] Working server architecture
|
||||
- [x] Server implementing the Language Server Protocol written in Rust
|
||||
- [x] Python agent running as a persistent process within the Django project's virtualenv
|
||||
- [x] Server-agent communication via Protocol Buffers
|
||||
- [x] Custom template parser to support LSP features
|
||||
- [x] Basic HTML parsing, including style and script tags
|
||||
- [x] Django variables and filters
|
||||
- [ ] Django block template tags
|
||||
- Early work has been done on an extensible template tag parsing specification (TagSpecs)
|
||||
- [ ] Actual LSP features (coming soon!... hopefully)
|
||||
|
||||
## Requirements
|
||||
|
||||
An editor that supports the Language Server Protocol (LSP) is required.
|
||||
|
||||
The Django Language Server aims to supports all actively maintained versions of Python and Django. Currently this includes:
|
||||
|
||||
- Python 3.9, 3.10, 3.11, 3.12, 3.13
|
||||
- Django 4.2, 5.0, 5.1
|
||||
|
||||
See the [Versioning](#versioning) section for details on how this project's version indicates Django compatibility.
|
||||
|
||||
## Installation
|
||||
|
||||
The Django Language Server consists of two main components:
|
||||
|
||||
- **An LSP server**: Rust binary `djls`, distributed through the Python package `djls-server`
|
||||
- **A Python agent**: `djls-agent` package that runs in your Django project
|
||||
|
||||
Both will need to be available in your Django project in order to function.
|
||||
|
||||
The quickest way to get started is to install both the server and agent in your project's environment:
|
||||
|
||||
```bash
|
||||
uv add --dev 'djls[server]'
|
||||
uv sync
|
||||
|
||||
# or
|
||||
|
||||
pip install djls[server]
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
The server should be installed globally on your development machine. The quick-start method above will install the server in each project's environment and is only intended for trying things out. See the [Server](#server) section below for details.
|
||||
|
||||
### Server
|
||||
|
||||
You can install the pre-built binary package from PyPI, or build from source using cargo.
|
||||
|
||||
The server binary is published to PyPI as `djls-server` for easy installation via uv or pipx:
|
||||
|
||||
```bash
|
||||
uv tool install djls-server
|
||||
|
||||
# or
|
||||
|
||||
pipx install djls-server
|
||||
```
|
||||
|
||||
If you have a Rust toolchain available and prefer to build from source, you can install via cargo:
|
||||
|
||||
```bash
|
||||
cargo install --git https://github.com/joshuadavidthomas/django-language-server
|
||||
```
|
||||
|
||||
### Agent
|
||||
|
||||
The agent needs to be installed in your Django project's environment to provide project introspection.
|
||||
|
||||
The agent is published to PyPI as `djls-agent` and should be added to your project's development dependencies:
|
||||
|
||||
```bash
|
||||
uv add --dev djls-agent
|
||||
uv sync
|
||||
|
||||
# or
|
||||
|
||||
pip install djls-agent
|
||||
```
|
||||
|
||||
## Editor Setup
|
||||
|
||||
The Django Language Server works with any editor that supports the Language Server Protocol (LSP). We currently have setup instructions for:
|
||||
|
||||
- [Neovim](editor-setup/neovim.md)
|
||||
|
||||
Got it working in your editor? [Help us add setup instructions!](#testing-and-documenting-editor-setup)
|
||||
|
||||
## Versioning
|
||||
|
||||
This project adheres to DjangoVer. For a quick overview of what DjangoVer is, here's an excerpt from Django core developer James Bennett's [Introducing DjangoVer](https://www.b-list.org/weblog/2024/nov/18/djangover/) blog post:
|
||||
|
||||
> In DjangoVer, a Django-related package has a version number of the form `DJANGO_MAJOR.DJANGO_FEATURE.PACKAGE_VERSION`, where `DJANGO_MAJOR` and `DJANGO_FEATURE` indicate the most recent feature release series of Django supported by the package, and `PACKAGE_VERSION` begins at zero and increments by one with each release of the package supporting that feature release of Django.
|
||||
|
||||
In short, `v5.1.x` means the latest version of Django the Django Language Server would support is 5.1 — so, e.g., versions `v5.1.0`, `v5.1.1`, `v5.1.2`, etc. should all work with Django 5.1.
|
||||
|
||||
At this moment, all components of the Django Language Server (the `djls` binary, the `djls-agent` agent package on PyPI, and the `djls-binary` binary distribution package on PyPI) will share the same version number. When a new version is released, all packages are updated together regardless of which component triggered the release.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
While DjangoVer doesn't encode API stability in the version number, this project strives to follow Django's standard practice of "deprecate for two releases, then remove" policy for breaking changes. Given this is a language server, breaking changes should primarily affect:
|
||||
|
||||
- Configuration options (settings in editor config files)
|
||||
- CLI commands and arguments
|
||||
- LSP protocol extensions (custom commands/notifications)
|
||||
|
||||
The project will provide deprecation warnings where possible and document breaking changes clearly in release notes. For example, if a configuration option is renamed:
|
||||
|
||||
- **`v5.1.0`**: Old option works but logs deprecation warning
|
||||
- **`v5.1.1`**: Old option still works, continues to show warning
|
||||
- **`v5.1.2`**: Old option removed, only new option works
|
||||
|
||||
## Contributing
|
||||
|
||||
The project needs help in several areas:
|
||||
|
||||
### Testing and Documenting Editor Setup
|
||||
|
||||
The server has only been tested with Neovim. Documentation for setting up the language server in other editors is sorely needed, particularly VS Code. However, any editor that has [LSP client](https://langserver.org/#:~:text=for%20more%20information.-,LSP%20clients,opensesame%2Dextension%2Dlanguage_server,-Community%20Discussion%20Forums) support would be welcome.
|
||||
|
||||
If you get it working in your editor:
|
||||
|
||||
1. Create a new Markdown file in the `docs/editors/` directory (e.g., `docs/editors/vscode.md`)
|
||||
2. Include step-by-step setup instructions, any required configuration snippets, and tips for troubleshooting
|
||||
|
||||
### Feature Requests
|
||||
|
||||
The motivation behind writing the server has been to improve the experience of using Django templates. However, it doesn't need to be limited to just that part of Django. In particular, it's easy to imagine how a language server could improve the experience of using the ORM -- imagine diagnostics warning about potential N+1 queries right in your editor!
|
||||
|
||||
After getting the basic plumbing of the server and agent in place, it's personally been hard to think of an area of the framework that *wouldn't* benefit from at least some feature of a language server.
|
||||
|
||||
All feature requests should ideally start out as a discussion topic, to gather feedback and consensus.
|
||||
|
||||
### Development
|
||||
|
||||
The project consists of both Rust and Python components:
|
||||
|
||||
- Rust: LSP server, template parsing, and core functionality (`crates/`)
|
||||
- Python: Django project and environment introspection agent (`packages/`)
|
||||
|
||||
Code contributions are welcome from developers of all backgrounds. Rust expertise is especially valuable for the LSP server and core components.
|
||||
|
||||
Python and Django developers should not be deterred by the Rust codebase - Django expertise is just as valuable. The Rust components were built by [a simple country CRUD web developer](https://youtu.be/7ij_1SQqbVo?si=hwwPyBjmaOGnvPPI&t=53) learning Rust along the way.
|
||||
|
||||
## License
|
||||
|
||||
django-language-server is licensed under the MIT license. See the [`LICENSE`](https://github.com/joshuadavidthomas/django-language-server/blob/main/LICENSE) file for more information.
|
410
docs/processor.py
Normal file
410
docs/processor.py
Normal file
|
@ -0,0 +1,410 @@
|
|||
# /// script
|
||||
# dependencies = [
|
||||
# "rich>=13.9.4",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
"""
|
||||
README.md processor using functional callbacks for processing steps.
|
||||
Uses rich for beautiful logging and progress display.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
from difflib import Differ
|
||||
from functools import reduce
|
||||
from itertools import islice
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
from rich.console import Console
|
||||
from rich.logging import RichHandler
|
||||
from rich.panel import Panel
|
||||
from rich.progress import track
|
||||
|
||||
console = Console()
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(message)s",
|
||||
handlers=[RichHandler(rich_tracebacks=True, show_time=False)],
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ProcessingFunc = Callable[[str], str]
|
||||
|
||||
|
||||
def compose(*functions: ProcessingFunc) -> ProcessingFunc:
|
||||
"""Compose multiple processing functions into a single function."""
|
||||
return reduce(lambda f, g: lambda x: g(f(x)), functions)
|
||||
|
||||
|
||||
def read_file(path: Path) -> str | None:
|
||||
"""Read content from a file."""
|
||||
try:
|
||||
content = path.read_text(encoding="utf-8")
|
||||
console.print(f"[green]✓[/green] Read {len(content)} bytes from {path}")
|
||||
return content
|
||||
except FileNotFoundError:
|
||||
console.print(f"[red]✗[/red] Input file not found: {path}")
|
||||
return None
|
||||
except Exception as e:
|
||||
console.print(f"[red]✗[/red] Error reading input file: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def write_file(path: Path, content: str) -> bool:
|
||||
"""Write content to a file."""
|
||||
try:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content, encoding="utf-8")
|
||||
console.print(f"[green]✓[/green] Wrote {len(content)} bytes to {path}")
|
||||
return True
|
||||
except Exception as e:
|
||||
console.print(f"[red]✗[/red] Error writing output file: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def preview_changes(original: str, processed: str, context_lines: int = 2) -> None:
|
||||
"""Show a preview of the changes made."""
|
||||
console.print("\n[yellow]Preview of changes:[/yellow]")
|
||||
|
||||
# Basic statistics
|
||||
orig_lines = original.count("\n")
|
||||
proc_lines = processed.count("\n")
|
||||
diff_lines = proc_lines - orig_lines
|
||||
|
||||
stats_panel = Panel(
|
||||
f"Original lines: {orig_lines}\n"
|
||||
f"Processed lines: {proc_lines}\n"
|
||||
f"Difference: {diff_lines:+d} lines",
|
||||
title="Statistics",
|
||||
border_style="blue",
|
||||
)
|
||||
console.print(stats_panel)
|
||||
|
||||
# Create diff
|
||||
differ = Differ()
|
||||
diff = list(differ.compare(original.splitlines(), processed.splitlines()))
|
||||
|
||||
# Find changed line groups with context
|
||||
changes = []
|
||||
current_group = []
|
||||
in_change = False
|
||||
last_change_line = -1
|
||||
|
||||
for i, line in enumerate(diff):
|
||||
if line.startswith("? "): # Skip hint lines
|
||||
continue
|
||||
|
||||
is_change = line.startswith(("- ", "+ "))
|
||||
if is_change:
|
||||
if not in_change: # Start of a new change group
|
||||
start = max(0, i - context_lines)
|
||||
# If we're close to previous group, connect them
|
||||
if start <= last_change_line + context_lines:
|
||||
start = last_change_line + 1
|
||||
else:
|
||||
if current_group:
|
||||
changes.append(current_group)
|
||||
current_group = []
|
||||
# Add previous context
|
||||
current_group.extend(
|
||||
l for l in diff[start:i] if not l.startswith("? ")
|
||||
)
|
||||
current_group.append(line)
|
||||
in_change = True
|
||||
last_change_line = i
|
||||
else:
|
||||
if in_change:
|
||||
# Add following context
|
||||
following_context = list(
|
||||
islice(
|
||||
(l for l in diff[i:] if not l.startswith("? ")), context_lines
|
||||
)
|
||||
)
|
||||
if following_context: # Only extend if we have context to add
|
||||
current_group.extend(following_context)
|
||||
in_change = False
|
||||
|
||||
if current_group:
|
||||
changes.append(current_group)
|
||||
|
||||
# Format and display the changes
|
||||
formatted_output = []
|
||||
for i, group in enumerate(changes):
|
||||
if i > 0:
|
||||
formatted_output.append(
|
||||
"[bright_black]⋮ skipped unchanged content ⋮[/bright_black]"
|
||||
)
|
||||
|
||||
# Track the last line to avoid duplicates
|
||||
last_line = None
|
||||
|
||||
for line in group:
|
||||
# Skip if this line is the same as the last one
|
||||
if line == last_line:
|
||||
continue
|
||||
|
||||
if line.startswith(" "): # unchanged
|
||||
formatted_output.append(f"[white]{line[2:]}[/white]")
|
||||
elif line.startswith("- "): # removed
|
||||
formatted_output.append(f"[red]━ {line[2:]}[/red]")
|
||||
elif line.startswith("+ "): # added
|
||||
formatted_output.append(f"[green]+ {line[2:]}[/green]")
|
||||
|
||||
last_line = line
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
"\n".join(formatted_output),
|
||||
title="Changes with Context",
|
||||
border_style="yellow",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def process_readme(
|
||||
input: str = "README.md",
|
||||
output: str = "docs/index.md",
|
||||
processors: list[ProcessingFunc] | None = None,
|
||||
preview: bool = True,
|
||||
) -> bool:
|
||||
"""
|
||||
Process README.md with given processing functions.
|
||||
|
||||
Args:
|
||||
input_path: Path to the input README.md file
|
||||
output_path: Path where the processed file will be saved
|
||||
processors: List of processing functions to apply
|
||||
preview: Whether to show a preview of changes
|
||||
|
||||
Returns:
|
||||
bool: True if processing was successful, False otherwise
|
||||
"""
|
||||
with console.status("[bold green]Processing README...") as status:
|
||||
input_path = Path(input)
|
||||
output_path = Path(output)
|
||||
|
||||
content = read_file(input_path)
|
||||
if content is None:
|
||||
return False
|
||||
|
||||
original_content = content
|
||||
|
||||
try:
|
||||
for proc in track(processors, description="Applying processors"):
|
||||
status.update(f"[bold green]Running {proc.__name__}...")
|
||||
content = proc(content)
|
||||
|
||||
if preview:
|
||||
preview_changes(original_content, content)
|
||||
|
||||
return write_file(output_path, content)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error during processing:[/red] {e}")
|
||||
return False
|
||||
|
||||
|
||||
def add_frontmatter(
|
||||
metadata: dict[str, str | int | float | bool | list | None],
|
||||
) -> ProcessingFunc:
|
||||
"""
|
||||
Add or update frontmatter from a dictionary of metadata.
|
||||
|
||||
Args:
|
||||
metadata: Dictionary of metadata to add to frontmatter
|
||||
|
||||
Returns:
|
||||
A processor function that adds/updates frontmatter
|
||||
|
||||
Example:
|
||||
Input:
|
||||
# Title
|
||||
Content here
|
||||
|
||||
Output:
|
||||
---
|
||||
title: My Page
|
||||
weight: 10
|
||||
hide:
|
||||
- navigation
|
||||
---
|
||||
|
||||
# Title
|
||||
Content here
|
||||
"""
|
||||
|
||||
def processor(content: str) -> str:
|
||||
# Remove existing frontmatter if present
|
||||
content_without_frontmatter = re.sub(
|
||||
r"^---\n.*?\n---\n", "", content, flags=re.DOTALL
|
||||
)
|
||||
|
||||
# Build the new frontmatter
|
||||
frontmatter_lines = ["---"]
|
||||
|
||||
for key, value in metadata.items():
|
||||
if isinstance(value, (str, int, float, bool)) or value is None:
|
||||
frontmatter_lines.append(f"{key}: {value}")
|
||||
elif isinstance(value, list):
|
||||
frontmatter_lines.append(f"{key}:")
|
||||
for item in value:
|
||||
frontmatter_lines.append(f" - {item}")
|
||||
# Could add more types (dict, etc.) as needed
|
||||
|
||||
frontmatter_lines.append("---\n\n")
|
||||
|
||||
return "\n".join(frontmatter_lines) + content_without_frontmatter
|
||||
|
||||
processor.__name__ = "add_frontmatter"
|
||||
return processor
|
||||
|
||||
|
||||
def convert_admonitions(content: str) -> str:
|
||||
"""
|
||||
Convert GitHub-style admonitions to Material for MkDocs-style admonitions.
|
||||
|
||||
Args:
|
||||
content: The markdown content to process
|
||||
|
||||
Returns:
|
||||
Processed content with converted admonitions
|
||||
|
||||
Example:
|
||||
Input:
|
||||
> [!NOTE]
|
||||
> Content here
|
||||
> More content
|
||||
|
||||
Output:
|
||||
!!! note
|
||||
|
||||
Content here
|
||||
More content
|
||||
"""
|
||||
# Mapping from GitHub admonition types to Material for MkDocs types
|
||||
ADMONITION_MAP = {
|
||||
"NOTE": "note",
|
||||
"TIP": "tip",
|
||||
"IMPORTANT": "important",
|
||||
"WARNING": "warning",
|
||||
"CAUTION": "warning",
|
||||
"ALERT": "danger",
|
||||
"DANGER": "danger",
|
||||
"INFO": "info",
|
||||
"TODO": "todo",
|
||||
"HINT": "tip",
|
||||
}
|
||||
|
||||
def process_match(match: re.Match[str]) -> str:
|
||||
# Get admonition type and map it, defaulting to note if unknown
|
||||
admonition_type = ADMONITION_MAP.get(match.group(1).upper(), "note")
|
||||
content_lines = match.group(2).rstrip().split("\n")
|
||||
|
||||
# Remove the leading '> ' from each line
|
||||
cleaned_lines = [line.lstrip("> ") for line in content_lines]
|
||||
|
||||
# Indent the content (4 spaces)
|
||||
indented_content = "\n".join(
|
||||
f" {line}" if line.strip() else "" for line in cleaned_lines
|
||||
)
|
||||
|
||||
# Preserve the exact number of trailing newlines from the original match
|
||||
trailing_newlines = len(match.group(2)) - len(match.group(2).rstrip("\n"))
|
||||
|
||||
return f"!!! {admonition_type}\n\n{indented_content}" + "\n" * trailing_newlines
|
||||
|
||||
# Match GitHub-style admonitions
|
||||
pattern = r"(?m)^>\s*\[!(.*?)\]\s*\n((?:>.*(?:\n|$))+)"
|
||||
|
||||
return re.sub(pattern, process_match, content)
|
||||
|
||||
|
||||
def convert_repo_links(repo_url: str) -> ProcessingFunc:
|
||||
"""
|
||||
Convert relative repository links to absolute URLs.
|
||||
|
||||
Args:
|
||||
repo_url: The base repository URL (e.g., 'https://github.com/username/repo')
|
||||
|
||||
Returns:
|
||||
A processor function that converts relative links to absolute URLs
|
||||
|
||||
Example:
|
||||
Input:
|
||||
See the [`LICENSE`](LICENSE) file for more information.
|
||||
Check the [Neovim](/docs/editors/neovim.md) guide.
|
||||
|
||||
Output:
|
||||
See the [`LICENSE`](https://github.com/username/repo/blob/main/LICENSE) file for more information.
|
||||
Check the [Neovim](editors/neovim.md) guide.
|
||||
"""
|
||||
|
||||
def processor(content: str) -> str:
|
||||
def replace_link(match: re.Match[str]) -> str:
|
||||
text = match.group(1)
|
||||
path = match.group(2)
|
||||
|
||||
# Skip anchor links
|
||||
if path.startswith("#"):
|
||||
return match.group(0)
|
||||
|
||||
# Skip already absolute URLs
|
||||
if path.startswith(("http://", "https://")):
|
||||
return match.group(0)
|
||||
|
||||
# Handle docs directory links
|
||||
if path.startswith(("/docs/", "docs/")):
|
||||
# Remove /docs/ or docs/ prefix and .md extension
|
||||
clean_path = path.removeprefix("/docs/").removeprefix("docs/")
|
||||
return f"[{text}]({clean_path})"
|
||||
|
||||
# Handle root-relative paths
|
||||
if path.startswith("/"):
|
||||
path = path.removeprefix("/")
|
||||
|
||||
# Remove ./ if present
|
||||
path = path.removeprefix("./")
|
||||
|
||||
# Construct the full URL for repository files
|
||||
full_url = f"{repo_url.rstrip('/')}/blob/main/{path}"
|
||||
return f"[{text}]({full_url})"
|
||||
|
||||
# Match markdown links: [text](url)
|
||||
pattern = r"\[((?:[^][]|\[[^]]*\])*)\]\(([^)]+)\)"
|
||||
return re.sub(pattern, replace_link, content)
|
||||
|
||||
processor.__name__ = "convert_repo_links"
|
||||
return processor
|
||||
|
||||
|
||||
def main():
|
||||
"""Example usage of the readme processor."""
|
||||
console.print("[bold blue]README Processor[/bold blue]")
|
||||
|
||||
processors = [
|
||||
add_frontmatter({"title": "Home"}),
|
||||
convert_admonitions,
|
||||
convert_repo_links(
|
||||
"https://github.com/joshuadavidthomas/django-language-server"
|
||||
),
|
||||
]
|
||||
|
||||
success = process_readme(
|
||||
input="README.md",
|
||||
output="docs/index.md",
|
||||
processors=processors,
|
||||
preview=True,
|
||||
)
|
||||
|
||||
if success:
|
||||
console.print("\n[green]✨ Processing completed successfully![/green]")
|
||||
else:
|
||||
console.print("\n[red]Processing failed![/red]")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
44
docs/stylesheets/extra.css
Normal file
44
docs/stylesheets/extra.css
Normal file
|
@ -0,0 +1,44 @@
|
|||
:root {
|
||||
--magiclink-github-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34-.46-1.16-1.11-1.47-1.11-1.47-.91-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.87 1.52 2.34 1.07 2.91.83.09-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.92 0-1.11.38-2 1.03-2.71-.1-.25-.45-1.29.1-2.64 0 0 .84-.27 2.75 1.02.79-.22 1.65-.33 2.5-.33s1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02.55 1.35.2 2.39.1 2.64.65.71 1.03 1.6 1.03 2.71 0 3.82-2.34 4.66-4.57 4.91.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.md-typeset .magiclink-commit:not(.magiclink-ignore)::before,
|
||||
.md-typeset .magiclink-compare:not(.magiclink-ignore)::before,
|
||||
.md-typeset .magiclink-discussion:not(.magiclink-ignore)::before,
|
||||
.md-typeset .magiclink-issue:not(.magiclink-ignore)::before,
|
||||
.md-typeset .magiclink-pull:not(.magiclink-ignore)::before,
|
||||
.md-typeset .magiclink-repository:not(.magiclink-ignore)::before,
|
||||
.md-typeset a[href^="mailto:"]:not(.magiclink-ignore)::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
background-color: var(--md-typeset-a-color);
|
||||
background-size: 1.25em;
|
||||
transition: background-color 125ms;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-size: contain;
|
||||
mask-size: contain;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.md-typeset .magiclink-commit:not(.magiclink-ignore),
|
||||
.md-typeset .magiclink-compare:not(.magiclink-ignore),
|
||||
.md-typeset .magiclink-discussion:not(.magiclink-ignore),
|
||||
.md-typeset .magiclink-issue:not(.magiclink-ignore),
|
||||
.md-typeset .magiclink-pull:not(.magiclink-ignore),
|
||||
.md-typeset .magiclink-repository:not(.magiclink-ignore),
|
||||
.md-typeset a[href^="mailto:"]:not(.magiclink-ignore) {
|
||||
position: relative;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
.md-typeset
|
||||
.magiclink-repository.magiclink-github:not(.magiclink-ignore)::before {
|
||||
-webkit-mask-image: var(--magiclink-github-icon);
|
||||
mask-image: var(--magiclink-github-icon);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue