2020-03-04 13:46:06 +01:00
|
|
|
#!/usr/bin/env python3
|
2020-03-09 11:48:13 +01:00
|
|
|
"""Transform any Python sample or example to Python NoteBook."""
|
2018-08-03 10:13:16 +02:00
|
|
|
import ast
|
2020-09-27 15:38:49 +02:00
|
|
|
import os
|
2020-10-02 17:47:54 +02:00
|
|
|
import sys
|
2022-04-13 14:32:40 +02:00
|
|
|
import re
|
2020-03-09 11:48:13 +01:00
|
|
|
from nbformat import v3
|
|
|
|
|
from nbformat import v4
|
2017-11-20 17:04:25 +01:00
|
|
|
|
2020-03-09 11:48:13 +01:00
|
|
|
input_file = sys.argv[1]
|
2020-09-27 15:38:49 +02:00
|
|
|
print(f'reading {input_file}')
|
2020-03-09 11:48:13 +01:00
|
|
|
with open(input_file) as fpin:
|
2022-04-13 14:32:40 +02:00
|
|
|
text = fpin.read()
|
2017-11-20 17:04:25 +01:00
|
|
|
|
2020-09-27 15:38:49 +02:00
|
|
|
# Compute output file path.
|
|
|
|
|
output_file = input_file
|
|
|
|
|
output_file = output_file.replace('.py', '.ipynb')
|
|
|
|
|
# For example/python/foo.py -> example/notebook/examples/foo.ipynb
|
|
|
|
|
output_file = output_file.replace('examples/python',
|
|
|
|
|
'examples/notebook/examples')
|
|
|
|
|
# For example/contrib/foo.py -> example/notebook/contrib/foo.ipynb
|
|
|
|
|
output_file = output_file.replace('examples/contrib',
|
|
|
|
|
'examples/notebook/contrib')
|
|
|
|
|
# For ortools/*/samples/foo.py -> example/notebook/*/foo.ipynb
|
|
|
|
|
output_file = output_file.replace('ortools', 'examples/notebook')
|
|
|
|
|
output_file = output_file.replace('samples/', '')
|
|
|
|
|
|
2018-08-03 10:13:16 +02:00
|
|
|
nbook = v3.reads_py('')
|
2017-11-20 17:04:25 +01:00
|
|
|
nbook = v4.upgrade(nbook) # Upgrade v3 to v4
|
|
|
|
|
|
2020-10-22 17:12:44 +02:00
|
|
|
print('Adding copyright cell...')
|
2022-04-13 14:32:40 +02:00
|
|
|
google = '##### Copyright 2022 Google LLC.'
|
2021-12-14 13:04:23 +01:00
|
|
|
nbook['cells'].append(v4.new_markdown_cell(source=google, id='google'))
|
2020-09-27 15:38:49 +02:00
|
|
|
|
2020-10-22 17:12:44 +02:00
|
|
|
print('Adding license cell...')
|
|
|
|
|
apache = '''Licensed under the Apache License, Version 2.0 (the "License");
|
2020-09-27 15:38:49 +02:00
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
|
limitations under the License.
|
|
|
|
|
'''
|
2021-12-14 13:04:23 +01:00
|
|
|
nbook['cells'].append(v4.new_markdown_cell(source=apache, id='apache'))
|
2020-09-27 15:38:49 +02:00
|
|
|
|
2020-10-22 17:12:44 +02:00
|
|
|
print('Adding Title cell...')
|
2020-09-27 15:38:49 +02:00
|
|
|
basename = '# ' + os.path.basename(input_file).replace('.py', '')
|
2021-12-14 13:04:23 +01:00
|
|
|
nbook['cells'].append(v4.new_markdown_cell(source=basename, id='basename'))
|
2020-09-27 15:38:49 +02:00
|
|
|
|
2020-10-22 17:12:44 +02:00
|
|
|
print('Adding link cell...')
|
2022-05-16 11:58:57 +02:00
|
|
|
github_logo = 'https://raw.githubusercontent.com/google/or-tools/main/tools/github_32px.png'
|
|
|
|
|
github_path = 'https://github.com/google/or-tools/blob/main/' + input_file
|
2020-09-27 15:38:49 +02:00
|
|
|
|
2022-05-16 11:58:57 +02:00
|
|
|
colab_path = 'https://colab.research.google.com/github/google/or-tools/blob/main/' + output_file
|
|
|
|
|
colab_logo = 'https://raw.githubusercontent.com/google/or-tools/main/tools/colab_32px.png'
|
2020-09-27 15:38:49 +02:00
|
|
|
link = f'''<table align=\"left\">
|
|
|
|
|
<td>
|
|
|
|
|
<a href=\"{colab_path}\"><img src=\"{colab_logo}\"/>Run in Google Colab</a>
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
<a href=\"{github_path}\"><img src=\"{github_logo}\"/>View source on GitHub</a>
|
|
|
|
|
</td>
|
|
|
|
|
</table>'''
|
2021-12-14 13:04:23 +01:00
|
|
|
nbook['cells'].append(v4.new_markdown_cell(source=link, id='link'))
|
2020-09-27 15:38:49 +02:00
|
|
|
|
2022-04-13 14:32:40 +02:00
|
|
|
print('Adding ortools install cell...')
|
2020-10-22 17:12:44 +02:00
|
|
|
install_doc = ('First, you must install '
|
|
|
|
|
'[ortools](https://pypi.org/project/ortools/) package in this '
|
|
|
|
|
'colab.')
|
2021-12-14 13:04:23 +01:00
|
|
|
nbook['cells'].append(v4.new_markdown_cell(source=install_doc, id='doc'))
|
2020-09-27 15:38:49 +02:00
|
|
|
install_cmd = '!pip install ortools'
|
2021-12-14 13:04:23 +01:00
|
|
|
nbook['cells'].append(v4.new_code_cell(source=install_cmd, id='install'))
|
2020-09-27 15:38:49 +02:00
|
|
|
|
2020-10-22 17:12:44 +02:00
|
|
|
print('Adding code cell...')
|
2018-08-03 10:13:16 +02:00
|
|
|
all_blocks = ast.parse(text).body
|
2020-09-27 15:38:49 +02:00
|
|
|
print(f'number of bocks: {len(all_blocks)}')
|
2020-03-09 11:48:13 +01:00
|
|
|
line_start = [c.lineno - 1 for c in all_blocks]
|
2018-08-03 10:26:25 +02:00
|
|
|
line_start[0] = 0
|
2018-08-03 10:13:16 +02:00
|
|
|
lines = text.split('\n')
|
|
|
|
|
|
2018-09-05 14:27:48 +02:00
|
|
|
full_text = ''
|
2022-04-13 14:32:40 +02:00
|
|
|
for idx, (c_block, s, e) in enumerate(
|
|
|
|
|
zip(all_blocks, line_start, line_start[1:] + [len(lines)])):
|
|
|
|
|
print(f'block[{idx}]: {c_block}')
|
|
|
|
|
c_text = '\n'.join(lines[s:e])
|
|
|
|
|
# Clean boilerplate header and description
|
2022-04-26 17:51:47 +02:00
|
|
|
if (idx == 0 and isinstance(c_block, ast.Expr)
|
|
|
|
|
and isinstance(c_block.value, ast.Constant)):
|
2022-04-13 14:32:40 +02:00
|
|
|
print('Adding description cell...')
|
|
|
|
|
filtered_lines = lines[s:e]
|
|
|
|
|
#filtered_lines = list(filter(lambda l: not l.startswith('#!'), lines[s:e]))
|
2022-04-26 17:51:47 +02:00
|
|
|
filtered_lines = list(
|
|
|
|
|
filter(lambda l: not re.search(r'^#!', l), filtered_lines))
|
|
|
|
|
filtered_lines = list(
|
|
|
|
|
filter(lambda l: not re.search(r'# \[START .*\]$', l),
|
|
|
|
|
filtered_lines))
|
|
|
|
|
filtered_lines = list(
|
|
|
|
|
filter(lambda l: not re.search(r'# \[END .*\]$', l),
|
|
|
|
|
filtered_lines))
|
2022-04-13 14:32:40 +02:00
|
|
|
# TODO(mizux): Remove only copyright not all line with '^#'
|
2022-04-26 17:51:47 +02:00
|
|
|
filtered_lines = list(
|
|
|
|
|
filter(lambda l: not l.startswith(r'#'), filtered_lines))
|
2022-04-13 14:32:40 +02:00
|
|
|
filtered_lines = [s.replace(r'"""', '') for s in filtered_lines]
|
|
|
|
|
filtered_text = '\n'.join(filtered_lines)
|
2022-04-26 17:51:47 +02:00
|
|
|
nbook['cells'].append(
|
|
|
|
|
v4.new_markdown_cell(source=filtered_text, id='description'))
|
2022-04-13 14:32:40 +02:00
|
|
|
# Remove absl app and flags import
|
|
|
|
|
elif (isinstance(c_block, ast.ImportFrom) and c_block.module == 'absl'
|
2022-04-26 17:51:47 +02:00
|
|
|
and c_block.names[0].name in ('flags', 'app')):
|
2022-04-13 14:32:40 +02:00
|
|
|
print(f'Removing import {c_block.module}.{c_block.names[0].name}...')
|
|
|
|
|
# Rewrite `FLAGS = flags.FLAGS`
|
2022-04-26 17:51:47 +02:00
|
|
|
elif (isinstance(c_block, ast.Assign)
|
|
|
|
|
and isinstance(c_block.targets[0], ast.Name)
|
|
|
|
|
and c_block.targets[0].id == 'FLAGS'):
|
2022-04-13 14:32:40 +02:00
|
|
|
print('Adding FLAGS class...')
|
|
|
|
|
fixed_lines = ['class FLAGS: pass\n']
|
|
|
|
|
full_text += '\n'.join(fixed_lines) + '\n'
|
|
|
|
|
# Rewrite `flags.DEFINE_*(*)`
|
2022-04-26 17:51:47 +02:00
|
|
|
elif (isinstance(c_block, ast.Expr) and isinstance(c_block.value, ast.Call)
|
|
|
|
|
and isinstance(c_block.value.func, ast.Attribute)
|
|
|
|
|
and c_block.value.func.value.id == 'flags'):
|
2022-04-13 14:32:40 +02:00
|
|
|
print('Adding FLAGS field...')
|
|
|
|
|
fixed_lines = []
|
|
|
|
|
attr = c_block.value.func.attr
|
|
|
|
|
if attr in ('DEFINE_integer', 'DEFINE_bool', 'DEFINE_string'):
|
|
|
|
|
args = c_block.value.args
|
|
|
|
|
#print(f'args: {args}')
|
|
|
|
|
name = args[0].value
|
|
|
|
|
if isinstance(args[1], ast.Constant):
|
|
|
|
|
value = args[1].value
|
|
|
|
|
elif isinstance(args[1], ast.UnaryOp):
|
|
|
|
|
if isinstance(args[1].op, ast.USub):
|
|
|
|
|
value = -1 * int(args[1].operand.value)
|
|
|
|
|
else:
|
|
|
|
|
print(f'unknow value operator: "{args[1].op}"')
|
|
|
|
|
sys.exit(2)
|
|
|
|
|
else:
|
|
|
|
|
print(f'unknow value: "{args[1]}"')
|
|
|
|
|
sys.exit(2)
|
|
|
|
|
comment = args[2].value
|
|
|
|
|
|
|
|
|
|
print(f'FLAGS.{name} = \'{value}\' # {comment}')
|
|
|
|
|
if attr in ('DEFINE_integer', 'DEFINE_bool'):
|
|
|
|
|
fixed_lines.append(f'FLAGS.{name} = {value} # {comment}\n')
|
|
|
|
|
else:
|
|
|
|
|
fixed_lines.append(f'FLAGS.{name} = \'{value}\' # {comment}\n')
|
|
|
|
|
else:
|
|
|
|
|
print(f'unknow method: "{attr}"')
|
|
|
|
|
sys.exit(2)
|
|
|
|
|
full_text += '\n'.join(fixed_lines)
|
|
|
|
|
# Add empty line after the last flags.DEFINE
|
2022-04-26 17:51:47 +02:00
|
|
|
if e - 2 >= s and lines[e - 1] == '' and lines[e - 2] == '':
|
2022-04-13 14:32:40 +02:00
|
|
|
full_text += '\n'
|
|
|
|
|
# Unwrap __main__ function
|
2022-04-26 17:51:47 +02:00
|
|
|
elif (isinstance(c_block, ast.If)
|
|
|
|
|
and c_block.test.comparators[0].s == '__main__'):
|
2022-04-13 14:32:40 +02:00
|
|
|
print('Unwrapping main function...')
|
|
|
|
|
c_lines = lines[s + 1:e]
|
|
|
|
|
# remove start and de-indent lines
|
|
|
|
|
spaces_to_delete = c_block.body[0].col_offset
|
|
|
|
|
fixed_lines = [
|
|
|
|
|
n_line[spaces_to_delete:]
|
|
|
|
|
if n_line.startswith(' ' * spaces_to_delete) else n_line
|
|
|
|
|
for n_line in c_lines
|
|
|
|
|
]
|
|
|
|
|
filtered_lines = fixed_lines
|
2022-04-26 17:51:47 +02:00
|
|
|
filtered_lines = list(
|
|
|
|
|
filter(lambda l: not re.search(r'# \[START .*\]$', l),
|
|
|
|
|
filtered_lines))
|
|
|
|
|
filtered_lines = list(
|
|
|
|
|
filter(lambda l: not re.search(r'# \[END .*\]$', l),
|
|
|
|
|
filtered_lines))
|
|
|
|
|
filtered_lines = [
|
|
|
|
|
re.sub(r'app.run\((.*)\)$', r'\1()', s) for s in filtered_lines
|
|
|
|
|
]
|
2022-04-13 14:32:40 +02:00
|
|
|
full_text += '\n'.join(filtered_lines) + '\n'
|
|
|
|
|
# Others
|
|
|
|
|
else:
|
|
|
|
|
print('Appending block...')
|
|
|
|
|
filtered_lines = lines[s:e]
|
2022-04-26 17:51:47 +02:00
|
|
|
filtered_lines = list(
|
|
|
|
|
filter(lambda l: not re.search(r'# \[START .*\]$', l),
|
|
|
|
|
filtered_lines))
|
|
|
|
|
filtered_lines = list(
|
|
|
|
|
filter(lambda l: not re.search(r'# \[END .*\]$', l),
|
|
|
|
|
filtered_lines))
|
2022-04-13 14:32:40 +02:00
|
|
|
full_text += '\n'.join(filtered_lines) + '\n'
|
2018-09-05 14:27:48 +02:00
|
|
|
|
2021-12-14 13:04:23 +01:00
|
|
|
nbook['cells'].append(v4.new_code_cell(source=full_text, id='code'))
|
2018-09-05 14:27:48 +02:00
|
|
|
|
|
|
|
|
jsonform = v4.writes(nbook) + '\n'
|
2020-03-04 13:46:06 +01:00
|
|
|
|
2020-09-27 15:38:49 +02:00
|
|
|
print(f'writing {output_file}')
|
2020-03-09 11:48:13 +01:00
|
|
|
with open(output_file, 'w') as fpout:
|
2022-04-13 14:32:40 +02:00
|
|
|
fpout.write(jsonform)
|