Get rid of some orange lines emacs was showing me
This commit is contained in:
parent
321ab89b18
commit
267a51124f
9 changed files with 203 additions and 89 deletions
|
|
@ -3,18 +3,18 @@ import logging
|
|||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .colors import *
|
||||
from .config import *
|
||||
from .explore import *
|
||||
from .known_files import *
|
||||
from .process import *
|
||||
from .prompt import *
|
||||
from .util import *
|
||||
from .colors import style_path, style_warning
|
||||
from .config import DEFAULT_CONFIG, Config, ConfigurationException
|
||||
from .explore import find_config_files
|
||||
from .known_files import KnownFiles
|
||||
from .process import Processor
|
||||
from .prompt import prompt_choice
|
||||
from .util import CatastrophicError, LessCatastrophicError
|
||||
|
||||
LOG_STYLE = "{"
|
||||
LOG_FORMAT = "{levelname:>7}: {message}"
|
||||
#logging.basicConfig(level=logging.DEBUG, style="{", format="{levelname:>7}: {message}")
|
||||
#logging.basicConfig(level=logging.INFO, style="{", format="{levelname:>7}: {message}")
|
||||
# logging.basicConfig(level=logging.DEBUG, style="{", format="{levelname:>7}: {message}")
|
||||
# logging.basicConfig(level=logging.INFO, style="{", format="{levelname:>7}: {message}")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
HEADER_FILE_SUFFIX = ".evering-header"
|
||||
|
|
@ -62,8 +62,10 @@ Writing problems:
|
|||
- can't write to known files (error)
|
||||
"""
|
||||
|
||||
|
||||
def run(args: Any) -> None:
|
||||
config = Config.load_config_file(args.config_file and Path(args.config_file) or None)
|
||||
config = Config.load_config_file(args.config_file
|
||||
and Path(args.config_file) or None)
|
||||
known_files = KnownFiles(config.known_files)
|
||||
|
||||
processor = Processor(config, known_files)
|
||||
|
|
@ -75,7 +77,8 @@ def run(args: Any) -> None:
|
|||
except LessCatastrophicError as e:
|
||||
logger.error(e)
|
||||
|
||||
if prompt_choice("[C]ontinue to the next file or [A]bort the program?", "Ca") == "a":
|
||||
if prompt_choice("[C]ontinue to the next file or [A]bort the "
|
||||
"program?", "Ca") == "a":
|
||||
raise CatastrophicError("Aborted")
|
||||
|
||||
for path in known_files.find_forgotten_files():
|
||||
|
|
@ -84,6 +87,7 @@ def run(args: Any) -> None:
|
|||
|
||||
known_files.save_final()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-c", "--config-file", type=Path)
|
||||
|
|
@ -95,7 +99,8 @@ def main() -> None:
|
|||
logging.basicConfig(level=level, style=LOG_STYLE, format=LOG_FORMAT)
|
||||
|
||||
if args.export_default_config is not None:
|
||||
logger.info(f"Exporting default config to {style_path(args.export_default_config)}")
|
||||
logger.info("Exporting default config to "
|
||||
f"{style_path(args.export_default_config)}")
|
||||
with open(args.export_default_config, "w") as f:
|
||||
f.write(DEFAULT_CONFIG.to_config_file())
|
||||
return
|
||||
|
|
@ -107,5 +112,6 @@ def main() -> None:
|
|||
except ConfigurationException as e:
|
||||
logger.error(e)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -5,13 +5,15 @@ escape sequences.
|
|||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Optional, Tuple, Union
|
||||
from typing import Union
|
||||
|
||||
__all__ = [
|
||||
"CSI", "ERASE_LINE",
|
||||
"BOLD", "ITALIC", "UNDERLINE",
|
||||
"Color",
|
||||
"BLACK", "RED", "GREEN", "YELLOW", "BLUE", "MAGENTA", "CYAN", "WHITE", "BRIGHT_BLACK", "BRIGHT_RED", "BRIGHT_GREEN", "BRIGHT_YELLOW", "BRIGHT_BLUE", "BRIGHT_MAGENTA", "BRIGHT_CYAN", "BRIGHT_WHITE",
|
||||
"BLACK", "RED", "GREEN", "YELLOW", "BLUE", "MAGENTA", "CYAN", "WHITE",
|
||||
"BRIGHT_BLACK", "BRIGHT_RED", "BRIGHT_GREEN", "BRIGHT_YELLOW",
|
||||
"BRIGHT_BLUE", "BRIGHT_MAGENTA", "BRIGHT_CYAN", "BRIGHT_WHITE",
|
||||
"style_sequence", "styled",
|
||||
"style_path", "style_var", "style_error", "style_warning",
|
||||
]
|
||||
|
|
@ -30,11 +32,13 @@ UNDERLINE = 4
|
|||
|
||||
# Colors
|
||||
|
||||
|
||||
@dataclass
|
||||
class Color:
|
||||
fg: int
|
||||
bg: int
|
||||
|
||||
|
||||
BLACK = Color(30, 40)
|
||||
RED = Color(31, 41)
|
||||
GREEN = Color(32, 42)
|
||||
|
|
@ -52,10 +56,12 @@ BRIGHT_MAGENTA = Color(95, 105)
|
|||
BRIGHT_CYAN = Color(96, 106)
|
||||
BRIGHT_WHITE = Color(97, 107)
|
||||
|
||||
|
||||
def style_sequence(*args: int) -> str:
|
||||
arglist = ";".join(str(arg) for arg in args)
|
||||
return f"{CSI}{arglist}m"
|
||||
|
||||
|
||||
def styled(text: str, *args: int) -> str:
|
||||
if args:
|
||||
sequence = style_sequence(*args)
|
||||
|
|
@ -64,16 +70,20 @@ def styled(text: str, *args: int) -> str:
|
|||
else:
|
||||
return text # No styling necessary
|
||||
|
||||
|
||||
def style_path(path: Union[str, Path]) -> str:
|
||||
if isinstance(path, Path):
|
||||
path = str(path)
|
||||
return styled(path, BRIGHT_BLACK.fg, BOLD)
|
||||
|
||||
|
||||
def style_var(text: str) -> str:
|
||||
return styled(repr(text), BLUE.fg)
|
||||
|
||||
|
||||
def style_error(text: str) -> str:
|
||||
return styled(text, RED.fg, BOLD)
|
||||
|
||||
|
||||
def style_warning(text: str) -> str:
|
||||
return styled(text, YELLOW.fg, BOLD)
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ from dataclasses import dataclass
|
|||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from .colors import *
|
||||
from .util import *
|
||||
from .colors import style_error, style_path, style_var
|
||||
from .util import (ExecuteException, ReadFileException, copy_local_variables,
|
||||
get_host, get_user, read_file, safer_exec)
|
||||
|
||||
__all__ = [
|
||||
"DEFAULT_LOCATIONS",
|
||||
|
|
@ -28,18 +29,22 @@ DEFAULT_LOCATIONS = [
|
|||
Path("~/.evering.py"),
|
||||
]
|
||||
|
||||
|
||||
class ConfigurationException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class DefaultConfigValue:
|
||||
# A short textual description of the value's function
|
||||
description: str
|
||||
# The actual default value
|
||||
value: Any
|
||||
# Whether this variable even has a default value or the value is set at runtime
|
||||
# Whether this variable even has a default value or the value is set at
|
||||
# runtime
|
||||
has_constant_value: bool
|
||||
|
||||
|
||||
class DefaultConfig:
|
||||
def __init__(self) -> None:
|
||||
self._values: Dict[str, DefaultConfigValue] = {}
|
||||
|
|
@ -60,7 +65,9 @@ class DefaultConfig:
|
|||
return self._values.get(name)
|
||||
|
||||
def to_local_vars(self) -> Dict[str, Any]:
|
||||
return {name: d.value for name, d in self._values.items() if d.has_constant_value}
|
||||
return {name: d.value
|
||||
for name, d in self._values.items()
|
||||
if d.has_constant_value}
|
||||
|
||||
def to_config(self) -> "Config":
|
||||
config = Config(self.to_local_vars())
|
||||
|
|
@ -70,10 +77,10 @@ class DefaultConfig:
|
|||
|
||||
def to_config_file(self) -> str:
|
||||
"""
|
||||
Attempt to convert the DefaultConfig into a format that can be read by a
|
||||
python interpreter. This assumes that all names are valid variable names
|
||||
and that the repr() representations of each object can be read by the
|
||||
interpreter.
|
||||
Attempt to convert the DefaultConfig into a format that can be read by
|
||||
a python interpreter. This assumes that all names are valid variable
|
||||
names and that the repr() representations of each object can be read by
|
||||
the interpreter.
|
||||
|
||||
This solution is quite hacky, so use at your own risk :P (At least make
|
||||
sure that this works with all your default values before you use it).
|
||||
|
|
@ -95,6 +102,7 @@ class DefaultConfig:
|
|||
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
DEFAULT_CONFIG = DefaultConfig()
|
||||
|
||||
DEFAULT_CONFIG.add(
|
||||
|
|
@ -120,17 +128,21 @@ DEFAULT_CONFIG.add(
|
|||
|
||||
DEFAULT_CONFIG.add(
|
||||
"binary",
|
||||
"When interpreting a header file: When True, the corresponding file is copied directly to the target instead of compiled. Has no effect if a file has no header file",
|
||||
("When interpreting a header file: When True, the corresponding file is "
|
||||
"copied directly to the target instead of compiled. Has no effect if a "
|
||||
"file has no header file"),
|
||||
value=True)
|
||||
|
||||
DEFAULT_CONFIG.add(
|
||||
"targets",
|
||||
"The locations a config file should be placed in. Either a path or a list of paths",
|
||||
("The locations a config file should be placed in. Either a path or a "
|
||||
"list of paths"),
|
||||
value=[])
|
||||
|
||||
DEFAULT_CONFIG.add(
|
||||
"action",
|
||||
"Whether a file should be treated as an action with a certain name. If set, must be a string",
|
||||
("Whether a file should be treated as an action with a certain name. If "
|
||||
"set, must be a string"),
|
||||
has_constant_value=False)
|
||||
|
||||
DEFAULT_CONFIG.add(
|
||||
|
|
@ -147,12 +159,14 @@ DEFAULT_CONFIG.add(
|
|||
|
||||
DEFAULT_CONFIG.add(
|
||||
"filename",
|
||||
"Name of the file currently being compiled, as a string. Set during compilation",
|
||||
("Name of the file currently being compiled, as a string. Set during "
|
||||
"compilation"),
|
||||
has_constant_value=False)
|
||||
|
||||
DEFAULT_CONFIG.add(
|
||||
"target",
|
||||
"Location the file is currently being compiled for, as a Path. Set during compilation",
|
||||
("Location the file is currently being compiled for, as a Path. Set "
|
||||
"during compilation"),
|
||||
has_constant_value=False)
|
||||
|
||||
DEFAULT_CONFIG.add(
|
||||
|
|
@ -165,6 +179,7 @@ DEFAULT_CONFIG.add(
|
|||
"Name of the current computer. Set during compilation",
|
||||
has_constant_value=False)
|
||||
|
||||
|
||||
class Config:
|
||||
@staticmethod
|
||||
def load_config_file(path: Optional[Path]) -> "Config":
|
||||
|
|
@ -183,10 +198,13 @@ class Config:
|
|||
conf = copy
|
||||
break
|
||||
except ConfigurationException as e:
|
||||
logger.debug(f"Tried default config file at {style_path(path)} and it didn't work: {e}")
|
||||
logger.debug("Tried default config file at "
|
||||
f"{style_path(path)} and it didn't work: {e}")
|
||||
else:
|
||||
raise ConfigurationException(style_error(
|
||||
"No valid config file found in any of the default locations"))
|
||||
"No valid config file found in any of the default "
|
||||
"locations"
|
||||
))
|
||||
else:
|
||||
# Use the path
|
||||
try:
|
||||
|
|
@ -208,7 +226,7 @@ class Config:
|
|||
May raise: ConfigurationException
|
||||
"""
|
||||
|
||||
if not "base_dir" in self.local_vars:
|
||||
if "base_dir" not in self.local_vars:
|
||||
self.local_vars["base_dir"] = path.parent
|
||||
|
||||
try:
|
||||
|
|
@ -228,10 +246,11 @@ class Config:
|
|||
May raise: ConfigurationException
|
||||
"""
|
||||
|
||||
if not name in self.local_vars:
|
||||
if name not in self.local_vars:
|
||||
raise ConfigurationException(
|
||||
style_error(f"Expected a variable named ") +
|
||||
style_var(name))
|
||||
style_error("Expected a variable named ") +
|
||||
style_var(name)
|
||||
)
|
||||
|
||||
value = self.local_vars[name]
|
||||
|
||||
|
|
@ -246,7 +265,7 @@ class Config:
|
|||
return value
|
||||
|
||||
def _get_optional(self, name: str, *types: type) -> Optional[Any]:
|
||||
if not name in self.local_vars:
|
||||
if name not in self.local_vars:
|
||||
return None
|
||||
else:
|
||||
return self._get(name, *types)
|
||||
|
|
@ -273,10 +292,12 @@ class Config:
|
|||
def _interpret_path(self, path: Union[str, Path]) -> Path:
|
||||
path = Path(path).expanduser()
|
||||
if path.is_absolute():
|
||||
logger.debug(style_path(path) + " is absolute, no interpreting required")
|
||||
logger.debug(f"{style_path(path)} is absolute, no interpreting "
|
||||
"required")
|
||||
return path
|
||||
else:
|
||||
logger.debug(style_path(path) + " is relative, interpreting as " + style_path(self.base_dir / path))
|
||||
logger.debug(f"{style_path(path)} is relative, interpreting as "
|
||||
f"{style_path(self.base_dir / path)}")
|
||||
return self.base_dir / path
|
||||
|
||||
@property
|
||||
|
|
@ -353,7 +374,7 @@ class Config:
|
|||
if len(delimiters[0]) < 1 or len(delimiters[1]) < 1:
|
||||
raise ConfigurationException(
|
||||
style_error("Expected both strings in variable ") +
|
||||
style_var(name) + style_error( "to be of length >= 1"))
|
||||
style_var(name) + style_error("to be of length >= 1"))
|
||||
|
||||
return delimiters
|
||||
|
||||
|
|
|
|||
|
|
@ -3,28 +3,33 @@ from dataclasses import dataclass
|
|||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from .colors import *
|
||||
from .util import *
|
||||
from .colors import style_error, style_path, style_warning
|
||||
from .util import CatastrophicError
|
||||
|
||||
__all__ = ["FileInfo", "find_config_files"]
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
HEADER_FILE_SUFFIX = ".evering-header"
|
||||
|
||||
|
||||
@dataclass
|
||||
class FileInfo:
|
||||
path: Path
|
||||
header: Optional[Path] = None
|
||||
|
||||
|
||||
def find_config_files(config_dir: Path) -> List[FileInfo]:
|
||||
try:
|
||||
return explore_dir(config_dir)
|
||||
except OSError as e:
|
||||
raise CatastrophicError(style_error("could not access config dir ") + style_path(config_dir) + f": {e}")
|
||||
raise CatastrophicError(style_error("could not access config dir ") +
|
||||
style_path(config_dir) + f": {e}")
|
||||
|
||||
|
||||
def explore_dir(cur_dir: Path) -> List[FileInfo]:
|
||||
if not cur_dir.is_dir():
|
||||
raise CatastrophicError(style_path(cur_dir) + style_error(" is not a directory"))
|
||||
raise CatastrophicError(style_path(cur_dir) +
|
||||
style_error(" is not a directory"))
|
||||
|
||||
files: Dict[Path, FileInfo] = {}
|
||||
header_files: List[Path] = []
|
||||
|
|
@ -51,9 +56,13 @@ def explore_dir(cur_dir: Path) -> List[FileInfo]:
|
|||
matching_file_info = files.get(matching_file)
|
||||
|
||||
if matching_file_info is None:
|
||||
logger.warning(style_warning("No corresponding file for header file ") + style_path(header_file))
|
||||
logger.warning(
|
||||
style_warning("No corresponding file for header file ") +
|
||||
style_path(header_file)
|
||||
)
|
||||
else:
|
||||
logger.debug(f"Assigned header file {style_path(header_file)} to file {style_path(matching_file)}")
|
||||
logger.debug(f"Assigned header file {style_path(header_file)} to "
|
||||
f"file {style_path(matching_file)}")
|
||||
matching_file_info.header = header_file
|
||||
|
||||
# 3. Collect the resulting FileInfos
|
||||
|
|
@ -64,6 +73,7 @@ def explore_dir(cur_dir: Path) -> List[FileInfo]:
|
|||
try:
|
||||
result.extend(explore_dir(subdir))
|
||||
except OSError as e:
|
||||
logger.warning(style_warning("Could not descend into folder ") + style_path(subdir) + f": {e}")
|
||||
logger.warning(style_warning("Could not descend into folder ") +
|
||||
style_path(subdir) + f": {e}")
|
||||
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Set
|
||||
from typing import Dict, Optional, Set
|
||||
|
||||
from .colors import *
|
||||
from .util import *
|
||||
from .colors import style_error, style_path
|
||||
from .util import CatastrophicError, WriteFileException, write_file
|
||||
|
||||
__all__ = ["KnownFiles"]
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class KnownFiles:
|
||||
def __init__(self, path: Path) -> None:
|
||||
self._path = path
|
||||
|
|
@ -18,7 +19,7 @@ class KnownFiles:
|
|||
try:
|
||||
with open(self._path) as f:
|
||||
self._old_known_files = self._read_known_files(f.read())
|
||||
except FileNotFoundError as e:
|
||||
except FileNotFoundError:
|
||||
logger.debug(f"File {style_path(self._path)} does not exist, "
|
||||
"creating a new file on the first upcoming save")
|
||||
|
||||
|
|
@ -30,13 +31,16 @@ class KnownFiles:
|
|||
raw_known_files = json.loads(text)
|
||||
|
||||
if not isinstance(raw_known_files, dict):
|
||||
raise CatastrophicError(style_error("Root level structure is not a dictionary"))
|
||||
raise CatastrophicError(style_error(
|
||||
"Root level structure is not a dictionary"))
|
||||
|
||||
for path, file_hash in raw_known_files.items():
|
||||
if not isinstance(path, str):
|
||||
raise CatastrophicError(style_error(f"Path {path!r} is not a string"))
|
||||
raise CatastrophicError(style_error(
|
||||
f"Path {path!r} is not a string"))
|
||||
if not isinstance(file_hash, str):
|
||||
raise CatastrophicError(style_error(f"Hash {hash!r} at path {path!r} is not a string"))
|
||||
raise CatastrophicError(style_error(
|
||||
f"Hash {hash!r} at path {path!r} is not a string"))
|
||||
|
||||
path = self._normalize_path(Path(path))
|
||||
known_files[path] = file_hash
|
||||
|
|
@ -61,7 +65,8 @@ class KnownFiles:
|
|||
|
||||
def save_incremental(self) -> None:
|
||||
to_save: Dict[str, str] = {}
|
||||
for path in self._old_known_files.keys() | self._new_known_files.keys():
|
||||
paths = self._old_known_files.keys() | self._new_known_files.keys()
|
||||
for path in paths:
|
||||
if path in self._new_known_files:
|
||||
to_save[str(path)] = self._new_known_files[path]
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from abc import ABC
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from .util import *
|
||||
from .util import safer_eval
|
||||
|
||||
"""
|
||||
This parsing solution has the following structure:
|
||||
|
|
@ -9,7 +9,7 @@ This parsing solution has the following structure:
|
|||
1. Separate header and config file content, if necessary
|
||||
2. Split up text into lines, if still necessary
|
||||
3. Parse each line individually
|
||||
4. Use a recursive descent approach to group the lines into blocks and if-blocks
|
||||
4. Use recursive descent approach to group the lines into blocks and if-blocks
|
||||
5. Evaluate the blocks recursively
|
||||
"""
|
||||
|
||||
|
|
@ -18,6 +18,7 @@ __all__ = [
|
|||
"ParseException", "Parser",
|
||||
]
|
||||
|
||||
|
||||
def split_header_and_rest(text: str) -> Tuple[List[str], List[str]]:
|
||||
lines = text.splitlines()
|
||||
|
||||
|
|
@ -38,11 +39,13 @@ def split_header_and_rest(text: str) -> Tuple[List[str], List[str]]:
|
|||
|
||||
return header, rest
|
||||
|
||||
|
||||
class ParseException(Exception):
|
||||
@classmethod
|
||||
def on_line(cls, line: "Line", text: str) -> "ParseException":
|
||||
return ParseException(f"Line {line.line_number}: {text}")
|
||||
|
||||
|
||||
class Parser:
|
||||
def __init__(self,
|
||||
raw_lines: List[str],
|
||||
|
|
@ -71,6 +74,7 @@ class Parser:
|
|||
lines = self.main_block.evaluate(local_vars)
|
||||
return "".join(f"{line}\n" for line in lines)
|
||||
|
||||
|
||||
# Line parsing (inline expressions)
|
||||
|
||||
class Line(ABC):
|
||||
|
|
@ -102,7 +106,10 @@ class Line(ABC):
|
|||
self.parser = parser
|
||||
self.line_number = line_number
|
||||
|
||||
def _parse_statement(self, text: str, statement_name: str) -> Optional[str]:
|
||||
def _parse_statement(self,
|
||||
text: str,
|
||||
statement_name: str
|
||||
) -> Optional[str]:
|
||||
start = f"{self.parser.statement_prefix} {statement_name}"
|
||||
text = text.strip()
|
||||
if text.startswith(start):
|
||||
|
|
@ -111,7 +118,9 @@ class Line(ABC):
|
|||
return None
|
||||
|
||||
def _parse_statement_noarg(self, text: str, statement_name: str) -> bool:
|
||||
return text.strip() == f"{self.parser.statement_prefix} {statement_name}"
|
||||
target = f"{self.parser.statement_prefix} {statement_name}"
|
||||
return text.strip() == target
|
||||
|
||||
|
||||
class ActualLine(Line):
|
||||
def __init__(self, parser: Parser, text: str, line_number: int) -> None:
|
||||
|
|
@ -149,7 +158,11 @@ class ActualLine(Line):
|
|||
# Find expression suffix
|
||||
cd = text.find(self.parser.expression_suffix, od_end)
|
||||
if cd == -1:
|
||||
raise ParseException.on_line(self, f"No matching expression suffix\n{text[:od_end]} <-- to THIS expression prefix")
|
||||
raise ParseException.on_line(
|
||||
self,
|
||||
f"No matching expression suffix\n{text[:od_end]} "
|
||||
"<-- to THIS expression prefix"
|
||||
)
|
||||
cd_end = cd + len(self.parser.expression_suffix)
|
||||
|
||||
# Split up into chunks
|
||||
|
|
@ -164,7 +177,8 @@ class ActualLine(Line):
|
|||
May raise: ExecuteException
|
||||
"""
|
||||
|
||||
return "".join(self._evaluate_chunk(chunk, local_vars) for chunk in self.chunks)
|
||||
return "".join(self._evaluate_chunk(chunk, local_vars)
|
||||
for chunk in self.chunks)
|
||||
|
||||
def _evaluate_chunk(self,
|
||||
chunk: Tuple[str, bool],
|
||||
|
|
@ -179,6 +193,7 @@ class ActualLine(Line):
|
|||
|
||||
return str(safer_eval(chunk[0], local_vars))
|
||||
|
||||
|
||||
class IfStatement(Line):
|
||||
def __init__(self, parser: Parser, text: str, line_number: int) -> None:
|
||||
"""
|
||||
|
|
@ -191,6 +206,7 @@ class IfStatement(Line):
|
|||
if self.argument is None:
|
||||
raise ParseException.on_line(self, "Not an 'if' statement")
|
||||
|
||||
|
||||
class ElifStatement(Line):
|
||||
def __init__(self, parser: Parser, text: str, line_number: int) -> None:
|
||||
"""
|
||||
|
|
@ -203,6 +219,7 @@ class ElifStatement(Line):
|
|||
if self.argument is None:
|
||||
raise ParseException.on_line(self, "Not an 'elif' statement")
|
||||
|
||||
|
||||
class ElseStatement(Line):
|
||||
def __init__(self, parser: Parser, text: str, line_number: int) -> None:
|
||||
"""
|
||||
|
|
@ -214,6 +231,7 @@ class ElseStatement(Line):
|
|||
if not self._parse_statement_noarg(text, "else"):
|
||||
raise ParseException.on_line(self, "Not an 'else' statement")
|
||||
|
||||
|
||||
class EndifStatement(Line):
|
||||
def __init__(self, parser: Parser, text: str, line_number: int) -> None:
|
||||
"""
|
||||
|
|
@ -225,6 +243,7 @@ class EndifStatement(Line):
|
|||
if not self._parse_statement_noarg(text, "endif"):
|
||||
raise ParseException.on_line(self, "Not an 'endif' statement")
|
||||
|
||||
|
||||
# Block parsing
|
||||
|
||||
class Block:
|
||||
|
|
@ -259,6 +278,7 @@ class Block:
|
|||
|
||||
return lines
|
||||
|
||||
|
||||
class IfBlock(Block):
|
||||
def __init__(self, parser: Parser, lines_queue: List[Line]) -> None:
|
||||
"""
|
||||
|
|
@ -268,18 +288,19 @@ class IfBlock(Block):
|
|||
self._sections: List[Tuple[Block, Optional[str]]] = []
|
||||
|
||||
if not lines_queue:
|
||||
raise ParseException("Unexpected end of file, expected 'if' statement")
|
||||
raise ParseException("Unexpected end of file, expected 'if' "
|
||||
"statement")
|
||||
|
||||
# If statement
|
||||
#
|
||||
# This is the short version:
|
||||
# if not isinstance(lines_queue[-1], IfStatement): # This should never happen
|
||||
# if not isinstance(lines_queue[-1], IfStatement): # Should never happen
|
||||
# raise ParseException.on_line(lines_queue[-1], "Expected 'if' statement")
|
||||
# self._sections.append((Block(parser, lines_queue), lines_queue.pop().argument))
|
||||
#
|
||||
# And this the long version, which mypy understands without errors:
|
||||
next_statement = lines_queue[-1]
|
||||
if not isinstance(next_statement, IfStatement): # This should never happen
|
||||
if not isinstance(next_statement, IfStatement): # Should never happen
|
||||
raise ParseException.on_line(next_statement, "Expected 'if' statement")
|
||||
lines_queue.pop()
|
||||
self._sections.append((Block(parser, lines_queue), next_statement.argument))
|
||||
|
|
|
|||
|
|
@ -4,22 +4,27 @@ import shutil
|
|||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
from .colors import *
|
||||
from .config import *
|
||||
from .known_files import *
|
||||
from .parser import *
|
||||
from .prompt import *
|
||||
from .util import *
|
||||
from .colors import style_error, style_path, style_warning
|
||||
from .config import Config
|
||||
from .known_files import KnownFiles
|
||||
from .parser import ParseException, Parser, split_header_and_rest
|
||||
from .prompt import prompt_yes_no
|
||||
from .util import (ExecuteException, LessCatastrophicError, ReadFileException,
|
||||
WriteFileException, read_file, safer_exec, write_file)
|
||||
|
||||
__all__ = ["Processor"]
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Processor:
|
||||
def __init__(self, config: Config, known_files: KnownFiles) -> None:
|
||||
self.config = config
|
||||
self.known_files = known_files
|
||||
|
||||
def process_file(self, path: Path, header_path: Optional[Path] = None) -> None:
|
||||
def process_file(self,
|
||||
path: Path,
|
||||
header_path: Optional[Path] = None
|
||||
) -> None:
|
||||
logger.info(f"{style_path(path)}:")
|
||||
|
||||
config = self.config.copy()
|
||||
|
|
@ -51,7 +56,11 @@ class Processor:
|
|||
|
||||
self._process_parseable(lines, config, path)
|
||||
|
||||
def _process_file_with_header(self, path: Path, header_path: Path, config: Config) -> None:
|
||||
def _process_file_with_header(self,
|
||||
path: Path,
|
||||
header_path: Path,
|
||||
config: Config
|
||||
) -> None:
|
||||
logger.debug(f"Processing file {style_path(path)} "
|
||||
f"with header {style_path(header_path)}")
|
||||
|
||||
|
|
@ -80,7 +89,7 @@ class Processor:
|
|||
self._process_parseable(lines, config, path)
|
||||
|
||||
def _process_binary(self, path: Path, config: Config) -> None:
|
||||
logger.debug(f"Processing as a binary file")
|
||||
logger.debug("Processing as a binary file")
|
||||
|
||||
if not config.targets:
|
||||
logger.info(" (no targets)")
|
||||
|
|
@ -96,7 +105,10 @@ class Processor:
|
|||
try:
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
except IOError as e:
|
||||
logger.warning(style_warning("Could not create target directory") + f": {e}")
|
||||
logger.warning(
|
||||
style_warning("Could not create target directory") +
|
||||
f": {e}"
|
||||
)
|
||||
continue
|
||||
|
||||
try:
|
||||
|
|
@ -108,11 +120,16 @@ class Processor:
|
|||
try:
|
||||
shutil.copymode(path, target)
|
||||
except shutil.Error as e:
|
||||
logger.warning(style_warning("Could not copy permissions") + f": {e}")
|
||||
logger.warning(style_warning("Could not copy permissions") +
|
||||
f": {e}")
|
||||
|
||||
self._update_known_hash(target)
|
||||
|
||||
def _process_parseable(self, lines: List[str], config: Config, source: Path) -> None:
|
||||
def _process_parseable(self,
|
||||
lines: List[str],
|
||||
config: Config,
|
||||
source: Path
|
||||
) -> None:
|
||||
if not config.targets:
|
||||
logger.info(" (no targets)")
|
||||
return
|
||||
|
|
@ -149,19 +166,24 @@ class Processor:
|
|||
try:
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
except IOError as e:
|
||||
logger.warning(style_warning("Could not create target directory") + f": {e}")
|
||||
logger.warning(
|
||||
style_warning("Could not create target directory") +
|
||||
f": {e}"
|
||||
)
|
||||
continue
|
||||
|
||||
try:
|
||||
write_file(target, text)
|
||||
except WriteFileException as e:
|
||||
logger.warning(style_warning("Could not write to target") + f": {e}")
|
||||
logger.warning(style_warning("Could not write to target") +
|
||||
f": {e}")
|
||||
continue
|
||||
|
||||
try:
|
||||
shutil.copymode(source, target)
|
||||
except shutil.Error as e:
|
||||
logger.warning(style_warning("Could not copy permissions") + f": {e}")
|
||||
logger.warning(style_warning("Could not copy permissions") +
|
||||
f": {e}")
|
||||
|
||||
self._update_known_hash(target)
|
||||
|
||||
|
|
@ -174,7 +196,8 @@ class Processor:
|
|||
with open(path, "rb") as f:
|
||||
while True:
|
||||
block = f.read(BLOCK_SIZE)
|
||||
if not block: break
|
||||
if not block:
|
||||
break
|
||||
h.update(block)
|
||||
|
||||
return h.hexdigest()
|
||||
|
|
@ -191,16 +214,19 @@ class Processor:
|
|||
return False
|
||||
|
||||
if self.known_files.was_recently_modified(target):
|
||||
logger.warning(style_warning("This target was already overwritten earlier"))
|
||||
logger.warning(style_warning("This target was already overwritten "
|
||||
"earlier"))
|
||||
return False
|
||||
|
||||
target_hash = self._obtain_hash(target)
|
||||
if target_hash is None:
|
||||
return prompt_yes_no("Overwriting a file that could not be hashed, continue?", False)
|
||||
return prompt_yes_no("Overwriting a file that could not be "
|
||||
"hashed, continue?", False)
|
||||
|
||||
known_target_hash = self.known_files.get_hash(target)
|
||||
if known_target_hash is None:
|
||||
return prompt_yes_no("Overwriting an unknown file, continue?", False)
|
||||
return prompt_yes_no("Overwriting an unknown file, continue?",
|
||||
False)
|
||||
|
||||
# The following condition is phrased awkwardly because I just
|
||||
# feel better if the final statement in this function is not a
|
||||
|
|
@ -212,7 +238,8 @@ class Processor:
|
|||
# last seen it.
|
||||
return True
|
||||
|
||||
return prompt_yes_no("Overwriting a file that was modified since it was last overwritten, continue?", False)
|
||||
return prompt_yes_no("Overwriting a file that was modified since it "
|
||||
"was last overwritten, continue?", False)
|
||||
|
||||
def _update_known_hash(self, target: Path) -> None:
|
||||
target_hash = self._obtain_hash(target)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from typing import Optional
|
|||
|
||||
__all__ = ["prompt_choice", "prompt_yes_no"]
|
||||
|
||||
|
||||
def prompt_choice(question: str, options: str) -> str:
|
||||
default_option = None
|
||||
for char in options:
|
||||
|
|
@ -22,6 +23,7 @@ def prompt_choice(question: str, options: str) -> str:
|
|||
else:
|
||||
print(f"Invalid answer, please choose one of [{option_string}].")
|
||||
|
||||
|
||||
def prompt_yes_no(question: str, default_answer: Optional[bool]) -> bool:
|
||||
if default_answer is None:
|
||||
options = "yn"
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ __all__ = [
|
|||
"CatastrophicError", "LessCatastrophicError",
|
||||
]
|
||||
|
||||
|
||||
def copy_local_variables(local: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Attempts to deep-copy a set of local variables, but keeping
|
||||
|
|
@ -33,15 +34,19 @@ def copy_local_variables(local: Dict[str, Any]) -> Dict[str, Any]:
|
|||
|
||||
return local_copy
|
||||
|
||||
|
||||
def get_user() -> str:
|
||||
return getpass.getuser()
|
||||
|
||||
|
||||
def get_host() -> str:
|
||||
return socket.gethostname()
|
||||
|
||||
|
||||
class ExecuteException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def safer_exec(code: str, local_vars: Dict[str, Any]) -> None:
|
||||
"""
|
||||
May raise: ExecuteException
|
||||
|
|
@ -52,6 +57,7 @@ def safer_exec(code: str, local_vars: Dict[str, Any]) -> None:
|
|||
except Exception as e:
|
||||
raise ExecuteException(e)
|
||||
|
||||
|
||||
def safer_eval(code: str, local_vars: Dict[str, Any]) -> Any:
|
||||
"""
|
||||
May raise: ExecuteException
|
||||
|
|
@ -62,9 +68,11 @@ def safer_eval(code: str, local_vars: Dict[str, Any]) -> Any:
|
|||
except Exception as e:
|
||||
raise ExecuteException(e)
|
||||
|
||||
|
||||
class ReadFileException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def read_file(path: Path) -> str:
|
||||
"""
|
||||
May raise: ReadFileException
|
||||
|
|
@ -76,9 +84,11 @@ def read_file(path: Path) -> str:
|
|||
except OSError as e:
|
||||
raise ReadFileException(e)
|
||||
|
||||
|
||||
class WriteFileException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def write_file(path: Path, text: str) -> None:
|
||||
"""
|
||||
May raise: WriteFileException
|
||||
|
|
@ -90,8 +100,10 @@ def write_file(path: Path, text: str) -> None:
|
|||
except OSError as e:
|
||||
raise WriteFileException(e)
|
||||
|
||||
|
||||
class CatastrophicError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LessCatastrophicError(Exception):
|
||||
pass
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue