Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest pep8-naming
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
pip install .[lint,test]
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,19 @@ ac-library-python is a Python port of [AtCoder Library (ACL)](https://atcoder.jp
+ scc
+ twosat

## Install

```
pip install git+https://github.com/not522/ac-library-python
```

## Usage

Copy and paste the library into your code.
The following command outputs a single combined code which can run in online judge systems.

We also plan to provide a feature to combine dependent libraries into a single file.
```
python -m atcoder [your-source-code] -o [single-combined-code]
```

## FAQ

Expand Down
12 changes: 10 additions & 2 deletions README_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,19 @@ ac-library-pythonは、[AtCoder Library (ACL)](https://atcoder.jp/posts/517)のP
+ scc
+ twosat

## インストール

```
pip install git+https://github.com/not522/ac-library-python
```

## 使い方

提出用のコードに本ライブラリの必要な部分をコピー&ペーストしてご利用ください
以下のコマンドでオンラインジャッジで実行可能な結合されたソースコードが出力されます

また、依存関係にあるライブラリを一つのファイルにまとめる機能を提供する予定です。
```
python -m atcoder [your-source-code] -o [single-combined-code]
```

## よくある質問

Expand Down
3 changes: 3 additions & 0 deletions atcoder/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Python port of AtCoder Library.

__version__ = '0.0.1'
144 changes: 144 additions & 0 deletions atcoder/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import argparse
import ast
import importlib
import inspect
import re
from typing import List, Optional


class ImportInfo:
def __init__(self, lineno: int, end_lineno: int,
import_from: Optional[str] = None,
name: Optional[str] = None,
asname: Optional[str] = None) -> None:
self.lineno = lineno
self.end_lineno = end_lineno
self.import_from = import_from
self.name = name
self.asname = asname


def iter_child_nodes(
node: ast.AST,
import_info: Optional[ImportInfo] = None) -> List[ImportInfo]:
result = []

if isinstance(node, ast.alias):
if import_info:
result.append(ImportInfo(
import_info.lineno, import_info.end_lineno,
import_info.import_from, node.name, node.asname))
return result

if isinstance(node, ast.Import):
for name in node.names:
if re.match(r'^atcoder\.?', name.name):
import_info = ImportInfo(node.lineno, node.end_lineno)
elif isinstance(node, ast.ImportFrom):
import_info = ImportInfo(node.lineno, node.end_lineno, node.module)

for child in ast.iter_child_nodes(node):
result += iter_child_nodes(child, import_info)
return result


class ModuleImporter:
def __init__(self) -> None:
self.imported_modules = []

def import_module(self, import_from: Optional[str], name: str,
asname: Optional[str] = None) -> str:
result = ''

if import_from is None:
module_name = name
else:
try:
module_name = import_from + '.' + name
importlib.import_module(module_name)
except ImportError:
module_name = import_from

if module_name not in self.imported_modules:
self.imported_modules.append(module_name)

module = importlib.import_module(module_name)
source = inspect.getsource(module)
lines = source.split('\n')
imports = iter_child_nodes(ast.parse(source))

import_lines = []
for import_info in imports:
result += self.import_module(
import_info.import_from, import_info.name,
import_info.asname)
for line in range(import_info.lineno - 1,
import_info.end_lineno):
import_lines.append(line)

for lineno, line in enumerate(lines):
if lineno not in import_lines:
continue
lines[lineno] = '# ' + line # TODO(not): indent

modules = module_name.split('.')
for i in range(len(modules) - 1):
result += self.import_module(None, '.'.join(modules[:i + 1]))

code = '_' + module_name.replace('.', '_') + '_code'
result += f'{code} = """\n'
result += '\n'.join(lines)
result += '"""\n\n'
result += f"{module_name} = types.ModuleType('{module_name}')\n"
result += f'exec({code}, {module_name}.__dict__)\n'

if import_from is None:
if asname is None:
if name != module_name:
result += f'{name} = {module_name}\n'
else:
result += f'{asname} = {module_name}\n'
else:
if asname is None:
if name != import_from + '.' + name:
result += f'{name} = {import_from}.{name}\n'
else:
result += f'{asname} = {import_from}.{name}\n'

return result + '\n'


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('src', help='Source code')
parser.add_argument('-o', '--output', help='Single combined code')
args = parser.parse_args()

with open(args.src) as f:
lines = f.readlines()
imports = iter_child_nodes(ast.parse(''.join(lines)))

importer = ModuleImporter()
result = 'import types\n\n'
import_lines = []
for import_info in imports:
result += importer.import_module(
import_info.import_from, import_info.name, import_info.asname)
for line in range(import_info.lineno - 1, import_info.end_lineno):
import_lines.append(line)

for lineno, line in enumerate(lines):
if lineno not in import_lines:
continue
lines[lineno] = '# ' + line # TODO(not): indent
result += ''.join(lines)

if args.output:
with open(args.output, 'w') as f:
f.write(result)
else:
print(result, end='')


if __name__ == '__main__':
main()
14 changes: 14 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[metadata]
name = ac-library-python
version = attr: atcoder.__version__
description = Python port of AtCoder Library (ACL).
license = CC0

[options]
packages = find:

[options.extras_require]
lint =
flake8
pep8-naming
test = pytest
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import setuptools

setuptools.setup()