diff --git a/README.md b/README.md index 78e7a5a..a11c409 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,9 @@ python-hosts [![codecov](https://codecov.io/gh/jonhadfield/python-hosts/branch/devel/graph/badge.svg)](https://codecov.io/gh/jonhadfield/python-hosts) [![Docs](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](http://python-hosts.readthedocs.org/en/latest/) -This is a python library for managing a hosts file. +This is a python library for managing a hosts file. It enables you to add and remove entries, or import them from a file or URL. +It remains compatible with Python 2.7 as well as modern Python 3 releases. Documentation ------------- @@ -42,7 +43,7 @@ A command line client using python-hosts can be found here: https://github.com/j Requirements ------------ -Tested on python 2.7, 3.5, 3.6, 3.7, 3.8, 3.9, pypy and pypy3 +Tested on Python 2.7 and Python 3.5+, including PyPy variants License diff --git a/python_hosts/hosts.py b/python_hosts/hosts.py index a5b7a49..1be9b26 100755 --- a/python_hosts/hosts.py +++ b/python_hosts/hosts.py @@ -118,24 +118,16 @@ def str_to_hostentry(entry): :return: An instance of HostsEntry """ split_line = entry.split('#', 1) - inline_comment = None - if len(split_line) == 2: - inline_comment = split_line[1].strip() - line_parts = split_line[0].strip().split() - else: - line_parts = entry.strip().split() - if is_ipv4(line_parts[0]) and valid_hostnames(line_parts[1:]): - return HostsEntry(entry_type='ipv4', - address=line_parts[0], - names=line_parts[1:], + line = split_line[0].strip().split() + inline_comment = split_line[1].strip() if len(split_line) == 2 else None + + if is_ipv4(line[0]) and valid_hostnames(line[1:]): + return HostsEntry('ipv4', address=line[0], names=line[1:], comment=inline_comment) - elif is_ipv6(line_parts[0]) and valid_hostnames(line_parts[1:]): - return HostsEntry(entry_type='ipv6', - address=line_parts[0], - names=line_parts[1:], + if is_ipv6(line[0]) and valid_hostnames(line[1:]): + return HostsEntry('ipv6', address=line[0], names=line[1:], comment=inline_comment) - else: - return False + return False class Hosts(object): @@ -187,11 +179,16 @@ def determine_hosts_path(platform=None): """ if not platform: platform = sys.platform - if platform.startswith('win'): - result = r"c:\windows\system32\drivers\etc\hosts" - return result - else: - return '/etc/hosts' + + paths = { + 'win': r"c:\windows\system32\drivers\etc\hosts", + 'default': '/etc/hosts' + } + + for key, value in paths.items(): + if key != 'default' and platform.startswith(key): + return value + return paths['default'] def write(self, path=None, mode='w'): """ @@ -267,12 +264,8 @@ def exists(self, address=None, names=None, comment=None): if self.find_all_matching(address=address, name=name, comment=comment): return True - for entry in self.entries: - if entry.entry_type == 'comment' and entry.comment == comment: - return True - # elif entry.entry_type in ('ipv4', 'ipv6'): - # pass # already covered above - return False + return any(entry.entry_type == 'comment' and entry.comment == comment + for entry in self.entries) def remove_all_matching(self, address=None, name=None, comment=None): """ @@ -304,22 +297,14 @@ def find_all_matching(self, address=None, name=None, comment=None): :param comment: A host inline comment :return: HostEntry instances """ - results = [] - if address or name or comment: - for entry in self.entries: - if not entry.is_real_entry(): - continue - if address: - if address != entry.address: - continue - if name: - if name not in entry.names: - continue - if comment: - if comment != entry.comment: - continue - results.append(entry) - return results + if not any((address, name, comment)): + return [] + + return [entry for entry in self.entries + if entry.is_real_entry() + and (address is None or entry.address == address) + and (name is None or name in entry.names) + and (comment is None or entry.comment == comment)] def import_url(self, url=None, force=None): """ diff --git a/tests/test_utils.py b/tests/test_utils.py index d979a3e..a9b200f 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,7 +2,7 @@ import os import sys -from python_hosts.utils import is_ipv4, is_ipv6, valid_hostnames +from python_hosts.utils import is_ipv4, is_ipv6, valid_hostnames, dedupe_list, is_readable sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) @@ -52,3 +52,17 @@ def test_hostname_validation_failure_with_leading_hyphen(): Test function returns False if a hostname with a leading hyphen is specified """ assert not valid_hostnames(['example.com', '-example']) + + +def test_dedupe_list_preserves_order(): + """Ensure dedupe_list removes duplicates while keeping order.""" + items = ['a', 'b', 'a', 'c', 'b'] + assert dedupe_list(items) == ['a', 'b', 'c'] + + +def test_is_readable(tmpdir): + """Check is_readable returns expected values.""" + readable = tmpdir.join('file') + readable.write('data') + assert is_readable(readable.strpath) + assert not is_readable(readable.strpath + '_missing')