Example #1
0
def load_tags():
    """Load the tags from disk.

    Return a dictionary which maps tag names to sub-dictionaries, which in
    turn maps fully expanded paths to unexpanded paths like this:

    {
        "@app" : {
            "/home/user/app/backend": "~/app/backend",
            "/home/user/app/frontend": "~/app/frontend",
        },
        "@frontend": {
            "/home/user/app/frontend": "~/app/frontend",
        },
        "@backend": {
            "/home/user/app/backend": "~/app/backend",
        }
        ...
    }

    :return: the tags loaded from the disk
    """
    if not os.path.exists(TAGS_FILE_PATH):
        try:
            with open(TAGS_FILE_PATH, 'w') as open_file:
                json.dump({}, open_file)
            return {}
        except (IOError, OSError):
            halt('Failed to initialize {}'.format(TAGS_FILE_PATH))
    else:
        try:
            with open(TAGS_FILE_PATH, 'r') as open_file:
                json_str = open_file.read().strip()
                if not json_str:
                    return {}
                tag_data = json.loads(json_str)
                if not tag_data:
                    return {}
                else:
                    return {
                        tag: {expand_path(path): path
                              for path in paths}
                        for tag, paths in tag_data.items()
                    }
        except (ValueError, IOError, OSError):
            # TODO better error handling and messaging here
            halt('Failed to load {}'.format(TAGS_FILE_PATH))
Example #2
0
def load_tags():
    """Load the tags from disk.

    Return a dictionary which maps tag names to sub-dictionaries, which in
    turn maps fully expanded paths to unexpanded paths like this:

    {
        "@app" : {
            "/home/user/app/backend": "~/app/backend",
            "/home/user/app/frontend": "~/app/frontend",
        },
        "@frontend": {
            "/home/user/app/frontend": "~/app/frontend",
        },
        "@backend": {
            "/home/user/app/backend": "~/app/backend",
        }
        ...
    }

    :return: the tags loaded from the disk
    """
    if not os.path.exists(TAGS_FILE_PATH):
        try:
            with open(TAGS_FILE_PATH, 'w') as open_file:
                json.dump({}, open_file)
            return {}
        except (IOError, OSError):
            halt('Failed to initialize {}'.format(TAGS_FILE_PATH))
    else:
        try:
            with open(TAGS_FILE_PATH, 'r') as open_file:
                json_str = open_file.read().strip()
                if not json_str:
                    return {}
                tag_data = json.loads(json_str)
                if not tag_data:
                    return {}
                else:
                    return {
                        tag: {expand_path(path): path for path in paths}
                        for tag, paths in tag_data.items()
                    }
        except (ValueError, IOError, OSError):
            # TODO better error handling and messaging here
            halt('Failed to load {}'.format(TAGS_FILE_PATH))
Example #3
0
def save_tags(tags):
    """Save the tags to disk.

    Convert the incoming tags dictionary to JSON and dump the content to the
    file. For portability, only the unexpanded paths are saved.

    :param tags: tags to save to disk
    """
    try:
        with open(TEMP_FILE_PATH, 'w') as open_file:
            json.dump(
                {tag: sorted(paths.values()) for tag, paths in tags.items()},
                open_file,
                indent=4,
                sort_keys=True
            )
    except IOError:
        # TODO better error handling and messaging here
        halt('Failed to write to {}'.format(TAGS_FILE_PATH))
    else:
        # Overwrite the old file with the new
        os.rename(TEMP_FILE_PATH, TAGS_FILE_PATH)
Example #4
0
def save_tags(tags):
    """Save the tags to disk.

    Convert the incoming tags dictionary to JSON and dump the content to the
    file. For portability, only the unexpanded paths are saved.

    :param tags: tags to save to disk
    """
    try:
        with open(TEMP_FILE_PATH, 'w') as open_file:
            json.dump(
                {tag: sorted(paths.values())
                 for tag, paths in tags.items()},
                open_file,
                indent=4,
                sort_keys=True)
    except IOError:
        # TODO better error handling and messaging here
        halt('Failed to write to {}'.format(TAGS_FILE_PATH))
    else:
        # Overwrite the old file with the new
        os.rename(TEMP_FILE_PATH, TAGS_FILE_PATH)
Example #5
0
File: tags.py Project: nkhuyu/dtags
def main():
    tag_to_paths = load_tags()
    path_to_tags = defaultdict(set)
    for tag, paths in tag_to_paths.items():
        for path in paths.keys():
            path_to_tags[path].add(tag)

    parser = ArgumentParser(
        prog='tags',
        description=cmd_description,
        usage='tags [options] [paths] [tags]',
        formatter_class=HelpFormatter
    )
    parser.add_argument(
        '-e', '--edit',
        action='store_true',
        help='Edit the tags directly using an editor'
    )
    parser.add_argument(
        '-x', '--expand',
        action='store_true',
        help='Expand the directory paths'
    )
    parser.add_argument(
        '-r', '--reverse',
        help='display the reverse mapping',
        action='store_true'
    )
    parser.add_argument(
        '-j', '--json',
        help='display the raw JSON',
        action='store_true'
    )
    parser.add_argument(
        'search_terms',
        type=str,
        nargs='*',
        metavar='[paths] [tags]',
        help='tag and directory paths to filter by'
    ).completer = ChoicesCompleter(tag_to_paths.keys())
    autocomplete(parser)
    parsed = parser.parse_args()

    if parsed.edit:
        if parsed.search_terms:
            raise parser.error('no arguments allowed with option -e/--edit')
        edit_file_path = TAGS_FILE_PATH + '.edit'
        shutil.copy2(TAGS_FILE_PATH, edit_file_path)
        subprocess.call([os.environ.get('EDITOR', 'vi'), edit_file_path])
        new_tag_to_paths = save_error = None
        with open(edit_file_path, 'r') as edit_file:
            try:
                new_tag_to_paths = json.load(edit_file)
            except ValueError as err:
                save_error = 'Failed to save tags: {}'.format(msgify(err))
            else:
                try:
                    check_tags(new_tag_to_paths)
                except ValueError as err:
                    save_error = 'Failed to save tags: {}'.format(err)
        safe_remove(edit_file_path)
        if save_error is not None:
            halt(save_error)
        save_tags({
            tag: {expand_path(p): shrink_path(p) for p in paths}
            for tag, paths in new_tag_to_paths.items()
        })
        print('New tags saved successfully')
        sys.exit(0)

    if len(tag_to_paths) == 0:
        print('No tags defined')
        sys.exit(0)

    # Filter by any given tags and paths
    # TODO optimize here if possible
    if not parsed.search_terms:
        if parsed.expand:
            filtered = {t: ps.keys() for t, ps in tag_to_paths.items()}
        else:
            filtered = {t: ps.values() for t, ps in tag_to_paths.items()}
    else:
        filtered = {}
        for term in parsed.search_terms:
            if term in tag_to_paths:
                if parsed.expand:
                    filtered[term] = tag_to_paths[term].keys()
                else:
                    filtered[term] = tag_to_paths[term].values()
            elif os.path.isdir(term):
                term = expand_path(term)
                if term in path_to_tags:
                    for tag in path_to_tags[term]:
                        if parsed.expand:
                            filtered[tag] = tag_to_paths[tag].keys()
                        else:
                            filtered[tag] = tag_to_paths[tag].values()

    if parsed.json:
        formatted = {tag: sorted(paths) for tag, paths in filtered.items()}
        print(json.dumps(formatted, sort_keys=True, indent=4))
    elif parsed.reverse:
        reverse = defaultdict(set)
        for tag, paths in filtered.items():
            for path in paths:
                reverse[path].add(tag)
        for path, tags in reverse.items():
            print('{}{}{}'.format(CYAN, path, CLEAR))
            print('{}{}{}\n'.format(PINK, ' '.join(sorted(tags)), CLEAR))
    else:
        for tag, paths in sorted(filtered.items()):
            print('{}{}{}'.format(PINK, tag, CLEAR))
            print('{}{}{}\n'.format(CYAN, '\n'.join(sorted(paths)), CLEAR))
Example #6
0
def main():
    tag_to_paths = load_tags()
    path_to_tags = defaultdict(set)
    for tag, paths in tag_to_paths.items():
        for path in paths.keys():
            path_to_tags[path].add(tag)

    parser = ArgumentParser(prog='tags',
                            description=cmd_description,
                            usage='tags [options] [paths] [tags]',
                            formatter_class=HelpFormatter)
    parser.add_argument('-e',
                        '--edit',
                        action='store_true',
                        help='Edit the tags directly using an editor')
    parser.add_argument('-x',
                        '--expand',
                        action='store_true',
                        help='Expand the directory paths')
    parser.add_argument('-r',
                        '--reverse',
                        help='display the reverse mapping',
                        action='store_true')
    parser.add_argument('-j',
                        '--json',
                        help='display the raw JSON',
                        action='store_true')
    parser.add_argument('search_terms',
                        type=str,
                        nargs='*',
                        metavar='[paths] [tags]',
                        help='tag and directory paths to filter by'
                        ).completer = ChoicesCompleter(tag_to_paths.keys())
    autocomplete(parser)
    parsed = parser.parse_args()

    if parsed.edit:
        if parsed.search_terms:
            raise parser.error('no arguments allowed with option -e/--edit')
        edit_file_path = TAGS_FILE_PATH + '.edit'
        shutil.copy2(TAGS_FILE_PATH, edit_file_path)
        subprocess.call([os.environ.get('EDITOR', 'vi'), edit_file_path])
        new_tag_to_paths = save_error = None
        with open(edit_file_path, 'r') as edit_file:
            try:
                new_tag_to_paths = json.load(edit_file)
            except ValueError as err:
                save_error = 'Failed to save tags: {}'.format(msgify(err))
            else:
                try:
                    check_tags(new_tag_to_paths)
                except ValueError as err:
                    save_error = 'Failed to save tags: {}'.format(err)
        safe_remove(edit_file_path)
        if save_error is not None:
            halt(save_error)
        save_tags({
            tag: {expand_path(p): shrink_path(p)
                  for p in paths}
            for tag, paths in new_tag_to_paths.items()
        })
        print('New tags saved successfully')
        sys.exit(0)

    if len(tag_to_paths) == 0:
        print('No tags defined')
        sys.exit(0)

    # Filter by any given tags and paths
    # TODO optimize here if possible
    if not parsed.search_terms:
        if parsed.expand:
            filtered = {t: ps.keys() for t, ps in tag_to_paths.items()}
        else:
            filtered = {t: ps.values() for t, ps in tag_to_paths.items()}
    else:
        filtered = {}
        for term in parsed.search_terms:
            if term in tag_to_paths:
                if parsed.expand:
                    filtered[term] = tag_to_paths[term].keys()
                else:
                    filtered[term] = tag_to_paths[term].values()
            elif os.path.isdir(term):
                term = expand_path(term)
                if term in path_to_tags:
                    for tag in path_to_tags[term]:
                        if parsed.expand:
                            filtered[tag] = tag_to_paths[tag].keys()
                        else:
                            filtered[tag] = tag_to_paths[tag].values()

    if parsed.json:
        formatted = {tag: sorted(paths) for tag, paths in filtered.items()}
        print(json.dumps(formatted, sort_keys=True, indent=4))
    elif parsed.reverse:
        reverse = defaultdict(set)
        for tag, paths in filtered.items():
            for path in paths:
                reverse[path].add(tag)
        for path, tags in reverse.items():
            print('{}{}{}'.format(CYAN, path, CLEAR))
            print('{}{}{}\n'.format(PINK, ' '.join(sorted(tags)), CLEAR))
    else:
        for tag, paths in sorted(filtered.items()):
            print('{}{}{}'.format(PINK, tag, CLEAR))
            print('{}{}{}\n'.format(CYAN, '\n'.join(sorted(paths)), CLEAR))