Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

draft: rough prototype of generating interlinks from code #347

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions examples/autolinks/_quarto.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
project:
type: website

# tell quarto to read the generated sidebar
metadata-files:
- _sidebar.yml


quartodoc:
# the name used to import the package you want to create reference docs for
package: quartodoc

# write sidebar data to this file
sidebar: _sidebar.yml

sections:
- title: Some functions
desc: Functions to inspect docstrings.
contents:
# the functions being documented in the package.
# you can refer to anything: class methods, modules, etc..
- get_object
- preview
20 changes: 20 additions & 0 deletions examples/autolinks/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from shiny.express import render, ui, input
from quartodoc.interlinks_auto import CallVisitor

ui.input_text_area("raw_code", "Code"),


@render.code
def code():
res = CallVisitor().analyze(input.raw_code())
chars = [list(row) for row in input.raw_code().split("\n")]
print(chars)
for inter in res:
ii, jj = inter.lineno - 1, inter.col_offset
chars[ii][jj] = "<--" + chars[ii][jj]

ii, jj = inter.end_lineno - 1, inter.end_col_offset - 1
chars[ii][jj] = chars[ii][jj] + "-->"

final = "\n".join(["".join(row) for row in chars])
return final
13 changes: 13 additions & 0 deletions examples/autolinks/index.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: Autolink example
---

```python
from great_tables import GT, exibble

(
GT(exibble)
.tab_header("I am a header")
.tab_source_note("I am a source not")
)
```
11 changes: 11 additions & 0 deletions quartodoc/ast.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import ast
import warnings

from enum import Enum
Expand Down Expand Up @@ -278,6 +279,16 @@ def fields(el: object):
return None


@dispatch
def fields(el: ast.AST):
return el._fields


@dispatch
def fields(el: ast.Attribute | ast.Call | ast.Name):
return el._fields + ("col_offset", "end_col_offset")


class Formatter:
n_spaces = 3
icon_block = "█─"
Expand Down
98 changes: 98 additions & 0 deletions quartodoc/interlinks_auto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from __future__ import annotations

import jedi

import ast
from fastapi import FastAPI
from pydantic import BaseModel
import uvicorn

from typing_extensions import TypeAlias, Union

ASTItems: TypeAlias = Union[ast.Call, ast.Name, ast.Attribute]


class CallVisitor(ast.NodeVisitor):
results: list[ASTItems]

def __init__(self):
self.results = []

def visit_Call(self, node):
if isinstance(node.func, ast.Attribute):
self.results.append(node)
return self.visit(node.func.value)
elif isinstance(node.func, ast.Name):
self.results.append(node)

def visit_Name(self, node):
self.results.append(node)

def visit_Attribute(self, node):
self.results.append(node)

def reset(self):
self.results = []

return self

@classmethod
def analyze(cls, code) -> list[Interlink]:
visitor = cls()
visitor.visit(ast.parse(code))
script = jedi.Script(code)
all_links = [node_to_interlink_name(script, call) for call in visitor.results]
return [link for link in all_links if link is not None]


def narrow_node_start(node: ast.AST) -> tuple[int, int]:
if isinstance(node, ast.Attribute):
return (node.value.end_lineno, node.value.end_col_offset)

return node.lineno, node.col_offset


def node_to_interlink_name(script: jedi.Script, node: ast.AST) -> Interlink | None:
print(node.func.lineno, node.func.end_col_offset)
try:
func = node.func
name = script.goto(func.lineno, func.end_col_offset)[0]
full_name = name.full_name
lineno, col_offset = narrow_node_start(func)
return Interlink(
name=full_name,
lineno=lineno,
end_lineno=func.end_lineno,
col_offset=col_offset,
end_col_offset=func.end_col_offset,
)
except IndexError:
return None


class Code(BaseModel):
content: str


class Interlink(BaseModel):
name: str
lineno: int
end_lineno: int
col_offset: int
end_col_offset: int


app = FastAPI()


@app.post("/analyze")
async def analyze(code: Code):
res = CallVisitor.analyze(code.content)
return {"interlinks": res}


if __name__ == "__main__":
import sys

port = int(sys.argv[1])
uvicorn.run(app, port=port, host="0.0.0.0")
Loading