Skip to content

Commit

Permalink
improve workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
pelodelfuego committed Jan 25, 2018
1 parent eeeceb1 commit da6b0d6
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 89 deletions.
32 changes: 20 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@ Practically speaking, for each notebook a python file is dynamically created:<br
Usage
--------

Only 2 magics commands are exposed: `%jet_init` and `%jet`
3 magics commands are exposed: `%jet_beg`, `%jet` and `%jet_end`

* **jet_init**: save all the content of the current cell and place it at the top of the file.
It is supposed to contains imports / global variables.
* **jet_beg**: save all the content of the current cell and place it at the top of the file.<br>
It is supposed to contains imports / global variables.<br>
*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.<br>
Classes, function and decorators are supported.<br>
Comments will be stripped.<br>
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.<br>
It is supposed to contain for example a ifmain block.<br>
*NB: Only one init is allowed and it must be the last line of the cell.*


Example
------------
Expand All @@ -37,7 +42,7 @@ Example
import numpy as np
pi = 3.14
%jet_init
%jet_beg
```
*And save it as raw content*

Expand Down Expand Up @@ -68,24 +73,26 @@ print circle_area(2.2)

<hr>

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.
Expand All @@ -99,6 +106,7 @@ Jupyjet is available on pip:

```
pip install jupyjet
pip3 install jupyjet
```


Expand Down
162 changes: 86 additions & 76 deletions jupyjet.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-


Expand All @@ -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.
Expand Down Expand Up @@ -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.
"""
Expand All @@ -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.
"""
Expand All @@ -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
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit da6b0d6

Please sign in to comment.