diff --git a/README.md b/README.md index 8d46ba7..41146cc 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,21 @@ Practically speaking, for each notebook a python file is dynamically created:
+It is supposed to contains imports / global variables.
*NB: Only one init is allowed and it must be the last line of the cell.* -* **jet** decl1 decl2 ...: save or update the declaration in the file. -Classes, function and decorators are supported. +* **jet** decl1 decl2 ...: save or update the declaration in the file.
+Classes, function and decorators are supported.
+Comments will be stripped.
The file content is updated everytime the magic runs. +* **jet_end**: save all the content of the current cell and place it at the bottom of the file.
+It is supposed to contain for example a ifmain block.
+*NB: Only one init is allowed and it must be the last line of the cell.* + Example ------------ @@ -37,7 +42,7 @@ Example import numpy as np pi = 3.14 -%jet_init +%jet_beg ``` *And save it as raw content* @@ -68,24 +73,26 @@ print circle_area(2.2)
-The coresponding generated file like this: +The coresponding generated file is: **my_super_notebook.py** ``` import numpy as np pi = 3.14 +# --- JET BEG --- # -# --- JET INIT END --- # +def circle_perim(): + return 2 * pi * r +def circle_area(): + return pi * r ** 2 -def circle_perim(r): - return 2 * pi * r + +# --- JET END --- # -def circle_area(r): - return pi * r ** 2 ``` So this python generated module can be called from other notebooks. @@ -99,6 +106,7 @@ Jupyjet is available on pip: ``` pip install jupyjet +pip3 install jupyjet ``` diff --git a/jupyjet.py b/jupyjet.py index a9dbe49..432c58a 100644 --- a/jupyjet.py +++ b/jupyjet.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- @@ -19,16 +19,13 @@ -_jet_beg_key = '__jet_beg' -_jet_end_key = '__jet_end' +_jet_beg = '\n# --- JET BEG --- #\n\n' +_jet_end = '\n# --- JET END --- #\n\n' -_jet_beg = '# --- JET BEG --- #' -_jet_end = '# --- JET END --- #' +_jet_split_re = re.compile(r'\n# --- JET (BEG|END) --- #\n\n') -_jet_split_re = re.compile('# --- JET (BEG|END) --- #') - -# PATH +# NOTEBOOK PATH def _find_file_path(): """ Find the local path to the current notebook. @@ -60,7 +57,7 @@ def _find_file_path(): return {'top_dir': os.getcwd(),'file_name': ntb_name} -def _build_file_path(): +def _build_py_fp(): """ Construct the path to the generated python file. """ @@ -69,7 +66,7 @@ def _build_file_path(): return mp['top_dir'] + '/' + mp['file_name'] + '.py' -def _build_notebook_path(): +def _build_ntb_fp(): """ Construct the path to the existing notebook file. """ @@ -78,96 +75,92 @@ def _build_notebook_path(): return mp['top_dir'] + '/' + mp['file_name'] + '.ipynb' -# FILE -def _load_file_ast(): +# JET CODE and JET FILE +def _load_jet_code(): """ - Build the ast of the current python file. + Load the python file into jet_code format. + + jet_code is a dict: + beg => content of begining (str) + body => declaration to save (OrderedDict) + symbol name => declaration + symbol name => declaration + beg => content of end (str) + + """ # the ast should be empty if the file does not exist - mfp = _build_file_path() - if not os.path.isfile(mfp): + py_fp = _build_py_fp() + if not os.path.isfile(py_fp): return {'beg': '', - 'symbols': [], + 'body': OrderedDict(), 'end': ''} # if it does, the ast is loaded - with open(mfp) as source_file: - # TODO: add end - # file_code = source_file.read().split(_jet_beg) - file_code = _jet_split_re.split(source_file.read())[::2] + with open(py_fp) as py_f: + file_code = _jet_split_re.split(py_f.read())[::2] - # assert len(file_code) == 3 + assert len(file_code) == 3, 'the jet file is not in the good format' - # if len(file_code) == 1: - # return {'beg': file_code[0]} + body_ast = ast.parse(file_code[1]).body + + body_symb = OrderedDict() + for node in body_ast: + if hasattr(node, 'name'): + body_symb[node.name] = node return {'beg': file_code[0], - 'symbols': ast.parse(file_code[1]).body, + 'body': body_symb, 'end': file_code[2]} -# symbols -def _extract_symbols(file_ast): +def _save_jet_file(jet_code): """ - Extract the symbols from the file ast. + Save the jet_code into a the jet file. """ - symbols = [(_jet_beg_key, file_ast['beg'] + _jet_beg)] - symbols += [(symb.name, codegen.to_source(symb) + '\n') for symb in file_ast['symbols']] - symbols += [(_jet_end_key, file_ast['end'] + _jet_end)] + # generate the code for the symbols + body_str = '\n'.join([codegen.to_source(symb)+'\n\n' for symb in jet_code['body'].values()]) - return OrderedDict(symbols) + # concat eveything + jet_code_str = jet_code['beg'] + \ + _jet_beg + \ + body_str + \ + _jet_end + \ + jet_code['end'] + # write into the file + py_fp = _build_py_fp() + with open(py_fp, 'w') as py_file: + py_file.write(jet_code_str) -def _save_symbols(symbols, file_path): +# CELL and SYMBOLS +def _find_last_decl(In, symb_name): """ - Save the symbols into the file. + Iter through execution flow backward to find the last existing delaration. """ - symbols_content = '\n\n'.join(symbols.values()) - - print(symbols_content) - - with open(file_path, 'w') as file_file: - file_file.write(symbols_content) - - -# DECLARATION symb -def _find_last_decl(In, symb_name): for cell in reversed(In): + # select only function and classes decl_ast = [decl for decl in ast.parse(cell).body if hasattr(decl, 'name')] - if len(decl_ast) > 0: - for decl in decl_ast: - if decl.name == symb_name: - return codegen.to_source(decl) + '\n' - - -def _update_decl(In, symbols, decl): - new_symbols = OrderedDict(symbols) - new_symbols[decl.__name__] = _find_last_decl(In, decl.__name__) - return new_symbols + # retrieve the last + for decl in reversed(decl_ast): + if decl.name == symb_name: + return decl + return None -# BEG symb -def _get_beg_symb(In): - raw_content = In[-1] - content = raw_content.split('\n') - content[-1] = _jet_beg - return (_jet_beg_key, '\n'.join(content)) - - -def _update_beg_symb(symbols_dict, beg_symb): - if _jet_beg_key in symbols_dict: - new_symbols = OrderedDict(symbols_dict) - new_symbols[_jet_beg_key] = beg_symb[1] - return new_symbols +def _fetch_current_cell(In): + body = In[-1] + if len(body.split('\n')) == 1: + return '' else: - return OrderedDict([beg_symb] + \ - [(k, v) for k, v in symbols_dict.iteritems()] + \ - [end_symb]) + # remove the last line + return body[:body.rfind('\n')] + # POINT OF ENTRY @@ -177,21 +170,38 @@ class JetMagics(Magics): @line_magic def jet_beg(self, arg_line): In = self.shell.user_ns['In'] - new_symbols = _update_beg_symb(_extract_symbols(_load_file_ast()), _get_beg_symb(In)) + jet_code = _load_jet_code() + + jet_code['beg'] = _fetch_current_cell(In) + + _save_jet_file(jet_code) + + + @line_magic + def jet_end(self, arg_line): + + In = self.shell.user_ns['In'] + jet_code = _load_jet_code() + + jet_code['end'] = _fetch_current_cell(In) + + _save_jet_file(jet_code) - _save_symbols(new_symbols, _build_file_path()) @line_magic def jet(self, arg_line): In = self.shell.user_ns['In'] - decl_list = [eval(func_name, self.shell.user_global_ns, self.shell.user_ns) for func_name in arg_line.split()] + update_list = [name for name in arg_line.split()] + + jet_code = _load_jet_code() - new_symbols = reduce(lambda cur_bloc, new_decl: _update_decl(In, cur_bloc, new_decl), - decl_list, - _extract_symbols(_load_file_ast())) + for name in update_list: + decl = _find_last_decl(In, name) + assert decl, 'the symbol "{}" is not defined'.format(name) - _save_symbols(new_symbols, _build_file_path()) + jet_code['body'][name] = decl + _save_jet_file(jet_code) def load_ipython_extension(ip): ip.register_magics(JetMagics) diff --git a/setup.py b/setup.py index 7478915..a9bcb3e 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ url='https://github.com/pelodelfuego/jupyjet', author='Clément CREPY', author_email='clement.crepy@gmail.com', - version='0.2.4', + version='0.3.0', py_modules=['jupyjet'], license='MIT', description='jupyter extention to generate python modules',