5
\$\begingroup\$

I know I can use the unix column function to replicate 90% of the functionality here, so this is mostly just to practice python. I find a misaligned fstab file really hard to read. This script parses the file and aligns the columns by determining the longest entry in each column and then padding each entry in the column to that length (plus an offset). Newlines are passed over and comment lines (starting with #) are formatted to ensure there is a space between the first '#' and the comment content.

Is there anything I'm missing that could make this code better or more readable?

from pathlib import Path
from typing import NamedTuple

FSTAB = Path('/etc/fstab')
OFFSET = 4 # number of spaces desired between columns

class FstabLine(NamedTuple):
    device_spec: str
    mount_point: str
    fs_type: str
    options: str
    dump: int
    pass_: int


def format_line(len_dict: dict, line: FstabLine) -> str:
    return (
        f"{line.device_spec:{len_dict['device_spec']}}"
        f"{line.mount_point:{len_dict['mount_point']}}"
        f"{line.fs_type:{len_dict['fs_type']}}"
        f"{line.options:{len_dict['options']}}{line.dump:{len_dict['dump']}}"
        f"{line.pass_}\n"
    )

def get_longest(lines: list) -> dict:
    d = {}
    for field in FstabLine._fields:
        if field == 'pass_':
            continue

        d[field] = max(
            [len(getattr(l, field)) for l in lines if isinstance(l, FstabLine)]
        ) + OFFSET

    return d

def parse(content: list) -> list:
    parsed_content = []
    for line in content:
        if line.startswith('#'):
            parsed_content.append(f'# {line.lstrip("#").strip()}\n')
        elif line.startswith('\n'):
            parsed_content.append(line)
        else:
            parsed_content.append(FstabLine(*line.split(maxsplit=6)))

    return parsed_content

def main() -> None:
    with FSTAB.open() as f:
        lines = f.readlines()

    lines = parse(lines)

    len_dict = get_longest(lines)

    with FSTAB.open('w+') as f:
        for line in lines:
            if isinstance(line, FstabLine):
                f.write(format_line(len_dict, line))
            else:
                f.write(line)

if __name__ == '__main__':
    main()
\$\endgroup\$
6
  • \$\begingroup\$ You probably want to fix the syntax error in f-string(format_line). \$\endgroup\$ Commented Aug 2, 2017 at 14:18
  • \$\begingroup\$ @AshwiniChaudhary could you be more specific about the syntax error? \$\endgroup\$ Commented Aug 2, 2017 at 14:25
  • \$\begingroup\$ You could run it and check. \$\endgroup\$ Commented Aug 2, 2017 at 14:25
  • \$\begingroup\$ @AshwiniChaudhary are you using python 3.6? The code runs fine and the fstab file returned is formatted correctly that's why I'm asking you to be more specific. \$\endgroup\$ Commented Aug 2, 2017 at 14:27
  • \$\begingroup\$ Yes, I am on 3.6.2. 3.6.1 -> repl.it/Jtvf \$\endgroup\$ Commented Aug 2, 2017 at 14:30

1 Answer 1

2
\$\begingroup\$

The type hints you're using for containers are too generic .i.e list, dict etc. You could make them more helpful using the types present in typing module.

from typing import Dict, List, Union


def format_line(len_dict: Dict[str, str], line: FstabLine) -> str:
def get_longest(lines: List[str]) -> Dict[str, str]:
def parse(content: List[str]) -> List[Union[str, FstabLine]]:

This could also be done for variables like parsed_content as well because Mypy currently throws error for it:

error: Argument 1 to "append" of "list" has incompatible type "FstabLine"; expected "str"

parsed_content: List[Union[str, FstabLine]] = []
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.