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\$
  • \$\begingroup\$ You probably want to fix the syntax error in f-string(format_line). \$\endgroup\$ – Ashwini Chaudhary Aug 2 '17 at 14:18
  • \$\begingroup\$ @AshwiniChaudhary could you be more specific about the syntax error? \$\endgroup\$ – Corey Leavitt Aug 2 '17 at 14:25
  • \$\begingroup\$ You could run it and check. \$\endgroup\$ – Ashwini Chaudhary Aug 2 '17 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\$ – Corey Leavitt Aug 2 '17 at 14:27
  • \$\begingroup\$ Yes, I am on 3.6.2. 3.6.1 -> repl.it/Jtvf \$\endgroup\$ – Ashwini Chaudhary Aug 2 '17 at 14:30
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, privacy policy and cookie policy

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