Last active
November 30, 2020 06:54
-
-
Save mondeja/084585268fec1c71515c69119b2d204f to your computer and use it in GitHub Desktop.
Python-to-Markdown magics line table generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# DataFrame example | |
df = pd.DataFrame(data={'col1': ["one", 2], 'col2': [3, "four"]}) | |
df = df.to_dict() # You need to pass as dict | |
%md table $df # <---- In Jupyter Notebook cell | |
#------------------------------------------------------------ | |
# Dict structure example | |
dstruct = dict(columns=["col1", "col2"], values=[["first", 2], [3, "four"]]) | |
# Can be called as markdown or md | |
%markdown table $dstruct -v -i |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from re import findall, search | |
from ast import literal_eval | |
from IPython.core.magic import Magics, magics_class, line_magic | |
from IPython.display import display_javascript | |
@magics_class | |
class MarkdownMagics(Magics): | |
""" | |
Markdown tables cell code generator from Python data structures. | |
Args: | |
replace (bool, optional): If True, the caller cell will be replaced | |
with the call command. You can pass this flag in individual | |
commands with ``-r``. As default, ``False``. | |
inplace (bool, optional): If True, the code generated will replace | |
the code in the cell generated, so use this flag with caution | |
as ``-i``. As default, ``False``. | |
verbose (bool, optional): If True, the command rendered will be showed | |
in the next cell of caller (if ``replace == True`` the caller | |
cell will be replaced instead. Pass this flag to individual commands | |
with ``-v``. As default, ``False``. | |
""" | |
def __init__(self, shell, replace=False, inplace=False, verbose=False): | |
super(MarkdownMagics, self).__init__(shell) | |
self.replace = replace # Replace cell on ouput | |
self.inplace = inplace # Inplace code on ouput cell (False will add code to the end of the line) | |
self.verbose = verbose # Create a new code cell with the call rendered | |
# Original arguments | |
self._replace = replace | |
self._inplace = inplace | |
self._verbose = verbose | |
def render_cell_in_markdown_format(self, content): | |
jscode = "var t_cell = IPython.notebook.get_selected_cell();" | |
if self.inplace: | |
jscode += "t_cell.set_text('{}');".format(content.replace('\n','\\n')) | |
else: | |
jscode += "t_cell.set_text(t_cell.get_text() + '\\n' + '{}');".format(content.replace('\n','\\n')) | |
jscode += """var t_index = IPython.notebook.get_cells().indexOf(t_cell); | |
IPython.notebook.to_markdown(t_index); | |
IPython.notebook.get_cell(t_index).render();""" | |
return display_javascript(jscode, raw=True) | |
def command_parser(self, line=""): | |
# Parse global flags | |
map_global_flags = {"-i": "inplace", "-r": "replace", "-v": "verbose"} | |
for flag, attr in map_global_flags.items(): | |
if " %s" % flag in line: | |
exec("self.%s = True" % attr) | |
line.replace(" %s" % flag, "") | |
else: | |
exec("self.%s = self._%s" % (attr, attr)) | |
# Get magics function name and optional arguments | |
if " " in line: # With optional arguments | |
_splitted = line.split(" ") | |
funcname = _splitted[0] | |
args = " ".join(_splitted[1:]) | |
else: # Without optional arguments | |
args = "" | |
funcname = line | |
return funcname, args | |
#-------------------------------------------------------- | |
# Markdown magic line functions | |
#-------------------------------------------------------- | |
@line_magic | |
def md(self, line): | |
return self.markdown(line, called_as_md=True) | |
@line_magic | |
def markdown(self, line, called_as_md=False): | |
if called_as_md: | |
return eval('self._%s("%s", called_as_md=True)' % self.command_parser(line)) | |
return eval('self._%s("%s")' % self.command_parser(line)) | |
def _table(self, line="", called_as_md=False): | |
""" | |
Generates a table in the next markdown cell. | |
Can be called with the commands: | |
- %markdown table | |
- %md table $data | |
- %md table center=True | |
Args: | |
rows (int, optional): NUmber of rows of the table. Only | |
works if ``data == None``. As default, ``3``. | |
cols (int, optional): NUmber of cols of the table. Only | |
works if ``data == None``. As default, ``3``. | |
data (dict, optional): Python variables that will be rendered in | |
a table if is possible. You can pass a ``pandas.DataFrame`` | |
(needs to be converted to dict with ``df.to_dict()`` method), | |
or a dictionary with the following fields and structure: | |
``dict(columns=["col1", "col2"], | |
values=[["first", 2], [3, "four"]])`` | |
As default, ``None`` (a void table will be created). | |
center (bool, optional): Header table separator will be build | |
with center marks like ``:----:``. As default, ``False``. | |
header (bool, optional): Select if you want to include a header. | |
As default, ``False``. | |
separator (bool, optional): Select if you want to include a | |
separator. As default, ``False``. | |
body (bool, optional): Select if you want to include table body. | |
as default, ``False``. | |
""" | |
args = dict(rows=3, cols=3, data=None, | |
center=False, header=True, | |
separator=True, body=True) | |
if line != "": | |
__args = line.split() | |
if __args[0].isnumeric(): | |
args["rows"] = int(__args[0]) | |
if __args[1].isnumeric(): | |
args["cols"] = int(__args[1]) | |
data = search(r'{.+}', line) | |
if data: | |
args["data"] = literal_eval(data.group(0)) | |
# Check if data is formatted as pd.DataFrame.to_dict() | |
# or {"colums": <list>, "values": <list of lists>} | |
PANDAS_DF = False | |
new_dict = {"columns": [], "values": []} | |
for key, value in args["data"].items(): | |
if isinstance(value, dict): # As pd.DataFrame | |
PANDAS_DF = True | |
elif isinstance(value, list): | |
break | |
if PANDAS_DF: | |
new_dict["columns"].append(key) | |
new_dict["values"].append(list(value.values())) | |
if PANDAS_DF: | |
args["data"] = new_dict | |
# Optional arguments parsing | |
kwargs = findall(r"\s*([a-z]+)=([TrueFals]+)\s*", line) | |
for arg, value in kwargs: | |
args[arg] = True if value == "True" else False | |
if args["data"]: | |
max_len = max([ | |
max([len(str(value)) for value in args["data"]["columns"]]), | |
max([len(str(value)) for row in args["data"]["values"] for value in row]) | |
]) | |
def _fill_celd(max_len, value): | |
space = max([0, max_len - len(str(value))]) | |
if space % 2 == 0: | |
spaces_left = spaces_right = int(space/2) | |
else: | |
spaces_left = int((space/2)+1) | |
spaces_right = int(space/2) | |
return " %s%s%s " % (" "*spaces_left, str(value), " "*spaces_right) | |
response = "" | |
if args["header"] or args["data"]: | |
args["cols"] -= 1 | |
header = "" | |
if args["data"]: | |
for i in args["data"]["columns"]: | |
header += "|%s" % _fill_celd(max_len, i) | |
else: | |
for _ in range(args["rows"]): | |
header += "| " | |
header += "|\n" | |
response += header | |
if args["separator"] or args["data"]: | |
args["cols"] -= 1 | |
separator = "" | |
_range = len(args["data"]["columns"]) if args["data"] else args["rows"] | |
for _ in range(_range): | |
separator += "|" | |
if args["center"]: | |
separator += ":-" | |
if args["data"]: | |
separator += "-"*(max_len+2) if not args["center"] else "-"*(max_len-2) | |
else: | |
if not args["centered"]: | |
separator += "----" | |
if args["center"]: | |
separator += "-:" | |
separator += "|\n" | |
response += separator | |
if args["body"] or args["data"]: | |
body = "" | |
if args["data"]: | |
for row in args["data"]["values"]: | |
_row = "" | |
for value in row: | |
_row += "|%s" % _fill_celd(max_len, value) | |
_row += "|\n" | |
body += _row | |
response += body | |
else: | |
for c in range(args["cols"]): | |
_row = "" | |
for r in range(args["rows"]): | |
_row += "| " | |
_row += "|\n" | |
body += _row | |
response += body | |
how_was_called = "markdown" if not called_as_md else "md" | |
if self.verbose or self.replace: | |
self.shell.set_next_input("%%%s table %s\n" % (how_was_called, line), replace=self.replace) | |
return self.render_cell_in_markdown_format(response) | |
ip = get_ipython() | |
magics = MarkdownMagics(ip) | |
ip.register_magics(magics) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment