Skip to content

Commit

Permalink
Release 1.0.0: Refactor kttool to only use python. Fix a minor issue …
Browse files Browse the repository at this point in the history
…with submit where it terminate at New
  • Loading branch information
heiseish committed Nov 1, 2021
1 parent 4fe4ff7 commit 7140dfe
Show file tree
Hide file tree
Showing 22 changed files with 1,081 additions and 29,217 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,5 @@ dmypy.json
.pyre/

build.sh
publish.sh
Makefile
8 changes: 6 additions & 2 deletions kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#!/usr/bin/env python
import sys
import signal
from src.ktlib import arg_parse, color_red, color_green, exit_gracefully
from kttool.parser import arg_parse
from kttool.logger import log_red, log
from kttool.utils import exit_gracefully
import traceback


if __name__ == '__main__':
Expand All @@ -11,4 +14,5 @@ if __name__ == '__main__':
action = arg_parse(sys.argv[1:])
action.act()
except Exception as e:
print(color_red(str(e)))
log_red(str(e))
log(traceback.format_exc())
1 change: 1 addition & 0 deletions kttool/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
File renamed without changes.
Empty file added kttool/actions/__init__.py
Empty file.
124 changes: 124 additions & 0 deletions kttool/actions/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@

import json
import os
from kttool.logger import log
from pathlib import Path
import subprocess
from kttool.base import Action
from kttool.logger import color_cyan, color_green, color_red, log_green
from kttool.utils import MAP_TEMPLATE_TO_PLANG, ask_with_default

class Config(Action):
def add_template(self) -> None:
question = 'Which template would you like to add:\n'
selectable_lang = {}
idx = 1
existed_templates = {}
options = {}

log_green('Adapted from xalanq\'s cf tool')
log('''
Template will run 3 scripts in sequence when you run "kt test":
- before_script (execute once)
- script (execute the number of samples times)
- after_script (execute once)
You could set "before_script" or "after_script" to empty string, meaning not executing.
You have to run your program in "script" with standard input/output (no need to redirect).
You can insert some placeholders in your scripts. When execute a script,
cf will replace all placeholders by following rules:
$%path%$ Path to source file (Excluding $%full%$, e.g. "/home/user/")
$%full%$ Full name of source file (e.g. "a.cpp")
$%file%$ Name of source file (Excluding suffix, e.g. "a")
$%rand%$ Random string with 8 character (including "a-z" "0-9")
''')


existed_templates = self.load_kt_config()

for template_type, lang in MAP_TEMPLATE_TO_PLANG.items():
if template_type not in existed_templates:
temp = f'{idx} ({lang.extension}): {lang.full_name}\n'
question += temp
selectable_lang[idx] = (template_type, lang)
idx += 1

res = input(question)
ret = int(res)
assert 1 <= ret < idx, 'Invalid input'

selected_lang = selectable_lang[ret][1]

import readline, glob
def complete(text, state):
return (glob.glob(os.path.expanduser(text)+'*')+[None])[state]

readline.set_completer_delims(' \t\n;')
readline.parse_and_bind("tab: complete")
readline.set_completer(complete)
options['path'] = os.path.expanduser(input('Path to template file: '))
options['pre_script'] = ask_with_default('Pre-script', selected_lang.pre_script)
options['script'] = ask_with_default('Script', selected_lang.script)
options['post_script'] = ask_with_default('Post-script', selected_lang.post_script)
options['default'] = False if existed_templates else True

existed_templates[selected_lang.alias] = options
with open(self.kt_config, 'w') as kt_config:
json.dump(existed_templates, kt_config, indent=2)
log_green(f'Yosh, your configuration has been saved to {self.kt_config}')


def remove_template(self) -> None:
''' Remove a template from ktconfig file'''
existed_templates = self.load_kt_config()

log(f'Which template would you like to {color_red("delete")} ? For eg cpp, cc, ...')
for k in existed_templates.keys():
log(k)
res = input()

assert res in existed_templates, f'Invalid template chosen. Template {res} is not in ur config file'

move_default = existed_templates[res]['default']
existed_templates.pop(res, None)
if existed_templates and move_default: # move default to the first key of template
existed_templates[next(iter(existed_templates))] = True
with open(self.kt_config, 'w') as kt_config:
json.dump(existed_templates, kt_config, indent=2)

def update_default(self) -> None:
default_key = ''
existed_templates = self.load_kt_config()

log(f'Which template would you like to gen as {color_cyan("default")} ? For eg cpp, cc, ...')

for k, v in existed_templates.items():
log(f'{k} {color_green("(default)") if v["default"] else ""}')
if v["default"]:
default_key = k
res = input()

assert res in existed_templates, f'Invalid template chosen. Template {res} is not in ur config file'
existed_templates[default_key]["default"] = False
existed_templates[res]["default"] = True
with open(self.kt_config, 'w') as kt_config:
json.dump(existed_templates, kt_config, indent=2)
log_green('Yosh, your configuration has been saved')

def _act(self) -> None:
question = color_cyan('Select an option:\n')
question += """1: Add a template
2: Remove a template
3: Select a default template
"""
res = input(question)
opt = int(res)
if opt == 1:
self.add_template()
elif opt == 2:
self.remove_template()
elif opt == 3:
self.update_default()
else:
raise ValueError('Invalid option')
113 changes: 113 additions & 0 deletions kttool/actions/gen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from pathlib import Path
from kttool.base import Action
import requests
from bs4 import BeautifulSoup
from dataclasses import dataclass
from concurrent.futures import ProcessPoolExecutor
import json
from kttool.logger import log, log_green, log_red
import shutil
from collections import namedtuple

from kttool.utils import HEADERS, MAP_TEMPLATE_TO_PLANG



@dataclass
class SampleData:
problem_id: str = ''
is_in: bool = True
sample_id: str = ''
data: str = ''



def write_samples(sample_data: SampleData) -> None:
""" Write input/output sample to file. This is used for multiprocess pool to generate input/output files
Parameters
----------
sample_data : SampleData
a tuple representing index, string data, problem id and a boolean declaring whether current
file is input (False if the file is output)
"""
file_name_prefix = 'in' if sample_data.is_in else 'ans'
file_name = f'{sample_data.problem_id}/{file_name_prefix}{sample_data.sample_id}.txt'

with open(file_name, 'w+') as f:
f.write(sample_data.data)

class Gen(Action):
REQUIRED_CONFIG = True

''' Handle `gen` command for kt_tool '''
problem_id: str

__slots__ = 'problem_id'


''' Handle `gen` command for kt_tool '''
def __init__(self, problem_id: str, *args, **kwargs):
super().__init__(*args, **kwargs)
self.problem_id = problem_id

def _gen_samples(self) -> None:
""" Generate sample input file for `self.problem_id`
The basic flow is to scrape the problem task page and retrieve the relevent fields
Generate the sample files to problem id folder
For example, if the problem id is distinctivecharacter, `kt gen` will
- Generate a folder called distinctivecharacter
- Get sample intput, output from problem task page and generate to distinctivecharacter/, in this
example there will be 4 files generated
+ distinctivecharacter/in1.txt
+ distinctivecharacter/ans1.txt
+ distinctivecharacter/in2.txt
+ distinctivecharacter/ans2.txt
- Generate a template file (distinctivecharacter.cpp) if a template file is provided in the .ktconfig file
"""
template_file = {}
sample_data = []
i = 0

self.login()
url = self.get_problem_url()
page = requests.get(url, cookies=self.cookies, headers=HEADERS)
soup = BeautifulSoup(page.content, 'html.parser')
data = soup.find_all('pre')

for i in range(len(data)):
if i & 1:
sample_data.append(SampleData(sample_id=i // 2 + 1, data=data[i].text, problem_id=self.problem_id, is_in=False))
else:
sample_data.append(SampleData(sample_id=i // 2 + 1, data=data[i].text, problem_id=self.problem_id, is_in=True))

assert len(data) % 2 == 0, 'Internal error: Number of sample input '\
' is not equal to number of sample output'

with ProcessPoolExecutor(max_workers=4) as executor:
executor.map(write_samples, sample_data)

log_green(f'Generate {len(sample_data) // 2} sample(s) to {self.problem_id}')
if not self.kt_config.is_file():
log_red('.ktconfig file has not been set up so no template was generated. '
'Please use `kt config` to set up a template file')
return



template_file = self.load_kt_config()

for k, template in template_file.items():
if template.get('default', False):
shutil.copyfile(template.get('path'), f'{self.problem_id}/{self.problem_id}.{MAP_TEMPLATE_TO_PLANG[k].extension}')
log_green('Template file has been generated')
return
log_red(f'No default template detected in {self.kt_config}')

def get_problem_id(self) -> str:
return self.problem_id

def _act(self) -> None:
log(f'Problem is {self.problem_id}')
problem_dir = self.cwd / self.problem_id
problem_dir.mkdir(parents=True, exist_ok=True)
self._gen_samples()
11 changes: 11 additions & 0 deletions kttool/actions/open.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from kttool.base import Action
import webbrowser

from kttool.logger import log

class Open(Action):
REQUIRED_CONFIG = True

def _act(self) -> None:
log(f'Openning {self.get_problem_url()}')
webbrowser.open(self.get_problem_url())
Loading

0 comments on commit 7140dfe

Please sign in to comment.