mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-29 13:32:08 +00:00
* init * fix tables * add shortcuts * handle svgs * test * test2 * test3 * Update loggers.py * Update sdk_helpers.py * Update loggers.py * Update loggers.py * updates * updates * update * handle add text high/low for candlestick traces when updating * adding save overlay to tables * maybe fix * prints * asdf * working * working * removing prints * update * fix no figure title text * add trace labels space * download finished popup for MacOS * bump pywry * update * update table * Update plotly_helper.py * only format numbers over 10_000 on tables * Delete generate_index.py * turn off logs in generations * update tables * bump pywry, fix tables, added ichimoku ta indicator * Update backend.py * mypy * update tables * updates * juan more * tables * test * bump pywry * bump pywry * Update loggers.py * test * fixing some formatting * add update button to indicators dashboard * fix oecd dates to be full dates * prepare charts/tables for pywry update * Update poetry.lock * Update finviz_view.py * tests * tests * tests * tests * testss? * tests * Create test_call_func[call_sec-nasdaq_view.sec_filings-other_args37-called_with37].yaml * testtss please * Update loggers.py * testss * `with open(newline="\n")` * tests * tests -__- * why * Revert "`with open(newline="\n")`" This reverts commit554e0f53f4. * Revert "why" This reverts commit89f1d9b823. * Revert "tests -__-" This reverts commita9c694ae96. * Revert "tests" This reverts commit5463249498. * whelp * . * rename `streamlit.py` -> `streamlit_run.py`, bump pywry/finviz/streamlit * Update backend.py * update * Revert "update file" --------- Co-authored-by: andrewkenreich <andrew.kenreich@gmail.com> Co-authored-by: James Maslek <jmaslek11@gmail.com>
385 lines
13 KiB
Python
385 lines
13 KiB
Python
import json
|
|
import os
|
|
import re
|
|
import shutil
|
|
import traceback
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from textwrap import shorten
|
|
from typing import Dict, List, TextIO, Union
|
|
|
|
from openbb_terminal.core.session.current_system import set_system_variable
|
|
from openbb_terminal.core.session.current_user import get_current_user
|
|
from openbb_terminal.rich_config import console
|
|
from website.controller_doc_classes import (
|
|
ControllerDoc,
|
|
LoadControllersDoc,
|
|
sub_names_full as subnames,
|
|
)
|
|
|
|
set_system_variable("TEST_MODE", True)
|
|
set_system_variable("LOG_COLLECT", False)
|
|
website_path = Path(__file__).parent.absolute()
|
|
|
|
reference_import = """
|
|
import ReferenceCard from "@site/src/components/General/ReferenceCard";
|
|
|
|
<ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 -ml-6">
|
|
"""
|
|
|
|
USER_PATH = (
|
|
f"{get_current_user().preferences.USER_DATA_DIRECTORY}",
|
|
"`USER_DATA_DIRECTORY`",
|
|
)
|
|
|
|
|
|
def existing_markdown_file_examples(
|
|
trailmap: str, cmd_dict: Dict[str, str]
|
|
) -> Dict[str, Union[str, List[str]]]:
|
|
"""Get existing markdown file examples
|
|
|
|
Parameters
|
|
----------
|
|
trailmap : str
|
|
The trailmap to the function
|
|
cmd_dict : Dict[str, str]
|
|
The dictionary of the command
|
|
|
|
Returns
|
|
-------
|
|
Dict[str, Union[str, List[str]]]
|
|
Updated dictionary of the command with examples and images keys
|
|
"""
|
|
trail = trailmap.split(".")
|
|
for sub in trail:
|
|
if sub in ["ba", "ta", "qa"]:
|
|
trail.remove(trailmap.split(".")[0])
|
|
|
|
examples_path = (
|
|
f"old_content/terminal/{'/'.join(trail)}/{cmd_dict['cmd_name']}/_index.md"
|
|
)
|
|
examples_dict: Dict[str, Union[str, List[str]]] = {}
|
|
|
|
if os.path.exists(website_path / examples_path):
|
|
with open(website_path / examples_path, encoding="utf-8") as f:
|
|
content = f.read()
|
|
|
|
examples: str = ""
|
|
if "Example:" in content:
|
|
example_split = content.split("Example:")[1].split("```")
|
|
if example_split and len(example_split) > 1:
|
|
examples = f"{example_split[1].strip()}"
|
|
|
|
examples_dict["example"] = examples
|
|
images = [
|
|
x for x in content.split("\n") if x.startswith("!") and "TODO" not in x
|
|
]
|
|
examples_dict["images"] = images
|
|
|
|
return examples_dict
|
|
|
|
|
|
# pylint: disable=isinstance-second-argument-not-valid-type
|
|
def process_cmd_parsers(ctrl: ControllerDoc) -> List[Dict[str, str]]:
|
|
"""Process command parsers from ControllerDoc"""
|
|
|
|
commands = []
|
|
for cmd, parser in ctrl.cmd_parsers.items():
|
|
actions = []
|
|
for action in parser._actions: # pylint: disable=protected-access
|
|
if action.dest == "help":
|
|
continue
|
|
|
|
if (default := action.default) is not None:
|
|
if isinstance(default, list):
|
|
default = ", ".join([str(x) for x in default])
|
|
|
|
elif isinstance(default, datetime):
|
|
if "start" in action.dest:
|
|
default = "datetime.now() - timedelta(days=365)"
|
|
elif "end" in action.dest or "date" in action.dest:
|
|
default = "datetime.now()"
|
|
|
|
if (choices := action.choices) is not None:
|
|
# We do this so that range(0, N) objects are not converted to a list
|
|
if isinstance(choices, (dict, type({}.keys()), list)):
|
|
listdict: list = []
|
|
for choice in choices:
|
|
# We check for nested dicts
|
|
if isinstance(choice, (dict, type({}.keys()))):
|
|
listdict.append([f"{k}" for k in choice])
|
|
|
|
choices = listdict or [f"{x}" for x in choices]
|
|
choices = ", ".join(choices) if len(choices) > 0 else "None"
|
|
|
|
if action.dest == "file":
|
|
new_desc = "File in `EXPORTS` or `CUSTOM_IMPORTS` directories"
|
|
|
|
if (file_ext := re.compile(r"\.(\w{3,5})").findall(choices)) != []: # type: ignore
|
|
exts = set(file_ext)
|
|
ext_examples = "file_name." + ", file_name.".join(exts)
|
|
new_desc += f" (e.g: `{ext_examples}`)"
|
|
action.choices = [f"`{ext_examples}`"]
|
|
|
|
choices = new_desc
|
|
|
|
if (doc := action.help) is not None:
|
|
# We do this to fix multiline docstrings for the markdown
|
|
doc = " ".join(doc.split()).replace(*USER_PATH)
|
|
|
|
if (flags := action.option_strings) is not None:
|
|
flags = " ".join(flags)
|
|
|
|
actions.append(
|
|
{
|
|
"opt_name": action.dest if action.dest else "",
|
|
"flags": flags if flags else "",
|
|
"doc": doc if doc else "",
|
|
"default": f"{default}".replace(*USER_PATH),
|
|
"optional": not action.required,
|
|
"choices": choices,
|
|
}
|
|
)
|
|
|
|
if (desc := parser.description) is not None:
|
|
# We do this to fix multiline docstrings for the markdown
|
|
desc = " ".join(desc.split()).replace(*USER_PATH)
|
|
|
|
param = {
|
|
"cmd_name": cmd.replace("call_", ""),
|
|
"actions": actions,
|
|
"usage": " ".join(parser.format_usage().split()).replace("usage: ", ""),
|
|
"description": desc if desc else "",
|
|
}
|
|
commands.append(param)
|
|
|
|
return commands # type: ignore
|
|
|
|
|
|
def generate_markdown(
|
|
cmd_meta: Dict[str, str], examples: Dict[str, Union[str, List[str]]]
|
|
) -> str:
|
|
"""Generate markdown section
|
|
|
|
Parameters
|
|
----------
|
|
cmd_meta : Dict[str, str]
|
|
Command metadata
|
|
examples : Dict[str, Union[str, List[str]]]
|
|
Command examples
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
Markdown string
|
|
"""
|
|
if not cmd_meta:
|
|
raise ValueError("No command metadata found")
|
|
|
|
markdown = f"""---
|
|
########### THIS FILE IS AUTO GENERATED - ANY CHANGES WILL BE VOID ###########
|
|
title: {cmd_meta["cmd_name"]}
|
|
description: OpenBB Terminal Function
|
|
---\n\n"""
|
|
|
|
# head meta https://docusaurus.io/docs/markdown-features/head-metadata
|
|
markdown += f"# {cmd_meta['cmd_name']}\n\n{cmd_meta['description']}\n\n"
|
|
markdown += f"### Usage\n\n```python wordwrap\n{cmd_meta['usage']}\n```\n\n"
|
|
|
|
markdown += "---\n\n## Parameters\n\n"
|
|
if cmd_meta["actions"]:
|
|
markdown += (
|
|
"| Name | Parameter | Description | Default | Optional | Choices |\n"
|
|
)
|
|
markdown += (
|
|
"| ---- | --------- | ----------- | ------- | -------- | ------- |\n"
|
|
)
|
|
|
|
for param in cmd_meta["actions"]:
|
|
if isinstance(param, dict):
|
|
markdown += (
|
|
f"| {param['opt_name']} | {param['flags']} | {param['doc']} "
|
|
f"| {param['default']} | {param['optional']} | {param['choices']} |\n"
|
|
)
|
|
else:
|
|
markdown += "This command has no parameters\n\n"
|
|
|
|
if examples.get("example", ""):
|
|
markdown += "\n\n---\n\n## Examples\n\n"
|
|
markdown += f"```python\n{examples['example']}\n```"
|
|
|
|
markdown += "\n"
|
|
|
|
if isinstance(examples.get("images", None), list):
|
|
for image in examples["images"]:
|
|
markdown += f"{image}\n\n"
|
|
|
|
markdown += "---\n"
|
|
return markdown.replace("<", "").replace(">", "")
|
|
|
|
|
|
def create_nested_menus_card(folder: Path, url: str) -> str:
|
|
sub_categories = [
|
|
sub.stem
|
|
for sub in folder.glob("**/**/*.md*")
|
|
if sub.is_file() and sub.stem != "index"
|
|
]
|
|
categories = shorten(", ".join(sub_categories), width=116, placeholder="...")
|
|
url = f"/terminal/reference/{url}/{folder.name}".replace("//", "/")
|
|
|
|
index_card = f"""<ReferenceCard
|
|
title="{folder.name}"
|
|
description="{categories}"
|
|
url="{url}"
|
|
/>\n"""
|
|
return index_card
|
|
|
|
|
|
def create_cmd_cards(cmd_text: List[Dict[str, str]]) -> str:
|
|
cmd_cards = ""
|
|
for cmd in cmd_text:
|
|
url = f"/terminal/reference/{cmd['url']}/{cmd['title']}".replace("//", "/")
|
|
description = shorten(cmd["description"], width=116, placeholder="...")
|
|
|
|
cmd_cards += f"""<ReferenceCard
|
|
title="{cmd["title"]}"
|
|
description="{description.replace('"', "'")}"
|
|
url="{url}"
|
|
command="true"
|
|
/>\n"""
|
|
return cmd_cards
|
|
|
|
|
|
def write_reference_index(
|
|
reference_cards: Dict[Path, List[Dict[str, str]]],
|
|
fname: str,
|
|
path: Path,
|
|
rel_path: Path,
|
|
f: TextIO,
|
|
) -> None:
|
|
"""Write to the corresponding index.mdx file for a given folder, with the
|
|
appropriate nested menus and command cards.
|
|
|
|
Parameters
|
|
----------
|
|
reference_cards : Dict[Path, List[Dict[str, str]]]
|
|
Dictionary of command cards to be written to the index.md file.
|
|
fname : str
|
|
Name of the index.md file.
|
|
path : Path
|
|
Path to the folder to be written.
|
|
rel_path : Path
|
|
Relative path to the folder to be written.
|
|
f : TextIO
|
|
File to write to.
|
|
"""
|
|
f.write(f"# {fname}\n{reference_import}")
|
|
for folder in path.glob("*"):
|
|
if folder.is_dir():
|
|
f.write(create_nested_menus_card(folder, "/".join(rel_path.parts)))
|
|
|
|
folder_cmd_cards: List[Dict[str, str]] = reference_cards.get(path, {}) # type: ignore
|
|
if folder_cmd_cards:
|
|
f.write(create_cmd_cards(folder_cmd_cards))
|
|
|
|
f.write("</ul>\n")
|
|
|
|
|
|
def main() -> bool:
|
|
"""Main function to generate markdown files"""
|
|
console.print(
|
|
"[bright_yellow]Loading Controllers... Please wait and ignore any errors, this is normal.[/bright_yellow]"
|
|
)
|
|
|
|
load_ctrls = LoadControllersDoc()
|
|
ctrls = load_ctrls.available_controllers()
|
|
wopen_kwargs = {"encoding": "utf-8", "newline": "\n"}
|
|
|
|
console.print(
|
|
"[bright_yellow]Generating markdown files... Don't ignore any errors now[/bright_yellow]"
|
|
)
|
|
content_path: Path = website_path / "content/terminal/reference"
|
|
reference_cards: Dict[Path, List[Dict[str, str]]] = {}
|
|
|
|
for file in content_path.glob("*"):
|
|
if file.is_file():
|
|
file.unlink()
|
|
else:
|
|
shutil.rmtree(file)
|
|
|
|
for ctrl_trailmap in ctrls:
|
|
try:
|
|
ctrl = load_ctrls.get_controller_doc(ctrl_trailmap)
|
|
ctrl_cmds = process_cmd_parsers(ctrl)
|
|
|
|
for cmd in ctrl_cmds:
|
|
examples = existing_markdown_file_examples(ctrl_trailmap, cmd)
|
|
markdown = generate_markdown(cmd, examples)
|
|
|
|
if cmd["cmd_name"] == "index":
|
|
cmd["cmd_name"] = "index_cmd"
|
|
|
|
trail = ctrl_trailmap.split(".")
|
|
|
|
filepath = content_path / f"{'/'.join(trail)}/{cmd['cmd_name']}.md"
|
|
reference_cards.setdefault(filepath.parent, []).append(
|
|
dict(
|
|
title=cmd["cmd_name"],
|
|
description=cmd["description"],
|
|
url="/".join(trail),
|
|
)
|
|
)
|
|
filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(filepath, "w", **wopen_kwargs) as f: # type: ignore
|
|
f.write(markdown)
|
|
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
console.print(
|
|
f"[red]Failed to generate markdown for {ctrl_trailmap}: {e}[/red]"
|
|
)
|
|
return False
|
|
|
|
# Sort reference_cards
|
|
reference_cards = dict(sorted(reference_cards.items(), key=lambda item: item[0]))
|
|
|
|
# Generate root "_category_.json" file
|
|
with open(content_path / "_category_.json", "w", **wopen_kwargs) as f: # type: ignore
|
|
f.write(json.dumps({"label": "Terminal Reference", "position": 4}, indent=2))
|
|
|
|
# Generate root "index.md" file
|
|
with open(content_path / "index.mdx", "w", **wopen_kwargs) as f: # type: ignore
|
|
fname = "OpenBB Terminal Features"
|
|
rel_path = content_path.relative_to(content_path)
|
|
write_reference_index(reference_cards, fname, content_path, rel_path, f)
|
|
|
|
def gen_category_json(fname: str, path: Path):
|
|
"""Generate category json"""
|
|
fname = subnames[fname.lower()] if fname.lower() in subnames else fname.title()
|
|
with open(path / "_category_.json", "w", **wopen_kwargs) as f: # type: ignore
|
|
f.write(json.dumps({"label": fname}, indent=2))
|
|
|
|
with open(path / "index.mdx", "w", **wopen_kwargs) as f: # type: ignore
|
|
rel_path = path.relative_to(content_path)
|
|
write_reference_index(reference_cards, fname, path, rel_path, f)
|
|
|
|
def gen_category_recursive(nested_path: Path):
|
|
"""Generate category json recursively"""
|
|
for folder in nested_path.iterdir():
|
|
if folder.is_dir():
|
|
gen_category_json(folder.name, folder)
|
|
gen_category_recursive(folder) # pylint: disable=cell-var-from-loop
|
|
|
|
# Generate modules/sub category json and index files
|
|
gen_category_recursive(content_path)
|
|
|
|
console.print(
|
|
"[green]Markdown files generated, check the website/content/terminal/reference/ folder[/green]"
|
|
)
|
|
|
|
return True
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|