thatsIch/sublime-rainmeter

View on GitHub
rainmeter.py

Summary

Maintainability
C
1 day
Test Coverage
"""Module for getting Rainmeter-specific paths."""

import os
import re
import filecmp
import ctypes

# own dependencies
from . import logger

from .path.program_path_provider import get_cached_program_path
from .path.setting_path_provider import get_cached_setting_path
from .path.program_drive_provider import get_cached_program_drive
from .path.plugin_path_provider import get_cached_plugin_path
from .path.addon_path_provider import get_cached_addon_path
from .path.skin_path_provider import get_cached_skin_path


def get_current_path(filepath):
    """Get the value of the #CURRENTPATH# variable for the specified path.

    Returns None if the file path is not in the skins folder
    """
    filepath = os.path.normpath(filepath)

    skinspath = get_cached_skin_path()
    if not skinspath or not filepath.startswith(skinspath):
        logger.info("current path could not be found because" +
                    " either the skins path could not be found or the current file" +
                    " is not located in the skins path.")
        return

    if os.path.isfile(filepath):
        return os.path.dirname(filepath) + "\\"
    else:
        return filepath + "\\"


def get_root_config_path(filepath):
    """Get the value of the #ROOTCONFIGPATH# variable for the specified path.

    Returns None if the path is not in the skins folder
    """
    filepath = os.path.normpath(filepath)

    skinspath = get_cached_skin_path()
    if not skinspath or not filepath.startswith(skinspath):
        logger.info("root config path could not be found" +
                    " because either the skins path could not be found or the" +
                    " current file is not located in the skins path.")
        return

    relpath = os.path.relpath(filepath, skinspath)
    logger.info(os.path.join(skinspath, relpath.split("\\")[0]) + "\\")

    return os.path.join(skinspath, relpath.split("\\")[0]) + "\\"


def get_current_file(filepath):
    """Get the value of the #CURRENTFILE# variable for the specified path.

    Returns None if the path is not in the skins folder
    """
    filepath = os.path.normpath(filepath)

    skinspath = get_cached_skin_path()
    if not skinspath or not filepath.startswith(skinspath):
        logger.info("current file could not be found because" +
                    " either the skins path could not be found or the current" +
                    " file is not located in the skins path.")
        return

    if os.path.isfile(filepath):
        return os.path.basename(filepath)
    else:
        logger.info("specified path is not a file.")
        return


def get_current_config(filepath):
    """Get the value of the #CURRENTCONFIG# variable for the specified path.

    Returns None if the path is not in the skins folder
    """
    filepath = os.path.normpath(filepath)

    skinspath = get_cached_skin_path()
    if not skinspath:
        logger.info("current config could not be found" +
                    " because the skins path could not be found.")
        return
    if not filepath.startswith(skinspath):
        logger.info("current config could not be found" +
                    " because the current file is not located in the skins path.")

        """workaround for symlinks:
        It could exists a symlink in the rainmeter skins folder.
        Rainmeter detects it correctly but uses the name of the symlink
        for the config name."""

        logger.info("trying symlink detection to find the skin config.")
        skins = os.listdir(skinspath)

        for skin in skins:
            check = os.path.join(skinspath, skin)
            # 0x0400 is the attribute for reparse point https://msdn.microsoft.com/en-us/library/ee332330(VS.85).aspx
            symbolic = os.path.isdir(check) and (ctypes.windll.kernel32.GetFileAttributesW(str(check)) & 0x0400) > 0
            if symbolic:
                logger.info("detected a symbolic link '" + check + "' through reparse pointer in file attributes.")
                walked = os.walk(check)
                # we walk deeper here because there can also be sub configs hidden in folders like <config/subconfig/config.ini>
                # which results the config being <config/subconfig>
                # at this point <skin> is probably the <config/
                for root, dirs, files in walked:
                    for file in files:
                        probe = os.path.join(root, file)
                        if filecmp.cmp(probe, filepath):
                            config = os.path.relpath(root, skinspath)
                            logger.info("found same file '" + file + "'. Recreate interpreted config '" + config + "'.")

                            return config

                            # when we found the file, we need to reverse the path to detect the sub configs
                            # _, probe_path_and_file = os.path.splitdrive(probe)
                            # _, actual_path_and_file = os.path.splitdrive(filepath)
                            
                            # prime the algorithm to remove the files
                            # probe_path, _ = os.path.split(probe_path_and_file)
                            # actual_path, _ = os.path.split(actual_path_and_file)

                            # while 1:
                            #     probe_path, probe_folder = os.path.split(probe_path)
                            #     actual_path, actual_folder = os.path.split(actual_path)
                            #     if probe_folder != actual_folder:
                            #         logger.info("Found last not matching folder '" + probe_folder + "' with '" + actual_folder + "'")

                            #         directory = os.path.dirname(probe)

                                    
               
        return

    # before you get the whole path called from the skin D:\Documents\Rainmeter\Skins\test\test.ini
    if os.path.isfile(filepath):
        filepath = os.path.dirname(filepath)
    # after the file name is trimmed D:\Documents\Rainmeter\Skins\test

    # the result is the directory path minus the skin path
    # D:\Documents\Rainmeter\Skins\test minus D:\Documents\Rainmeter\Skins\test equals test
    # thus the config is called <test>
    return os.path.relpath(filepath, skinspath)


def get_resources_path(filepath):
    """Get the value of the #@# variable for the specified path.

    Returns None if the path is not in the skins folder
    """
    rfp = get_root_config_path(filepath)

    if not rfp:
        return
    logger.info(os.path.join(rfp, "@Resources") + "\\")
    return os.path.join(rfp, "@Resources") + "\\"


def replace_variables(string, filepath):
    """Replace Rainmeter built-in variables and Windows environment variables in string.

    Replaces occurrences of the following variables in the string:
    #CURRENTFILE#
    #CURRENTPATH#
    #ROOTCONFIGPATH#
    #CURRENTCONFIG#
    #@#
    #SKINSPATH#
    #SETTINGSPATH#
    #PROGRAMPATH#
    #PROGRAMDRIVE#
    #ADDONSPATH#
    #PLUGINSPATH#
    Any Windows environment variables (like %APPDATA%)
    filepath must be a skin file located in a subdirectory of the skins folder
    """
    variables = {
        # lambdas for lazy evaluation
        "#CURRENTFILE#": lambda: get_current_file(filepath),
        "#CURRENTPATH#": lambda: get_current_path(filepath),
        "#ROOTCONFIGPATH#": lambda: get_root_config_path(filepath),
        "#CURRENTCONFIG#": lambda: get_current_config(filepath),
        "#@#": lambda: get_resources_path(filepath),
        "#SKINSPATH#": get_cached_skin_path,
        "#SETTINGSPATH#": get_cached_setting_path,
        "#PROGRAMPATH#": get_cached_program_path,
        "#PROGRAMDRIVE#": get_cached_program_drive,
        "#ADDONSPATH#": get_cached_addon_path,
        "#PLUGINSPATH#": get_cached_plugin_path
    }

    pattern = re.compile("(?i)" + "|".join(list(variables.keys())))
    # replace Rainmeter variables
    repl = pattern.sub(lambda x: variables[x.group().upper()](),
                       string)
    # expand windows environment variables
    repl = os.path.expandvars(repl)
    return repl


def make_path(string, filepath):
    """Make the string into an absolute path of an existing file or folder.

    Replacing Rainmeter built-in variables relative to the file specified in
    filepath (see replace_variables()) will return None if the file or folder
    doesn't exist, or if string is None or empty.
    """
    if not string:
        return None

    repl = replace_variables(string, filepath)
    norm = os.path.normpath(repl)

    # For relative paths, try folder of current file first

    if not os.path.isabs(norm):
        curpath = get_current_path(filepath)
        if curpath:
            abso = os.path.join(curpath, norm)
        else:
            abso = os.path.join(os.path.dirname(filepath), norm)

        if os.path.exists(abso):
            return abso

        # if that doesn't work, try relative to skins path
        # (for #CURRENTCONFIG#)
        abso = os.path.join(get_cached_skin_path(), norm)
        if os.path.exists(abso):
            return abso
    # for absolute paths, try opening containing folder if file does not exist
    else:
        if os.path.exists(norm):
            return norm

        if os.path.exists(os.path.dirname(norm)):
            return os.path.dirname(norm)

    return