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