<commit_before>import subprocess
import re
import threading
from syncplay.players.basePlayer import BasePlayer
from syncplay import constants, utils
import os
import sys
import random
import socket
import asynchat, asyncore
import urllib
from syncplay.messages import getMessage

class VlcPlayer(BasePlayer):
    speedSupported = True
    customOpenDialog = False
    secondaryOSDSupported = True
    osdMessageSeparator = "; "

    RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX)
    SLAVE_ARGS = constants.VLC_SLAVE_ARGS
    if not sys.platform.startswith('darwin'):
         SLAVE_ARGS.extend(constants.VLC_SLAVE_NONOSX_ARGS)
    random.seed()
    vlcport = random.randrange(constants.VLC_MIN_PORT, constants.VLC_MAX_PORT) if (constants.VLC_MIN_PORT < constants.VLC_MAX_PORT) else constants.VLC_MIN_PORT

    def __init__(self, client, playerPath, filePath, args):
        from twisted.internet import reactor
        self.reactor = reactor
        self._client = client
        self._paused = None
        self._duration = None
        self._filename = None
        self._filepath = None
        self._filechanged = False
        try: # Hack to fix locale issue without importing locale library
            self.radixChar = "{:n}".format(1.5)[1:2]
            if self.radixChar == "" or self.radixChar == "1" or self.radixChar == "5":
                raise ValueError
        except:
            self._client.ui.showErrorMessage("Failed to determine locale. As a fallback Syncplay is using the following radix character: \".\".")
            self.radixChar = "."

        self._durationAsk = threading.Event()
        self._filenameAsk = threading.Event()
        self._pathAsk = threading.Event()
        self._positionAsk = threading.Event()
        self._pausedAsk = threading.Event()
        self._vlcready = threading.Event()
        self._vlcclosed = threading.Event()
        try:
            self._listener = self.__Listener(self, playerPath, filePath, args, self._vlcready, self._vlcclosed)
        except ValueError:
            self._client.ui.showErrorMessage(getMessage("vlc-failed-connection"), True)
            self.reactor.callFromThread(self._client.stop, True,)
            return
        self._listener.setDaemon(True)
        self._listener.start()
        if not self._vlcready.wait(constants.VLC_OPEN_MAX_WAIT_TIME):
            self._vlcready.set()
            self._client.ui.showErrorMessage(getMessage("vlc-failed-connection"), True)
            self.reactor.callFromThread(self._client.stop, True,)
        self.reactor.callFromThread(self._client.initPlayer, self,)

    def _fileUpdateClearEvents(self):
        self._durationAsk.clear()
        self._filenameAsk.clear()
        self._pathAsk.clear()

    def _fileUpdateWaitEvents(self):
        self._durationAsk.wait()
        self._filenameAsk.wait()
        self._pathAsk.wait()

    def _onFileUpdate(self):
        self._fileUpdateClearEvents()
        self._getFileInfo()
        self._fileUpdateWaitEvents()
        args = (self._filename, self._duration, self._filepath)
        self.reactor.callFromThread(self._client.updateFile, *args)
        self.setPaused(self._client.getGlobalPaused())
        self.setPosition(self._client.getGlobalPosition())

    def askForStatus(self):
        self._filechanged = False
        self._positionAsk.clear()
        self._pausedAsk.clear()
        self._listener.sendLine(".")
        if self._filename and not self._filechanged:
            self._positionAsk.wait()
            self._pausedAsk.wait()
            self._client.updatePlayerStatus(self._paused, self._position)
        else:
            self._client.updatePlayerStatus(self._client.getGlobalPaused(), self._client.getGlobalPosition())

    def displayMessage(self, message, duration=constants.OSD_DURATION * 1000, secondaryOSD=False):
        duration /= 1000
        if secondaryOSD == False:
            self._listener.sendLine('display-osd: {}, {}, {}'.format('top-right', duration, message.encode('utf8')))
        else:
            self._listener.sendLine('display-secondary-osd: {}, {}, {}'.format('center', duration, message.encode('utf8')))

    def setSpeed(self, value):
        self._listener.sendLine("set-rate: {:.2n}".format(value))

    def setPosition(self, value):
        self._listener.sendLine("set-position: {}".format(value).replace(".",self.radixChar))

    def setPaused(self, value):
        self._paused = value
        self._listener.sendLine('set-playstate: {}'.format("paused" if value else "playing"))

    def getMRL(self, fileURL):
        fileURL = fileURL.replace(u'\\', u'/')
        fileURL = fileURL.encode('utf8')
        fileURL = urllib.quote_plus(fileURL)
        if sys.platform.startswith('win'):
            fileURL = "file:///" + fileURL
        else:
            fileURL = "file://" + fileURL
        fileURL = fileURL.replace("+", "%20")
        return fileURL

    def openFile(self, filePath, resetPosition=False):
        if utils.isASCII(filePath):
            self._listener.sendLine('load-file: {}'.format(filePath.encode('ascii', 'ignore')))
        else:
            fileURL = self.getMRL(filePath)
            self._listener.sendLine('load-file: {}'.format(fileURL))

    def _getFileInfo(self):
        self._listener.sendLine("get-duration")
        self._listener.sendLine("get-filepath")
        self._listener.sendLine("get-filename")

    def lineReceived(self, line):
        try:
            self._client.ui.showDebugMessage("player << {}".format(line))
        except:
            pass
        match, name, value = self.RE_ANSWER.match(line), "", ""
        if match:
            name, value = match.group('command'), match.group('argument')

        if line == "filepath-change-notification":
            self._filechanged = True
            t = threading.Thread(target=self._onFileUpdate)
            t.setDaemon(True)
            t.start()
        elif name == "filepath":
            self._filechanged = True
            if value == "no-input":
                self._filepath = None
            else:
                if "file://" in value:
                    value = value.replace("file://", "")
                    if not os.path.isfile(value):
                        value = value.lstrip("/")
                self._filepath = value
            self._pathAsk.set()
        elif name == "duration":
            if value == "no-input":
                self._duration = 0
            else:
                self._duration = float(value.replace(",", "."))
            self._durationAsk.set()
        elif name == "playstate":
            self._paused = bool(value != 'playing') if(value != "no-input" and self._filechanged == False) else self._client.getGlobalPaused()
            self._pausedAsk.set()
        elif name == "position":
            self._position = float(value.replace(",", ".")) if (value != "no-input" and self._filechanged == False) else self._client.getGlobalPosition()
            self._positionAsk.set()
        elif name == "filename":
            self._filechanged = True
            self._filename = value.decode('utf-8')
            self._filenameAsk.set()
        elif line.startswith("interface-version: "):
            interface_version = line[19:24]
            if int(interface_version.replace(".", "")) < int(constants.VLC_INTERFACE_MIN_VERSION.replace(".", "")):
                self._client.ui.showErrorMessage(getMessage("vlc-interface-version-mismatch").format(str(interface_version), str(constants.VLC_INTERFACE_MIN_VERSION)))
        elif line[:16] == "VLC media player":
            vlc_version = line[17:22]
            if int(vlc_version.replace(".", "")) < int(constants.VLC_MIN_VERSION.replace(".", "")):
                self._client.ui.showErrorMessage(getMessage("vlc-version-mismatch").format(str(vlc_version), str(constants.VLC_MIN_VERSION)))
            self._vlcready.set()
            self._listener.sendLine("get-interface-version")

    @staticmethod
    def run(client, playerPath, filePath, args):
        vlc = VlcPlayer(client, VlcPlayer.getExpandedPath(playerPath), filePath, args)
        return vlc

    @staticmethod
    def getDefaultPlayerPathsList():
        l = []
        for path in constants.VLC_PATHS:
            p = VlcPlayer.getExpandedPath(path)
            if p:
                l.append(p)
        return l

    @staticmethod
    def isValidPlayerPath(path):
        if "vlc" in path.lower() and VlcPlayer.getExpandedPath(path):
            return True
        return False

    @staticmethod
    def getPlayerPathErrors(playerPath, filePath):
        return None

    @staticmethod
    def getIconPath(path):
        return constants.VLC_ICONPATH

    @staticmethod
    def getExpandedPath(playerPath):
        if not os.path.isfile(playerPath):
            if os.path.isfile(playerPath + u"vlc.exe"):
                playerPath += u"vlc.exe"
                return playerPath
            elif os.path.isfile(playerPath + u"\\vlc.exe"):
                playerPath += u"\\vlc.exe"
                return playerPath
        if os.access(playerPath, os.X_OK):
            return playerPath
        for path in os.environ['PATH'].split(':'):
            path = os.path.join(os.path.realpath(path), playerPath)
            if os.access(path, os.X_OK):
                return path

    def drop(self):
        self._vlcclosed.clear()
        self._listener.sendLine('close-vlc')
        self._vlcclosed.wait()
        self._durationAsk.set()
        self._filenameAsk.set()
        self._pathAsk.set()
        self._positionAsk.set()
        self._vlcready.set()
        self._pausedAsk.set()
        self.reactor.callFromThread(self._client.stop, False,)

    class __Listener(threading.Thread, asynchat.async_chat):
        def __init__(self, playerController, playerPath, filePath, args, vlcReady, vlcClosed):
            self.__playerController = playerController
            call = [playerPath]
            if filePath:
                if utils.isASCII(filePath):
                    call.append(filePath)
                else:
                    call.append(self.__playerController.getMRL(filePath))
            def _usevlcintf(vlcIntfPath, vlcIntfUserPath):
                vlcSyncplayInterfacePath = vlcIntfPath + "syncplay.lua"
                if not os.path.isfile(vlcSyncplayInterfacePath):
                    vlcSyncplayInterfacePath = vlcIntfUserPath + "syncplay.lua"
                if os.path.isfile(vlcSyncplayInterfacePath):
                    with open(vlcSyncplayInterfacePath, 'rU') as interfacefile:
                        for line in interfacefile:
                            if "local connectorversion" in line:
                                interface_version = line[26:31]
                                if int(interface_version.replace(".", "")) >= int(constants.VLC_INTERFACE_MIN_VERSION.replace(".", "")):
                                    return True
                                else:
                                    playerController._client.ui.showErrorMessage(getMessage("vlc-interface-oldversion-ignored"))
                                    return False
                playerController._client.ui.showErrorMessage(getMessage("vlc-interface-not-installed"))
                return False
            if sys.platform.startswith('linux'):
                playerController.vlcIntfPath = "/usr/lib/vlc/lua/intf/"
                playerController.vlcIntfUserPath = os.path.join(os.getenv('HOME', '.'), ".local/share/vlc/lua/intf/")
            elif sys.platform.startswith('darwin'):
                playerController.vlcIntfPath = "/Applications/VLC.app/Contents/MacOS/share/lua/intf/"
                playerController.vlcIntfUserPath = os.path.join(os.getenv('HOME', '.'), "Library/Application Support/org.videolan.vlc/lua/intf/")
            elif 'bsd' in sys.platform or sys.platform.startswith('dragonfly'):
                # *BSD ports/pkgs install to /usr/local by default.
                # This should also work for all the other BSDs, such as OpenBSD or DragonFly.
                playerController.vlcIntfPath = "/usr/local/lib/vlc/lua/intf/"
                playerController.vlcIntfUserPath = os.path.join(os.getenv('HOME', '.'), ".local/share/vlc/lua/intf/")
            else:
                playerController.vlcIntfPath = os.path.dirname(playerPath).replace("\\", "/") + "/lua/intf/"
                playerController.vlcIntfUserPath = os.path.join(os.getenv('APPDATA', '.'), "VLC\\lua\\intf\\")
            playerController.vlcModulePath = playerController.vlcIntfPath + "modules/?.luac"
            if _usevlcintf(playerController.vlcIntfPath, playerController.vlcIntfUserPath):
                playerController.SLAVE_ARGS.append('--lua-config=syncplay={{port=\"{}\"}}'.format(str(playerController.vlcport)))
            else:
                if sys.platform.startswith('linux'):
                    playerController.vlcDataPath = "/usr/lib/syncplay/resources"
                else:
                    playerController.vlcDataPath = utils.findWorkingDir() + "\\resources"
                playerController.SLAVE_ARGS.append('--data-path={}'.format(playerController.vlcDataPath))
                playerController.SLAVE_ARGS.append('--lua-config=syncplay={{modulepath=\"{}\",port=\"{}\"}}'.format(playerController.vlcModulePath, str(playerController.vlcport)))

            call.extend(playerController.SLAVE_ARGS)
            if args:
                call.extend(args)

            self._vlcready = vlcReady
            self._vlcclosed = vlcClosed
            self.__process = subprocess.Popen(call, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
            for line in iter(self.__process.stderr.readline, ''):
                if "[syncplay]" in line:
                    if "Listening on host" in line:
                        break
                    if "Hosting Syncplay" in line:
                        break
                    elif "Couldn't find lua interface" in line:
                        playerController._client.ui.showErrorMessage(getMessage("vlc-failed-noscript").format(line), True)
                        break
                    elif "lua interface error" in line:
                        playerController._client.ui.showErrorMessage(getMessage("media-player-error").format(line), True)
                        break
            try:
                self.__process.stderr = subprocess.STD_ERROR_HANDLE
            except:
                self.__playerController._client.ui.showDebugMessage("Failed to set VLC's stderr to STD_ERROR_HANDLE")
            threading.Thread.__init__(self, name="VLC Listener")
            asynchat.async_chat.__init__(self)
            self.set_terminator("\n")
            self._ibuffer = []
            self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
            self._sendingData = threading.Lock()

        def initiate_send(self):
            with self._sendingData:
                asynchat.async_chat.initiate_send(self)

        def run(self):
            self._vlcready.clear()
            self.connect(('localhost', self.__playerController.vlcport))
            asyncore.loop()

        def handle_connect(self):
            asynchat.async_chat.handle_connect(self)
            self._vlcready.set()

        def collect_incoming_data(self, data):
            self._ibuffer.append(data)

        def handle_close(self):
            asynchat.async_chat.handle_close(self)
            self.__playerController.drop()

        def found_terminator(self):
            self.__playerController.lineReceived("".join(self._ibuffer))
            self._ibuffer = []

        def sendLine(self, line):
            if self.connected:
                try:
                    self.push(line + "\n")
                    self.__playerController._client.ui.showDebugMessage("player >> {}".format(line))
                except:
                    pass
            if line == "close-vlc":
                self._vlcclosed.set()
<commit_msg>Use sys.stderr over subprocess.STD_ERROR_HANDLE as it should be cross-platform compatible<commit_after>import subprocess
import re
import threading
from syncplay.players.basePlayer import BasePlayer
from syncplay import constants, utils
import os
import sys
import random
import socket
import asynchat, asyncore
import urllib
from syncplay.messages import getMessage

class VlcPlayer(BasePlayer):
    speedSupported = True
    customOpenDialog = False
    secondaryOSDSupported = True
    osdMessageSeparator = "; "

    RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX)
    SLAVE_ARGS = constants.VLC_SLAVE_ARGS
    if not sys.platform.startswith('darwin'):
         SLAVE_ARGS.extend(constants.VLC_SLAVE_NONOSX_ARGS)
    random.seed()
    vlcport = random.randrange(constants.VLC_MIN_PORT, constants.VLC_MAX_PORT) if (constants.VLC_MIN_PORT < constants.VLC_MAX_PORT) else constants.VLC_MIN_PORT

    def __init__(self, client, playerPath, filePath, args):
        from twisted.internet import reactor
        self.reactor = reactor
        self._client = client
        self._paused = None
        self._duration = None
        self._filename = None
        self._filepath = None
        self._filechanged = False
        try: # Hack to fix locale issue without importing locale library
            self.radixChar = "{:n}".format(1.5)[1:2]
            if self.radixChar == "" or self.radixChar == "1" or self.radixChar == "5":
                raise ValueError
        except:
            self._client.ui.showErrorMessage("Failed to determine locale. As a fallback Syncplay is using the following radix character: \".\".")
            self.radixChar = "."

        self._durationAsk = threading.Event()
        self._filenameAsk = threading.Event()
        self._pathAsk = threading.Event()
        self._positionAsk = threading.Event()
        self._pausedAsk = threading.Event()
        self._vlcready = threading.Event()
        self._vlcclosed = threading.Event()
        try:
            self._listener = self.__Listener(self, playerPath, filePath, args, self._vlcready, self._vlcclosed)
        except ValueError:
            self._client.ui.showErrorMessage(getMessage("vlc-failed-connection"), True)
            self.reactor.callFromThread(self._client.stop, True,)
            return
        self._listener.setDaemon(True)
        self._listener.start()
        if not self._vlcready.wait(constants.VLC_OPEN_MAX_WAIT_TIME):
            self._vlcready.set()
            self._client.ui.showErrorMessage(getMessage("vlc-failed-connection"), True)
            self.reactor.callFromThread(self._client.stop, True,)
        self.reactor.callFromThread(self._client.initPlayer, self,)

    def _fileUpdateClearEvents(self):
        self._durationAsk.clear()
        self._filenameAsk.clear()
        self._pathAsk.clear()

    def _fileUpdateWaitEvents(self):
        self._durationAsk.wait()
        self._filenameAsk.wait()
        self._pathAsk.wait()

    def _onFileUpdate(self):
        self._fileUpdateClearEvents()
        self._getFileInfo()
        self._fileUpdateWaitEvents()
        args = (self._filename, self._duration, self._filepath)
        self.reactor.callFromThread(self._client.updateFile, *args)
        self.setPaused(self._client.getGlobalPaused())
        self.setPosition(self._client.getGlobalPosition())

    def askForStatus(self):
        self._filechanged = False
        self._positionAsk.clear()
        self._pausedAsk.clear()
        self._listener.sendLine(".")
        if self._filename and not self._filechanged:
            self._positionAsk.wait()
            self._pausedAsk.wait()
            self._client.updatePlayerStatus(self._paused, self._position)
        else:
            self._client.updatePlayerStatus(self._client.getGlobalPaused(), self._client.getGlobalPosition())

    def displayMessage(self, message, duration=constants.OSD_DURATION * 1000, secondaryOSD=False):
        duration /= 1000
        if secondaryOSD == False:
            self._listener.sendLine('display-osd: {}, {}, {}'.format('top-right', duration, message.encode('utf8')))
        else:
            self._listener.sendLine('display-secondary-osd: {}, {}, {}'.format('center', duration, message.encode('utf8')))

    def setSpeed(self, value):
        self._listener.sendLine("set-rate: {:.2n}".format(value))

    def setPosition(self, value):
        self._listener.sendLine("set-position: {}".format(value).replace(".",self.radixChar))

    def setPaused(self, value):
        self._paused = value
        self._listener.sendLine('set-playstate: {}'.format("paused" if value else "playing"))

    def getMRL(self, fileURL):
        fileURL = fileURL.replace(u'\\', u'/')
        fileURL = fileURL.encode('utf8')
        fileURL = urllib.quote_plus(fileURL)
        if sys.platform.startswith('win'):
            fileURL = "file:///" + fileURL
        else:
            fileURL = "file://" + fileURL
        fileURL = fileURL.replace("+", "%20")
        return fileURL

    def openFile(self, filePath, resetPosition=False):
        if utils.isASCII(filePath):
            self._listener.sendLine('load-file: {}'.format(filePath.encode('ascii', 'ignore')))
        else:
            fileURL = self.getMRL(filePath)
            self._listener.sendLine('load-file: {}'.format(fileURL))

    def _getFileInfo(self):
        self._listener.sendLine("get-duration")
        self._listener.sendLine("get-filepath")
        self._listener.sendLine("get-filename")

    def lineReceived(self, line):
        try:
            self._client.ui.showDebugMessage("player << {}".format(line))
        except:
            pass
        match, name, value = self.RE_ANSWER.match(line), "", ""
        if match:
            name, value = match.group('command'), match.group('argument')

        if line == "filepath-change-notification":
            self._filechanged = True
            t = threading.Thread(target=self._onFileUpdate)
            t.setDaemon(True)
            t.start()
        elif name == "filepath":
            self._filechanged = True
            if value == "no-input":
                self._filepath = None
            else:
                if "file://" in value:
                    value = value.replace("file://", "")
                    if not os.path.isfile(value):
                        value = value.lstrip("/")
                self._filepath = value
            self._pathAsk.set()
        elif name == "duration":
            if value == "no-input":
                self._duration = 0
            else:
                self._duration = float(value.replace(",", "."))
            self._durationAsk.set()
        elif name == "playstate":
            self._paused = bool(value != 'playing') if(value != "no-input" and self._filechanged == False) else self._client.getGlobalPaused()
            self._pausedAsk.set()
        elif name == "position":
            self._position = float(value.replace(",", ".")) if (value != "no-input" and self._filechanged == False) else self._client.getGlobalPosition()
            self._positionAsk.set()
        elif name == "filename":
            self._filechanged = True
            self._filename = value.decode('utf-8')
            self._filenameAsk.set()
        elif line.startswith("interface-version: "):
            interface_version = line[19:24]
            if int(interface_version.replace(".", "")) < int(constants.VLC_INTERFACE_MIN_VERSION.replace(".", "")):
                self._client.ui.showErrorMessage(getMessage("vlc-interface-version-mismatch").format(str(interface_version), str(constants.VLC_INTERFACE_MIN_VERSION)))
        elif line[:16] == "VLC media player":
            vlc_version = line[17:22]
            if int(vlc_version.replace(".", "")) < int(constants.VLC_MIN_VERSION.replace(".", "")):
                self._client.ui.showErrorMessage(getMessage("vlc-version-mismatch").format(str(vlc_version), str(constants.VLC_MIN_VERSION)))
            self._vlcready.set()
            self._listener.sendLine("get-interface-version")

    @staticmethod
    def run(client, playerPath, filePath, args):
        vlc = VlcPlayer(client, VlcPlayer.getExpandedPath(playerPath), filePath, args)
        return vlc

    @staticmethod
    def getDefaultPlayerPathsList():
        l = []
        for path in constants.VLC_PATHS:
            p = VlcPlayer.getExpandedPath(path)
            if p:
                l.append(p)
        return l

    @staticmethod
    def isValidPlayerPath(path):
        if "vlc" in path.lower() and VlcPlayer.getExpandedPath(path):
            return True
        return False

    @staticmethod
    def getPlayerPathErrors(playerPath, filePath):
        return None

    @staticmethod
    def getIconPath(path):
        return constants.VLC_ICONPATH

    @staticmethod
    def getExpandedPath(playerPath):
        if not os.path.isfile(playerPath):
            if os.path.isfile(playerPath + u"vlc.exe"):
                playerPath += u"vlc.exe"
                return playerPath
            elif os.path.isfile(playerPath + u"\\vlc.exe"):
                playerPath += u"\\vlc.exe"
                return playerPath
        if os.access(playerPath, os.X_OK):
            return playerPath
        for path in os.environ['PATH'].split(':'):
            path = os.path.join(os.path.realpath(path), playerPath)
            if os.access(path, os.X_OK):
                return path

    def drop(self):
        self._vlcclosed.clear()
        self._listener.sendLine('close-vlc')
        self._vlcclosed.wait()
        self._durationAsk.set()
        self._filenameAsk.set()
        self._pathAsk.set()
        self._positionAsk.set()
        self._vlcready.set()
        self._pausedAsk.set()
        self.reactor.callFromThread(self._client.stop, False,)

    class __Listener(threading.Thread, asynchat.async_chat):
        def __init__(self, playerController, playerPath, filePath, args, vlcReady, vlcClosed):
            self.__playerController = playerController
            call = [playerPath]
            if filePath:
                if utils.isASCII(filePath):
                    call.append(filePath)
                else:
                    call.append(self.__playerController.getMRL(filePath))
            def _usevlcintf(vlcIntfPath, vlcIntfUserPath):
                vlcSyncplayInterfacePath = vlcIntfPath + "syncplay.lua"
                if not os.path.isfile(vlcSyncplayInterfacePath):
                    vlcSyncplayInterfacePath = vlcIntfUserPath + "syncplay.lua"
                if os.path.isfile(vlcSyncplayInterfacePath):
                    with open(vlcSyncplayInterfacePath, 'rU') as interfacefile:
                        for line in interfacefile:
                            if "local connectorversion" in line:
                                interface_version = line[26:31]
                                if int(interface_version.replace(".", "")) >= int(constants.VLC_INTERFACE_MIN_VERSION.replace(".", "")):
                                    return True
                                else:
                                    playerController._client.ui.showErrorMessage(getMessage("vlc-interface-oldversion-ignored"))
                                    return False
                playerController._client.ui.showErrorMessage(getMessage("vlc-interface-not-installed"))
                return False
            if sys.platform.startswith('linux'):
                playerController.vlcIntfPath = "/usr/lib/vlc/lua/intf/"
                playerController.vlcIntfUserPath = os.path.join(os.getenv('HOME', '.'), ".local/share/vlc/lua/intf/")
            elif sys.platform.startswith('darwin'):
                playerController.vlcIntfPath = "/Applications/VLC.app/Contents/MacOS/share/lua/intf/"
                playerController.vlcIntfUserPath = os.path.join(os.getenv('HOME', '.'), "Library/Application Support/org.videolan.vlc/lua/intf/")
            elif 'bsd' in sys.platform or sys.platform.startswith('dragonfly'):
                # *BSD ports/pkgs install to /usr/local by default.
                # This should also work for all the other BSDs, such as OpenBSD or DragonFly.
                playerController.vlcIntfPath = "/usr/local/lib/vlc/lua/intf/"
                playerController.vlcIntfUserPath = os.path.join(os.getenv('HOME', '.'), ".local/share/vlc/lua/intf/")
            else:
                playerController.vlcIntfPath = os.path.dirname(playerPath).replace("\\", "/") + "/lua/intf/"
                playerController.vlcIntfUserPath = os.path.join(os.getenv('APPDATA', '.'), "VLC\\lua\\intf\\")
            playerController.vlcModulePath = playerController.vlcIntfPath + "modules/?.luac"
            if _usevlcintf(playerController.vlcIntfPath, playerController.vlcIntfUserPath):
                playerController.SLAVE_ARGS.append('--lua-config=syncplay={{port=\"{}\"}}'.format(str(playerController.vlcport)))
            else:
                if sys.platform.startswith('linux'):
                    playerController.vlcDataPath = "/usr/lib/syncplay/resources"
                else:
                    playerController.vlcDataPath = utils.findWorkingDir() + "\\resources"
                playerController.SLAVE_ARGS.append('--data-path={}'.format(playerController.vlcDataPath))
                playerController.SLAVE_ARGS.append('--lua-config=syncplay={{modulepath=\"{}\",port=\"{}\"}}'.format(playerController.vlcModulePath, str(playerController.vlcport)))

            call.extend(playerController.SLAVE_ARGS)
            if args:
                call.extend(args)

            self._vlcready = vlcReady
            self._vlcclosed = vlcClosed
            self.__process = subprocess.Popen(call, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
            for line in iter(self.__process.stderr.readline, ''):
                if "[syncplay]" in line:
                    if "Listening on host" in line:
                        break
                    if "Hosting Syncplay" in line:
                        break
                    elif "Couldn't find lua interface" in line:
                        playerController._client.ui.showErrorMessage(getMessage("vlc-failed-noscript").format(line), True)
                        break
                    elif "lua interface error" in line:
                        playerController._client.ui.showErrorMessage(getMessage("media-player-error").format(line), True)
                        break
            self.__process.stderr = sys.stderr
            threading.Thread.__init__(self, name="VLC Listener")
            asynchat.async_chat.__init__(self)
            self.set_terminator("\n")
            self._ibuffer = []
            self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
            self._sendingData = threading.Lock()

        def initiate_send(self):
            with self._sendingData:
                asynchat.async_chat.initiate_send(self)

        def run(self):
            self._vlcready.clear()
            self.connect(('localhost', self.__playerController.vlcport))
            asyncore.loop()

        def handle_connect(self):
            asynchat.async_chat.handle_connect(self)
            self._vlcready.set()

        def collect_incoming_data(self, data):
            self._ibuffer.append(data)

        def handle_close(self):
            asynchat.async_chat.handle_close(self)
            self.__playerController.drop()

        def found_terminator(self):
            self.__playerController.lineReceived("".join(self._ibuffer))
            self._ibuffer = []

        def sendLine(self, line):
            if self.connected:
                try:
                    self.push(line + "\n")
                    self.__playerController._client.ui.showDebugMessage("player >> {}".format(line))
                except:
                    pass
            if line == "close-vlc":
                self._vlcclosed.set()
<|endoftext|>


<commit_before><commit_msg>remove useless test.py<commit_after><|endoftext|>


<commit_before># python 2 compatibility
from __future__ import print_function, unicode_literals
from builtins import str
from io import open
# builtins
from os import path
from re import sub, findall
import logging
# custom
from blowdrycss.htmlparser import HTMLClassParser

class FileRegexMap(object):
    """ Given a file path including the file extension it maps the detected file extension to a regex pattern.

    **Supported Javascript and Typescript extensions:** .js, .ts

    Javascript has a number of cases where a special substitution is performed.

    **Supported jinja and django template extensions:** .jinja, .jinja2, .jnj, .ja, .djt, .djhtml

    **Jinja sub_regexes regex explained:**

    | Remove {{...}} and {%...%} where '...' is any character.
    | ``{`` -- Substring must start with ``{``.
    | ``.*`` -- Matches any character.
    | ``?`` -- Do not be greedy.
    | ``}`` -- Match with an ending ``}``.
    | ``?}`` -- Optionally allow one more ``}``

    **Supported XHTML, asp.net, and ruby template extensions:** .aspx, .ascx, .master, .erb

    **XHTML sub_regexes regex explained:**

    | Remove <%...%> patterns where '...' is any character
    | ``<%`` -- Substring must start with ``<%``.
    | ``.*`` -- Matches any character.
    | ``?`` -- Do not be greedy.
    | ``%>`` -- Substring must end with ``%>``.

    | **Raises OSError** if the ``_path`` does not exist.

    | **Parameters:**

    | **_path** (*str*) -- Relative or full path to the parsable file.

    **Examples:**

    >>> from blowdrycss.classparser import FileRegexMap
    >>> file_regex_map = FileRegexMap(path='Default.aspx')
    >>> file_regex_map.regex_dict
    {
        'sub_regexes': r'<%.*?%>',
        'findall_regexes': r'class="(.*?)"'
    }

    """
    def __init__(self, file_path=''):
        if path.isfile(file_path):
            self._regex_dict = dict()
            self.file_path = file_path.strip()                              # Remove external whitespace.
            self.name, self.extension = path.splitext(self.file_path)

            self.special_js = (
                r'(domClass.add\(\s*.*?,\s*["\'])',                              # dojo
                r'(domClass.add\(\s*.*?,\s*["\'])',
                r'(dojo.addClass\(\s*.*?,\s*["\'])',
                r'(domClass.remove\(\s*.*?,\s*["\'])',
                r'(dojo.removeClass\(\s*.*?,\s*["\'])',
                r'(YAHOO.util.Dom.addClass\(\s*.*?,\s*["\'])',                   # yui
                r'(YAHOO.util.Dom.hasClass\(\s*.*?,\s*["\'])',
                r'(YAHOO.util.Dom.removeClass\(\s*.*?,\s*["\'])',
                r'(.addClass\(\s*["\'])',                                        # jquery
                r'(.removeClass\(\s*["\'])',
                r'(\$\(\s*["\']\.)',
            )

            sub_js = (                              # JS Comments could contain unused class selectors code.
                r'//.*?\n',
                r'/\*.*?\*/',
                r'(domClass.add\(\s*.*?,\s*["\'])',                              # dojo
                r'(domClass.add\(\s*.*?,\s*["\'])',
                r'(dojo.addClass\(\s*.*?,\s*["\'])',
                r'(domClass.remove\(\s*.*?,\s*["\'])',
                r'(dojo.removeClass\(\s*.*?,\s*["\'])',
                r'(YAHOO.util.Dom.addClass\(\s*.*?,\s*["\'])',                   # yui
                r'(YAHOO.util.Dom.hasClass\(\s*.*?,\s*["\'])',
                r'(YAHOO.util.Dom.removeClass\(\s*.*?,\s*["\'])',
                r'(.addClass\(\s*["\'])',                                        # jquery
                r'(.removeClass\(\s*["\'])',
                r'(\$\(\s*["\']\.)',
            )
            sub_html = (r'', ) + sub_js             # TODO: Missing HTML comment removal.
            sub_jinja = (r'{.*?}?}', ) + sub_html
            sub_django = (r'{.*?}?}', ) + sub_html
            sub_dotnet = (r'<%.*?%>', ) + sub_html  # TODO: Missing XHTML comment removal.
            sub_ruby = (r'<%.*?%>', ) + sub_html

            class_regex = (r'class="(.*?)"', )

            findall_regex_js = (
                r'.classList.add\(\s*[\'"](.*?)["\']\s*\)',
                r'.classList.remove\(\s*[\'"](.*?)["\']\s*\)',
                r'.className\s*\+?=\s*.*?\+?[\'"](.*?)["\']',
                r'.getElementsByClassName\(\s*[\'"](.*?)["\']\s*\)',
                r'.setAttribute\(\s*.*?,\s*[\'"](.*?)["\']\s*\)',
                r'extract__this__class\(\s*[\'"](.*?)["\']\s*\)',                           # Find the chopped ones

            )

            findall_regex = class_regex + findall_regex_js

            self.file_type_dict = {
                '.js': {
                    'sub_regexes': sub_js,
                    'findall_regexes': findall_regex,
                },
                '.html': {
                    'sub_regexes': sub_html,
                    'findall_regexes': findall_regex,
                },
                '.jinja': {
                    'sub_regexes': sub_jinja,
                    'findall_regexes': findall_regex,
                },
                '.jinja2': {
                    'sub_regexes': sub_jinja,
                    'findall_regexes': findall_regex,
                },
                '.jnj': {
                    'sub_regexes': sub_jinja,
                    'findall_regexes': findall_regex,
                },
                '.ja': {
                    'sub_regexes': sub_jinja,
                    'findall_regexes': findall_regex,
                },
                '.djt': {
                    'sub_regexes': sub_django,
                    'findall_regexes': findall_regex,
                },
                '.djhtml': {
                    'sub_regexes': sub_django,
                    'findall_regexes': findall_regex,
                },
                '.aspx': {
                    'sub_regexes': sub_dotnet,
                    'findall_regexes': findall_regex,
                },
                '.ascx': {
                    'sub_regexes': sub_dotnet,
                    'findall_regexes': findall_regex,
                },
                '.master': {
                    'sub_regexes': sub_dotnet,
                    'findall_regexes': findall_regex,
                },
                '.erb': {
                    'sub_regexes': sub_ruby,
                    'findall_regexes': findall_regex,
                },
            }
        else:
            raise OSError(file_path + ' does not exist.')

    def is_valid_extension(self):
        """ Validates the extension. Returns whether True or False based on whether the extension is a key in
        ``file_type_dict``.

        :return: (*bool*) -- Returns True if the extension is a key in file_type_dict. Returns False otherwise.
        """
        return self.extension in self.file_type_dict

    @property
    def regex_dict(self):
        """ Validates ``self.extension`` and returns the associated regex dictionary for that extension.

        :return: Returns the regular expression dictionary associated with ``self.extension``.

        """
        self.is_valid_extension()
        return self.file_type_dict[self.extension]

class ClassExtractor(object):
    """ Given a file_regex_map of any type along with a substitution regex patterns and a findall regex patterns.
    Returns a minimum set of class selectors as ``class_set``.

    | **Parameters:**

    | **file_path** (*str*) -- Path to the file_regex_map to be parsed.

    | **sub_pattern** (*tuple of regexes*) -- Zero or more regex patterns to be removed from the file_regex_map text
      before further processing.

    | **findall_pattern** (*tuple of regexes*) -- Zero or more regex patterns used to find all class selectors
      in a given file.

    **Example Usage:**

    >>> from blowdrycss.classparser import ClassExtractor
    >>> # Assuming Default.aspx is located in the same directory
    >>> aspx_file = 'Default.aspx'
    >>> aspx_sub = r'<%.*?%>'
    >>> aspx_findall = r'class="(.*?)"'
    >>> class_extractor = ClassExtractor(file_path=aspx_file)
    >>> class_extractor.class_set
    {'row', 'padding-top-30', 'padding-bottom-30', 'bgc-green'}
    >>> jinja2_file = 'index.jinja2'
    >>> jinja2_sub = r'{.*?}?}'
    >>> jinja2_findall = r'class="(.*?)"'
    >>> class_extractor = ClassExtractor(file_path=jinja2_file, sub_regexes=jinja2_sub, findall_regexes=jinja2_findall)
    >>> class_extractor.class_set
    {'purple', 'padding-left-5', 'squirrel', 'text-align-center', 'large-up', 'border-1', 'row', 'text-align-center'}

    """
    def __init__(self, file_path=''):
        if path.isfile(file_path):
            self.file_path = file_path
            self.file_regex_map = FileRegexMap(file_path=file_path)
            regex_dict = self.file_regex_map.regex_dict
            self.sub_regexes = regex_dict['sub_regexes']
            self.findall_regexes = regex_dict['findall_regexes']

        else:
            raise OSError(file_path + ' does not exist.')

    @property
    def raw_class_list(self):
        """ Uses all sub_regexs and findall_regexes to extract space-delimited CSS class selector strings.
        Raw means space-delimited.

        Example: Look for the findall_regexes 'class="..."'. Extract the '...' part.

        sub_regexes() is used to remove template variables from quoted class selector strings in file text.
        findall_regexes() is used to find all class selector strings in a given files text.

        :return: (*list of strings*) -- Returns a list of raw class selector strings.

        """
        class_list = []
        with open(self.file_path, 'r', encoding='utf-8') as _file:
            text = _file.read()
            for sub_regex in self.sub_regexes:                      # Remove everything first.
                if sub_regex in self.file_regex_map.special_js:
                    text = sub(sub_regex, 'extract__this__class("', text)
                else:
                    text = sub(sub_regex, '', text)
            for findall_regex in self.findall_regexes:              # Find everything second.
                class_list += findall(findall_regex, text)
        return class_list

    @property
    def class_set(self):
        """ Reduce the list of quoted class selector strings to a minimum set of classes. Returns the ``class_set``.

        :return: (*set of strings*) -- Return the minimum set of individual class selector strings.

        """
        class_set = set()

        logging.debug(msg='classparser.raw_class_list:\t' + str(self.raw_class_list))
        for classes in self.raw_class_list:
            class_set = set.union(
                set(classes.split()),               # Split space delimited string into set().
                class_set                           # Unite the new set with class_set.
            )                                       # Assign union() to class_set.
        return class_set

class ClassParser(object):
    """ Parses all project files provided by file_dict. HTML files are sent to ``HTMLClassParser``, and all other
    file types are sent to ``ClassExtractor``.

    **Parameters**

    **file_dict** (*dict*) -- Expecting FileFinder.file_dict as input.

    **Returns** None

    **Example**

    >>> from os import getcwd, chdir, path
    >>> from blowdrycss.filehandler import FileFinder
    >>> from blowdrycss.classparser import ClassParser
    >>> current_dir = getcwd()
    >>> chdir('..')
    >>> project_directory = path.join(current_dir, 'examplesite')
    >>> chdir(current_dir)    # Change it back.
    >>> file_finder = FileFinder(project_directory=project_directory)
    >>> file_dict = file_finder.file_dict
    >>> general_class_parser = ClassParser(file_dict=file_dict)
    >>> general_class_parser.class_set
    { Returns a complete set of all the classes discovered after looking in at all file paths. }

    """
    def __init__(self, file_dict):
        # Handled 'html' files with HTMLClassParser, and remove *.html files from file_dict.
        if '.html' in file_dict:
            self.html_class_parser = HTMLClassParser(files=file_dict['.html'])
            del file_dict['.html']
            self.class_set = self.html_class_parser.class_set
        else:
            self.html_class_parser = None
            self.class_set = set()

        self.file_dict = file_dict

        self.file_path_list = []
        self.build_file_path_list()

        logging.debug(msg='classparser.html_class_parser.class_set:\t' + str(self.class_set))
        self.build_class_set()

    def build_file_path_list(self):
        """ Builds a list of all of the file paths regardless of type.

        :return: None

        """
        keys = list(self.file_dict)
        for key in keys:
            self.file_path_list += self.file_dict[key]

    def build_class_set(self):
        """ Builds a complete set of all the classes discovered after looking in at all file paths.

        :return: None

        """
        for file_path in self.file_path_list:
            class_extractor = ClassExtractor(file_path=file_path)
            logging.debug(msg='classparser.class_extractor.class_set:\t' + str(class_extractor.class_set))
            self.class_set = self.class_set.union(class_extractor.class_set)
            logging.debug(msg='classparser final class_set:\t' + str(self.class_set))
<commit_msg>Renamed the "special" variables to "js".<commit_after># python 2 compatibility
from __future__ import print_function, unicode_literals
from builtins import str
from io import open
# builtins
from os import path
from re import sub, findall
import logging
# custom
from blowdrycss.htmlparser import HTMLClassParser

class FileRegexMap(object):
    """ Given a file path including the file extension it maps the detected file extension to a regex pattern.

    **Supported Javascript and Typescript extensions:** .js, .ts

    Javascript has a number of cases where a special substitution is performed.

    **Supported jinja and django template extensions:** .jinja, .jinja2, .jnj, .ja, .djt, .djhtml

    **Jinja sub_regexes regex explained:**

    | Remove {{...}} and {%...%} where '...' is any character.
    | ``{`` -- Substring must start with ``{``.
    | ``.*`` -- Matches any character.
    | ``?`` -- Do not be greedy.
    | ``}`` -- Match with an ending ``}``.
    | ``?}`` -- Optionally allow one more ``}``

    **Supported XHTML, asp.net, and ruby template extensions:** .aspx, .ascx, .master, .erb

    **XHTML sub_regexes regex explained:**

    | Remove <%...%> patterns where '...' is any character
    | ``<%`` -- Substring must start with ``<%``.
    | ``.*`` -- Matches any character.
    | ``?`` -- Do not be greedy.
    | ``%>`` -- Substring must end with ``%>``.

    | **Raises OSError** if the ``_path`` does not exist.

    | **Parameters:**

    | **_path** (*str*) -- Relative or full path to the parsable file.

    **Examples:**

    >>> from blowdrycss.classparser import FileRegexMap
    >>> file_regex_map = FileRegexMap(path='Default.aspx')
    >>> file_regex_map.regex_dict
    {
        'sub_regexes': r'<%.*?%>',
        'findall_regexes': r'class="(.*?)"'
    }

    """
    def __init__(self, file_path=''):
        if path.isfile(file_path):
            self._regex_dict = dict()
            self.file_path = file_path.strip()                                  # Remove external whitespace.
            self.name, self.extension = path.splitext(self.file_path)

            js_substring = r'extract__class__set'
            self.js_replacement = js_substring + r'("'

            self.js_case = (
                r'(domClass.add\(\s*.*?,\s*["\'])',                             # dojo
                r'(domClass.add\(\s*.*?,\s*["\'])',
                r'(dojo.addClass\(\s*.*?,\s*["\'])',
                r'(domClass.remove\(\s*.*?,\s*["\'])',
                r'(dojo.removeClass\(\s*.*?,\s*["\'])',
                r'(YAHOO.util.Dom.addClass\(\s*.*?,\s*["\'])',                  # yui
                r'(YAHOO.util.Dom.hasClass\(\s*.*?,\s*["\'])',
                r'(YAHOO.util.Dom.removeClass\(\s*.*?,\s*["\'])',
                r'(.addClass\(\s*["\'])',                                       # jquery
                r'(.removeClass\(\s*["\'])',
                r'(\$\(\s*["\']\.)',
            )

            sub_js = (
                r'//.*?\n',                                                     # Remove JS Comments.
                r'/\*.*?\*/',
                r'(domClass.add\(\s*.*?,\s*["\'])',                             # dojo
                r'(domClass.add\(\s*.*?,\s*["\'])',
                r'(dojo.addClass\(\s*.*?,\s*["\'])',
                r'(domClass.remove\(\s*.*?,\s*["\'])',
                r'(dojo.removeClass\(\s*.*?,\s*["\'])',
                r'(YAHOO.util.Dom.addClass\(\s*.*?,\s*["\'])',                  # yui
                r'(YAHOO.util.Dom.hasClass\(\s*.*?,\s*["\'])',
                r'(YAHOO.util.Dom.removeClass\(\s*.*?,\s*["\'])',
                r'(.addClass\(\s*["\'])',                                       # jquery
                r'(.removeClass\(\s*["\'])',
                r'(\$\(\s*["\']\.)',
            )
            sub_html = (r'', ) + sub_js             # TODO: Missing HTML comment removal.
            sub_jinja = (r'{.*?}?}', ) + sub_html
            sub_django = (r'{.*?}?}', ) + sub_html
            sub_dotnet = (r'<%.*?%>', ) + sub_html  # TODO: Missing XHTML comment removal.
            sub_ruby = (r'<%.*?%>', ) + sub_html

            class_regex = (r'class="(.*?)"', )

            findall_regex_js = (
                r'.classList.add\(\s*[\'"](.*?)["\']\s*\)',
                r'.classList.remove\(\s*[\'"](.*?)["\']\s*\)',
                r'.className\s*\+?=\s*.*?\+?[\'"](.*?)["\']',
                r'.getElementsByClassName\(\s*[\'"](.*?)["\']\s*\)',
                r'.setAttribute\(\s*.*?,\s*[\'"](.*?)["\']\s*\)',
                js_substring + r'\(\s*[\'"](.*?)["\']\s*\)',                           # Find the chopped ones
            )

            findall_regex = class_regex + findall_regex_js

            self.file_type_dict = {
                '.js': {
                    'sub_regexes': sub_js,
                    'findall_regexes': findall_regex,
                },
                '.html': {
                    'sub_regexes': sub_html,
                    'findall_regexes': findall_regex,
                },
                '.jinja': {
                    'sub_regexes': sub_jinja,
                    'findall_regexes': findall_regex,
                },
                '.jinja2': {
                    'sub_regexes': sub_jinja,
                    'findall_regexes': findall_regex,
                },
                '.jnj': {
                    'sub_regexes': sub_jinja,
                    'findall_regexes': findall_regex,
                },
                '.ja': {
                    'sub_regexes': sub_jinja,
                    'findall_regexes': findall_regex,
                },
                '.djt': {
                    'sub_regexes': sub_django,
                    'findall_regexes': findall_regex,
                },
                '.djhtml': {
                    'sub_regexes': sub_django,
                    'findall_regexes': findall_regex,
                },
                '.aspx': {
                    'sub_regexes': sub_dotnet,
                    'findall_regexes': findall_regex,
                },
                '.ascx': {
                    'sub_regexes': sub_dotnet,
                    'findall_regexes': findall_regex,
                },
                '.master': {
                    'sub_regexes': sub_dotnet,
                    'findall_regexes': findall_regex,
                },
                '.erb': {
                    'sub_regexes': sub_ruby,
                    'findall_regexes': findall_regex,
                },
            }
        else:
            raise OSError(file_path + ' does not exist.')

    def is_valid_extension(self):
        """ Validates the extension. Returns whether True or False based on whether the extension is a key in
        ``file_type_dict``.

        :return: (*bool*) -- Returns True if the extension is a key in file_type_dict. Returns False otherwise.
        """
        return self.extension in self.file_type_dict

    @property
    def regex_dict(self):
        """ Validates ``self.extension`` and returns the associated regex dictionary for that extension.

        :return: Returns the regular expression dictionary associated with ``self.extension``.

        """
        self.is_valid_extension()
        return self.file_type_dict[self.extension]

class ClassExtractor(object):
    """ Given a file_regex_map of any type along with a substitution regex patterns and a findall regex patterns.
    Returns a minimum set of class selectors as ``class_set``.

    | **Parameters:**

    | **file_path** (*str*) -- Path to the file_regex_map to be parsed.

    | **sub_pattern** (*tuple of regexes*) -- Zero or more regex patterns to be removed from the file_regex_map text
      before further processing.

    | **findall_pattern** (*tuple of regexes*) -- Zero or more regex patterns used to find all class selectors
      in a given file.

    **Example Usage:**

    >>> from blowdrycss.classparser import ClassExtractor
    >>> # Assuming Default.aspx is located in the same directory
    >>> aspx_file = 'Default.aspx'
    >>> aspx_sub = r'<%.*?%>'
    >>> aspx_findall = r'class="(.*?)"'
    >>> class_extractor = ClassExtractor(file_path=aspx_file)
    >>> class_extractor.class_set
    {'row', 'padding-top-30', 'padding-bottom-30', 'bgc-green'}
    >>> jinja2_file = 'index.jinja2'
    >>> jinja2_sub = r'{.*?}?}'
    >>> jinja2_findall = r'class="(.*?)"'
    >>> class_extractor = ClassExtractor(file_path=jinja2_file, sub_regexes=jinja2_sub, findall_regexes=jinja2_findall)
    >>> class_extractor.class_set
    {'purple', 'padding-left-5', 'squirrel', 'text-align-center', 'large-up', 'border-1', 'row', 'text-align-center'}

    """
    def __init__(self, file_path=''):
        if path.isfile(file_path):
            self.file_path = file_path
            self.file_regex_map = FileRegexMap(file_path=file_path)
            regex_dict = self.file_regex_map.regex_dict
            self.sub_regexes = regex_dict['sub_regexes']
            self.findall_regexes = regex_dict['findall_regexes']
        else:
            raise OSError(file_path + ' does not exist.')

    @property
    def raw_class_list(self):
        """ Uses all sub_regexs and findall_regexes to extract space-delimited CSS class selector strings.
        Raw means space-delimited.

        Example: Look for the findall_regexes 'class="..."'. Extract the '...' part.

        sub_regexes() is used to remove template variables from quoted class selector strings in file text.
        findall_regexes() is used to find all class selector strings in a given files text.

        :return: (*list of strings*) -- Returns a list of raw class selector strings.

        """
        class_list = []
        with open(self.file_path, 'r', encoding='utf-8') as _file:
            text = _file.read()
            for sub_regex in self.sub_regexes:                      # Remove everything first.
                if sub_regex in self.file_regex_map.js_case:
                    text = sub(sub_regex, self.file_regex_map.js_replacement, text)
                else:
                    text = sub(sub_regex, '', text)
            for findall_regex in self.findall_regexes:              # Find everything second.
                class_list += findall(findall_regex, text)
            print(text)
        return class_list

    @property
    def class_set(self):
        """ Reduce the list of quoted class selector strings to a minimum set of classes. Returns the ``class_set``.

        :return: (*set of strings*) -- Return the minimum set of individual class selector strings.

        """
        class_set = set()

        logging.debug(msg='classparser.raw_class_list:\t' + str(self.raw_class_list))
        for classes in self.raw_class_list:
            class_set = set.union(
                set(classes.split()),               # Split space delimited string into set().
                class_set                           # Unite the new set with class_set.
            )                                       # Assign union() to class_set.
        return class_set

class ClassParser(object):
    """ Parses all project files provided by file_dict. HTML files are sent to ``HTMLClassParser``, and all other
    file types are sent to ``ClassExtractor``.

    **Parameters**

    **file_dict** (*dict*) -- Expecting FileFinder.file_dict as input.

    **Returns** None

    **Example**

    >>> from os import getcwd, chdir, path
    >>> from blowdrycss.filehandler import FileFinder
    >>> from blowdrycss.classparser import ClassParser
    >>> current_dir = getcwd()
    >>> chdir('..')
    >>> project_directory = path.join(current_dir, 'examplesite')
    >>> chdir(current_dir)    # Change it back.
    >>> file_finder = FileFinder(project_directory=project_directory)
    >>> file_dict = file_finder.file_dict
    >>> general_class_parser = ClassParser(file_dict=file_dict)
    >>> general_class_parser.class_set
    { Returns a complete set of all the classes discovered after looking in at all file paths. }

    """
    def __init__(self, file_dict):
        # Handled 'html' files with HTMLClassParser, and remove *.html files from file_dict.
        if '.html' in file_dict:
            self.html_class_parser = HTMLClassParser(files=file_dict['.html'])
            del file_dict['.html']
            self.class_set = self.html_class_parser.class_set
        else:
            self.html_class_parser = None
            self.class_set = set()

        self.file_dict = file_dict

        self.file_path_list = []
        self.build_file_path_list()

        logging.debug(msg='classparser.html_class_parser.class_set:\t' + str(self.class_set))
        self.build_class_set()

    def build_file_path_list(self):
        """ Builds a list of all of the file paths regardless of type.

        :return: None

        """
        keys = list(self.file_dict)
        for key in keys:
            self.file_path_list += self.file_dict[key]

    def build_class_set(self):
        """ Builds a complete set of all the classes discovered after looking in at all file paths.

        :return: None

        """
        for file_path in self.file_path_list:
            class_extractor = ClassExtractor(file_path=file_path)
            logging.debug(msg='classparser.class_extractor.class_set:\t' + str(class_extractor.class_set))
            self.class_set = self.class_set.union(class_extractor.class_set)
            logging.debug(msg='classparser final class_set:\t' + str(self.class_set))
<|endoftext|>


<commit_before>from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.management import call_command
from django.db import transaction
from django.test import LiveServerTestCase
from django.test.testcases import connections_support_transactions, disable_transaction_methods, restore_transaction_methods

from splinter import Browser

def css_dict(style):
    """
    Returns a dict from a style attribute value.

    Usage::

        >>> css_dict('width: 2.2857142857142856%; overflow: hidden;')
        {'overflow': 'hidden', 'width': '2.2857142857142856%'}

        >>> css_dict('width:2.2857142857142856%;overflow:hidden')
        {'overflow': 'hidden', 'width': '2.2857142857142856%'}

    """
    if not style:
        return {}

    try:
        return dict([(k.strip(), v.strip()) for k, v in [prop.split(':') for prop in style.rstrip(';').split(';')]])
    except ValueError, e:
        raise ValueError('Could not parse CSS: %s (%s)' % (style, e))

class SeleniumTestCase(LiveServerTestCase):
    """
    Selenium test cases should inherit from this class.

    Wrapper around ``LiveServerTestCase`` to provide a standard browser instance. In addition it performs some tests to
    make sure all settings are correct.
    """

    @classmethod
    def setUpClass(cls):
        """
        Prepare the test class rather then doing this for every individual test.
        """
        if not hasattr(settings, 'SELENIUM_WEBDRIVER'):
            raise ImproperlyConfigured('Define SELENIUM_WEBDRIVER in your settings.py.')

        cls.browser = Browser(settings.SELENIUM_WEBDRIVER)

        super(SeleniumTestCase, cls).setUpClass()

    @classmethod
    def tearDownClass(cls):
        """
        Make sure the browser quits afterwards.
        """
        super(SeleniumTestCase, cls).tearDownClass()

        cls.browser.quit()

    def _fixture_setup(self):
        """
        Implement the same logic as ``django.test.TestCase`` to make fixture loading more smooth.
        """
        if not connections_support_transactions():
            return super(SeleniumTestCase, self)._fixture_setup()

        assert not self.reset_sequences, 'reset_sequences cannot be used on TestCase instances'

        for db_name in self._databases_names():
            transaction.enter_transaction_management(using=db_name)
            transaction.managed(True, using=db_name)
        disable_transaction_methods()

        from django.contrib.sites.models import Site
        Site.objects.clear_cache()

        for db in self._databases_names(include_mirrors=False):
            if hasattr(self, 'fixtures'):
                call_command('loaddata', *self.fixtures,
                             **{
                                'verbosity': 0,
                                'commit': False,
                                'database': db,
                                'skip_validation': True,
                             })

    def _fixture_teardown(self):
        """
        Implement the same logic as ``django.test.TestCase`` to make fixture loading more smooth.
        """
        if not connections_support_transactions():
            return super(SeleniumTestCase, self)._fixture_teardown()

        restore_transaction_methods()
        for db in self._databases_names():
            transaction.rollback(using=db)
            transaction.leave_transaction_management(using=db)

    def login(self, username, password):
        """
        Perform login operation on the website.

        :param username: The user's email address.
        :param password: The user's password
        :return: ``True`` if login was successful.
        """
        self.visit_homepage()

        # Find the link to the signup button page and click it.
        self.browser.find_link_by_text('Login').first.click()

        # Validate that we are on the intended page.
        if not self.browser.is_text_present('LOG IN', wait_time=10):
            return False

        # Fill in details.
        self.browser.fill('username', username)
        self.browser.fill('password', password)

        self.browser.find_by_value('Login').first.click()

        return self.browser.is_text_present('MY 1%', wait_time=10)

    def visit_path(self, path, lang_code=None):
        """
        Visits a relative path of the website.

        :param path: The relative URL path.
        :param lang_code: A two letter language code as used in the URL.
        """
        if lang_code is None:
            lang_code = 'en'

        if path and not path.startswith('#'):
            path = '#%s' % path

        # Open the homepage (always the starting point), in English.
        return self.browser.visit('%(url)s/%(lang_code)s/%(path)s' % {
            'url': self.live_server_url,
            'lang_code': lang_code,
            'path': path
        })

    def visit_homepage(self, lang_code=None):
        """
        Convenience function to open the homepage.

        :param lang_code: A two letter language code as used in the URL.
        :return: ``True`` if the homepage could be visited.
        """
        # Open the homepage, in the specified language.
        self.visit_path('', lang_code)

        # # Check if the homepage opened, and the dynamically loaded content appeared.
        # # Remember that
        # self.assertTrue(self.browser.is_text_present('CHOOSE YOUR PROJECT', wait_time=10),
        #         'Cannot load the homepage. Did you load any data fixtures for testing?')
        return self.browser.is_element_present_by_id('title', wait_time=10)

    def fill_form_by_css(self, form, data):
        """

        :param form: The form ``WebElement``.
        :param data: A dict in the form ``{'input css': 'value'}``.
        """

        if not isinstance(data, dict):
            raise RuntimeError('Argument data must be dict.')

        # Fill in the form.
        for css, val in data.items():
            form.find_by_css(css).first.fill(val)

    def fill_form_by_label(self, form, data):
        """
        Fills in a form by label.

        NOTE: This function works best if you define all labels and input elements in your data.

        :param form: The form ``WebElement``.
        :param data: List of tuples in the form ``[('label', 'value'), ...]``. The value can also be a list if multiple
                     inputs are connected to a single label.

        Example::

            # ...
            self.fill_form_by_label(
                self.browser.find_by_tag('form'),
                [
                    ('Name', ['John', 'Doe']),
                    ('Email', 'johndoe@onepercentclub.com'),
                ]
            )

        """
        if not isinstance(data, list):
            raise RuntimeError('Argument data must be a list of tuples.')

        labels = form.find_by_tag('label')
        inputs = form.find_by_css('input, textarea, select')

        # Fill in the form. Keep an offset for if multiple inputs are used.
        offset = 0
        for label_text, values in data:
            if not isinstance(values, list):
                values = [values]

            for index, form_label in enumerate(labels):
                if form_label.text.strip('\r\n ') == label_text:
                    for i, val in enumerate(values):
                        offset += i

                        if val is None:
                            continue

                        form_input = inputs[index + offset]
                        form_input_tag_name = form_input.tag_name

                        if form_input_tag_name == 'input':
                            form_input_type = form_input['type']

                            if form_input_type == 'file':
                                form_input.attach_file(val)
                            elif form_input_type == 'checkbox':
                                if val:
                                    form_input.check()
                                else:
                                    form_input.uncheck()
                            elif form_input_type == 'radio':
                                form_input.choose(val)
                            else:
                                form_input.fill(val)
                        elif form_input_tag_name == 'select':
                            # Workaround for form_input.select(val) which uses the name attribute to find the options.
                            # However, some select elements do not have a name attribute.
                            # TODO: Report issue found in Splinter 0.5.3
                            for option in form_input.find_by_tag('option'):
                                if option['value'] == val:
                                    option.click()
                                    break
                        else:
                            form_input.fill(val)
                    break
<commit_msg>Moved generic browser-like helper methods to a new Browser class. This class is now used for Selenium tests.<commit_after>from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.management import call_command
from django.db import transaction
from django.test import LiveServerTestCase
from django.test.testcases import connections_support_transactions, disable_transaction_methods, restore_transaction_methods

from splinter.browser import _DRIVERS
from splinter.element_list import ElementList
from splinter.exceptions import DriverNotFoundError

def css_dict(style):
    """
    Returns a dict from a style attribute value.

    Usage::

        >>> css_dict('width: 2.2857142857142856%; overflow: hidden;')
        {'overflow': 'hidden', 'width': '2.2857142857142856%'}

        >>> css_dict('width:2.2857142857142856%;overflow:hidden')
        {'overflow': 'hidden', 'width': '2.2857142857142856%'}

    """
    if not style:
        return {}

    try:
        return dict([(k.strip(), v.strip()) for k, v in [prop.split(':') for prop in style.rstrip(';').split(';')]])
    except ValueError, e:
        raise ValueError('Could not parse CSS: %s (%s)' % (style, e))

def BrowserExt(driver_name='firefox', *args, **kwargs):
    """
    Small helper to combine the correct webdriver with some additional methods without cloning the project.
    """
    try:
        driver_class = _DRIVERS[driver_name]
    except KeyError:
        raise DriverNotFoundError("No driver for %s" % driver_name)

    new_class = type('BrowserExt', (driver_class, WebDriverAdditionMixin), {})

    return new_class(*args, **kwargs)

class WebDriverAdditionMixin(object):
    """
    Additional helper methods for the web driver.
    """
    def fill_form_by_css(self, form, data):
        """
        Fills in a form by finding input elements by CSS.

        :param form: The form ``WebElement``.
        :param data: A dict in the form ``{'input css': 'value'}``.
        """

        if not isinstance(data, dict):
            raise RuntimeError('Argument data must be dict.')

        # Fill in the form.
        for css, val in data.items():
            form.find_by_css(css).first.fill(val)

    def fill_form_by_label(self, form, data):
        """
        Fills in a form by finding labels.

        NOTE: This function works best if you define all labels and input elements in your data.

        :param form: The form ``WebElement``.
        :param data: List of tuples in the form ``[('label', 'value'), ...]``. The value can also be a list if multiple
                     inputs are connected to a single label.

        Example::

            # ...
            self.fill_form_by_label(
                self.browser.find_by_tag('form'),
                [
                    ('Name', ['John', 'Doe']),
                    ('Email', 'johndoe@onepercentclub.com'),
                ]
            )

        """
        if not isinstance(data, list):
            raise RuntimeError('Argument data must be a list of tuples.')

        labels = form.find_by_tag('label')
        inputs = form.find_by_css('input, textarea, select')

        # Fill in the form. Keep an offset for if multiple inputs are used.
        offset = 0
        for label_text, values in data:
            if not isinstance(values, list):
                values = [values]

            for index, form_label in enumerate(labels):
                if form_label.text.strip('\r\n ') == label_text:
                    for i, val in enumerate(values):
                        offset += i

                        if val is None:
                            continue

                        form_input = inputs[index + offset]
                        form_input_tag_name = form_input.tag_name

                        if form_input_tag_name == 'input':
                            form_input_type = form_input['type']

                            if form_input_type == 'file':
                                form_input.attach_file(val)
                            elif form_input_type == 'checkbox':
                                if val:
                                    form_input.check()
                                else:
                                    form_input.uncheck()
                            elif form_input_type == 'radio':
                                radio_group = form_input['name']
                                self.choose(radio_group, val)
                            else:
                                form_input.fill(val)
                        elif form_input_tag_name == 'select':
                            # Workaround for form_input.select(val) which uses the name attribute to find the options.
                            # However, some select elements do not have a name attribute.
                            # TODO: Report issue found in Splinter 0.5.3
                            for option in form_input.find_by_tag('option'):
                                if option['value'] == val:
                                    option.click()
                                    break
                        else:
                            form_input.fill(val)
                    break

    def find_link_by_itext(self, text, exact=False):
        """
        Finds a link by text in a more robust way than the default method. Also allows for case sensitive and
        insensitive matches.

        :param text: The text to search for within a link element.
        :param exact: ``True`` if the match mut be an exact match. ``False`` (default) for case insensitive matches.

        :return: List of matching elements.
        """
        result = []
        for link in self.find_by_css('a, button, input[type="button"], input[type="submit"]'):
            if link.text == text or (not exact and link.text.lower() == text.lower()):
                result.append(link)
        return ElementList(result, find_by='link by itext', query=text)

class SeleniumTestCase(LiveServerTestCase):
    """
    Selenium test cases should inherit from this class.

    Wrapper around ``LiveServerTestCase`` to provide a standard browser instance. In addition it performs some tests to
    make sure all settings are correct.
    """

    @classmethod
    def setUpClass(cls):
        """
        Prepare the test class rather then doing this for every individual test.
        """
        if not hasattr(settings, 'SELENIUM_WEBDRIVER'):
            raise ImproperlyConfigured('Define SELENIUM_WEBDRIVER in your settings.py.')

        cls.browser = BrowserExt(settings.SELENIUM_WEBDRIVER)

        super(SeleniumTestCase, cls).setUpClass()

    @classmethod
    def tearDownClass(cls):
        """
        Make sure the browser quits afterwards.
        """
        super(SeleniumTestCase, cls).tearDownClass()

        cls.browser.quit()

    def _fixture_setup(self):
        """
        Implement the same logic as ``django.test.TestCase`` to make fixture loading more smooth.
        """
        if not connections_support_transactions():
            return super(SeleniumTestCase, self)._fixture_setup()

        assert not self.reset_sequences, 'reset_sequences cannot be used on TestCase instances'

        for db_name in self._databases_names():
            transaction.enter_transaction_management(using=db_name)
            transaction.managed(True, using=db_name)
        disable_transaction_methods()

        from django.contrib.sites.models import Site
        Site.objects.clear_cache()

        for db in self._databases_names(include_mirrors=False):
            if hasattr(self, 'fixtures'):
                call_command('loaddata', *self.fixtures,
                             **{
                                'verbosity': 0,
                                'commit': False,
                                'database': db,
                                'skip_validation': True,
                             })

    def _fixture_teardown(self):
        """
        Implement the same logic as ``django.test.TestCase`` to make fixture loading more smooth.
        """
        if not connections_support_transactions():
            return super(SeleniumTestCase, self)._fixture_teardown()

        restore_transaction_methods()
        for db in self._databases_names():
            transaction.rollback(using=db)
            transaction.leave_transaction_management(using=db)

    def login(self, username, password):
        """
        Perform login operation on the website.

        :param username: The user's email address.
        :param password: The user's password
        :return: ``True`` if login was successful.
        """
        self.visit_homepage()

        # Find the link to the signup button page and click it.
        self.browser.find_link_by_text('Login').first.click()

        # Validate that we are on the intended page.
        if not self.browser.is_text_present('LOG IN', wait_time=10):
            return False

        # Fill in details.
        self.browser.fill('username', username)
        self.browser.fill('password', password)

        self.browser.find_by_value('Login').first.click()

        return self.browser.is_text_present('MY 1%', wait_time=10)

    def visit_path(self, path, lang_code=None):
        """
        Visits a relative path of the website.

        :param path: The relative URL path.
        :param lang_code: A two letter language code as used in the URL.
        """
        if lang_code is None:
            lang_code = 'en'

        if path and not path.startswith('#'):
            path = '#%s' % path

        # Open the homepage (always the starting point), in English.
        return self.browser.visit('%(url)s/%(lang_code)s/%(path)s' % {
            'url': self.live_server_url,
            'lang_code': lang_code,
            'path': path
        })

    def visit_homepage(self, lang_code=None):
        """
        Convenience function to open the homepage.

        :param lang_code: A two letter language code as used in the URL.
        :return: ``True`` if the homepage could be visited.
        """
        # Open the homepage, in the specified language.
        self.visit_path('', lang_code)

        # # Check if the homepage opened, and the dynamically loaded content appeared.
        # # Remember that
        # self.assertTrue(self.browser.is_text_present('CHOOSE YOUR PROJECT', wait_time=10),
        #         'Cannot load the homepage. Did you load any data fixtures for testing?')
        return self.browser.is_element_present_by_id('title', wait_time=10)

<|endoftext|>


<commit_before>#
# Copyright (c) 2005-2007 rPath, Inc.
#
# All Rights Reserved
#

from mint import database

(RDT_STRING,
 RDT_BOOL,
 RDT_INT,
 RDT_ENUM,
 RDT_TROVE)= range(5)

class GenericDataTable(database.DatabaseTable):
    name = None

    def __init__(self, db):
        if not self.name:
            return NotImplementedError
        self.lowered = self.name[0].lower() + self.name[1:]
        self.front = self.lowered.replace('Data', '')
        self.fields = ['%sId' % self.front, 'name', 'value', 'dataType']
        return database.DatabaseTable.__init__(self, db)

    def setDataValue(self, id, name, value, dataType, commit=True):
        # do any data conversions necessary to safely store value as a string
        if dataType == RDT_BOOL:
            value=str(int(value))
        elif dataType == RDT_INT:
            value=str(value)

        cu = self.db.cursor()

        # audited for SQL injection
        cu.execute("DELETE FROM %s WHERE %sId=? AND name=?" % (self.name, self.front), id, name)
        # audited for SQL injection
        cu.execute("INSERT INTO %s (%sId, name, value, dataType) VALUES(?, ?, ?, ?)" % (self.name, self.front),
                   (id, name, value, dataType))
        if commit:
            self.db.commit()
        return True

    def getDataValue(self, id, name):
        # this function returns a tuple: isPresent, value to avoid
        # passing None to indicate that no value is set, since we don't
        # allow our XMLRPC server to pass None values.
        cu = self.db.cursor()
        # audited for SQL injection
        cu.execute("SELECT value, dataType FROM %s WHERE %sId=? AND name=?" % (self.name, self.front), (id, name))
        res = cu.fetchall()
        if len(res) != 1:
            return False, 0
        value, dataType = res[0]
        if dataType == RDT_BOOL:
            value=bool(int(value))
        elif dataType == RDT_INT:
            value=int(value)
        return True, value

    def getDataDict(self, id):
        cu = self.db.cursor()
        # audited for SQL injection
        cu.execute("SELECT name, value, dataType FROM %s WHERE %sId=?" % (self.name, self.front), id)
        dataDict = {}
        for name, value, dataType in cu.fetchall():
            if dataType == RDT_BOOL:
                value=bool(int(value))
            elif dataType == RDT_INT:
                value=int(value)
            dataDict[name] = value
        return dataDict

    def removeDataValue(self, id, name, commit=True):
        cu = self.db.cursor()

        cu.execute("DELETE FROM %s WHERE %sId=? AND name=?" % (self.name, self.front), id, name)
        if commit:
            self.db.commit()

        return True

class JobDataTable(GenericDataTable):
    name = "JobData"

class UserDataTable(GenericDataTable):
    name = "UserData"

# XXX This table is deprecated in favor of BuildDataTable
class ReleaseDataTable(GenericDataTable):
    name = "ReleaseData"

class BuildDataTable(GenericDataTable):
    name = "BuildData"

<commit_msg>Remove some redundancy<commit_after>#
# Copyright (c) 2005-2007 rPath, Inc.
#
# All Rights Reserved
#

from mint import database

(RDT_STRING,
 RDT_BOOL,
 RDT_INT,
 RDT_ENUM,
 RDT_TROVE)= range(5)

class GenericDataTable(database.DatabaseTable):
    name = None

    def __init__(self, db):
        if not self.name:
            return NotImplementedError
        self.lowered = self.name[0].lower() + self.name[1:]
        self.front = self.lowered.replace('Data', '')
        self.fields = ['%sId' % self.front, 'name', 'value', 'dataType']
        return database.DatabaseTable.__init__(self, db)

    def setDataValue(self, id, name, value, dataType, commit=True):
        # do any data conversions necessary to safely store value as a string
        if dataType == RDT_BOOL:
            value=str(int(value))
        elif dataType == RDT_INT:
            value=str(value)

        cu = self.db.cursor()

        self.removeDataValue(id, name, commit=False)

        # audited for SQL injection
        cu.execute("INSERT INTO %s (%sId, name, value, dataType) VALUES(?, ?, ?, ?)" % (self.name, self.front),
                   (id, name, value, dataType))
        if commit:
            self.db.commit()
        return True

    def getDataValue(self, id, name):
        # this function returns a tuple: isPresent, value to avoid
        # passing None to indicate that no value is set, since we don't
        # allow our XMLRPC server to pass None values.
        cu = self.db.cursor()
        # audited for SQL injection
        cu.execute("SELECT value, dataType FROM %s WHERE %sId=? AND name=?" % (self.name, self.front), (id, name))
        res = cu.fetchall()
        if len(res) != 1:
            return False, 0
        value, dataType = res[0]
        if dataType == RDT_BOOL:
            value=bool(int(value))
        elif dataType == RDT_INT:
            value=int(value)
        return True, value

    def getDataDict(self, id):
        cu = self.db.cursor()
        # audited for SQL injection
        cu.execute("SELECT name, value, dataType FROM %s WHERE %sId=?" % (self.name, self.front), id)
        dataDict = {}
        for name, value, dataType in cu.fetchall():
            if dataType == RDT_BOOL:
                value=bool(int(value))
            elif dataType == RDT_INT:
                value=int(value)
            dataDict[name] = value
        return dataDict

    def removeDataValue(self, id, name, commit=True):
        cu = self.db.cursor()

        cu.execute("DELETE FROM %s WHERE %sId=? AND name=?" % (self.name, self.front), id, name)
        if commit:
            self.db.commit()

        return True

class JobDataTable(GenericDataTable):
    name = "JobData"

class UserDataTable(GenericDataTable):
    name = "UserData"

# XXX This table is deprecated in favor of BuildDataTable
class ReleaseDataTable(GenericDataTable):
    name = "ReleaseData"

class BuildDataTable(GenericDataTable):
    name = "BuildData"

<|endoftext|>