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

class VlcPlayer(BasePlayer):
    speedSupported = True
    RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX)
    SLAVE_ARGS = constants.VLC_SLAVE_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
    SLAVE_ARGS.append('--lua-config=syncplay={{port=\"{}\"}}'.format(str(vlcport)))

    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

        self._durationAsk = threading.Event()
        self._filenameAsk = threading.Event()
        self._pathAsk = threading.Event()
        self._positionAsk = threading.Event()
        self._pausedAsk = threading.Event()
        self._vlcready = threading.Event()
        try:
            self._listener = self.__Listener(self, playerPath, filePath, args, self._vlcready)
        except ValueError:
            self._client.ui.showErrorMessage(getMessage("en", "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("en", "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._filechanged  == False:
            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):
        duration /= 1000
        self._listener.sendLine('display-osd: {}, {}, {}'.format('top-right', duration, message))

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

    def setPosition(self, value):
        self._position = value
        self._listener.sendLine("set-position: {:n}".format(value))

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

    def openFile(self, filePath):
        self._listener.sendLine('load-file: {}'.format(filePath))

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

    def lineReceived(self, line):
        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" and value != "no-input"):
            self._filechanged = True
            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" and (value != "no-input")):
            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
            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("en", "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("en", "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 and VlcPlayer.getExpandedPath(path)):
            return True
        return False

    @staticmethod
    def getExpandedPath(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._listener.sendLine('close-vlc')
        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):
            self.__playerController = playerController
            call = [playerPath]
            if(filePath):
                call.append(filePath)
            call.extend(playerController.SLAVE_ARGS)
            if(args):
                call.extend(args)

            self._vlcready = vlcReady
            self.__process = subprocess.Popen(call, stderr=subprocess.PIPE)
            for line in iter(self.__process.stderr.readline,''):
                if "[syncplay]" in line:
                    if "Listening on host" in line:
                        break
                    elif "lua interface error" in line:
                        playerController._client.ui.showErrorMessage("VLC Error: " + line)
                        playerController._client.ui.showErrorMessage(getMessage("en", "vlc-failed-connection"), True)
                        break
            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):
#            print "received: {}".format("".join(self._ibuffer))
            self.__playerController.lineReceived("".join(self._ibuffer))
            self._ibuffer = []

        def sendLine(self, line):
            if(self.connected):
#                print "send: {}".format(line)
                self.push(line + "\n")
<commit_msg>Improved VLC handling of unicode files<commit_after>import subprocess
import re
import threading
from syncplay.players.basePlayer import BasePlayer
from syncplay import constants
import os
import random
import socket
import asynchat, asyncore
from syncplay.messages import getMessage
import time

class VlcPlayer(BasePlayer):
    speedSupported = True
    RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX)
    SLAVE_ARGS = constants.VLC_SLAVE_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
    SLAVE_ARGS.append('--lua-config=syncplay={{port=\"{}\"}}'.format(str(vlcport)))

    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

        self._durationAsk = threading.Event()
        self._filenameAsk = threading.Event()
        self._pathAsk = threading.Event()
        self._positionAsk = threading.Event()
        self._pausedAsk = threading.Event()
        self._vlcready = threading.Event()
        try:
            self._listener = self.__Listener(self, playerPath, filePath, args, self._vlcready)
        except ValueError:
            self._client.ui.showErrorMessage(getMessage("en", "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("en", "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._filechanged  == False:
            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):
        duration /= 1000
        self._listener.sendLine('display-osd: {}, {}, {}'.format('top-right', duration, message.encode('ascii','ignore'))) #TODO: Proper Unicode support

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

    def setPosition(self, value):
        self._position = value
        self._listener.sendLine("set-position: {:n}".format(value))

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

    def _isASCII (self, s):
        return all(ord(c) < 256 for c in s)

    def openFile(self, filePath):
        if (self._isASCII(filePath) == True):
            self._listener.sendLine('load-file: {}'.format(filePath.encode('ascii','ignore'))) #TODO: Proper Unicode support
        else:
            self._client.ui.showErrorMessage(getMessage("en", "vlc-unicode-loadfile-error"), True)

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

    def lineReceived(self, line):
        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" and value != "no-input"):
            self._filechanged = True
            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" and (value != "no-input")):
            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
            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("en", "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("en", "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 and VlcPlayer.getExpandedPath(path)):
            return True
        return False

    @staticmethod
    def getExpandedPath(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._listener.sendLine('close-vlc')
        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):
            self.__playerController = playerController
            call = [playerPath]
            if(filePath):
                if (self.__playerController._isASCII(filePath) == True):
                    call.append(filePath) #TODO: Proper Unicode support
                else:
                    playerController._client.ui.showErrorMessage(getMessage("en", "vlc-unicode-loadfile-error"), True)
            call.extend(playerController.SLAVE_ARGS)
            if(args):
                call.extend(args)

            self._vlcready = vlcReady
            self.__process = subprocess.Popen(call, stderr=subprocess.PIPE)
            for line in iter(self.__process.stderr.readline,''):
                if "[syncplay]" in line:
                    if "Listening on host" in line:
                        break
                    elif "lua interface error" in line:
                        playerController._client.ui.showErrorMessage("VLC Error: " + line)
                        playerController._client.ui.showErrorMessage(getMessage("en", "vlc-failed-connection"), True)
                        break
            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):
#            print "received: {}".format("".join(self._ibuffer))
            self.__playerController.lineReceived("".join(self._ibuffer))
            self._ibuffer = []

        def sendLine(self, line):
            if(self.connected):
#                print "send: {}".format(line)
                self.push(line + "\n")
<|endoftext|>


<commit_before># Copyright (c) 2011-2021, Manfred Moitzi
# License: MIT License
from typing import TYPE_CHECKING, Iterable, Iterator, Optional, List, Dict, cast
from collections import OrderedDict
import logging

from ezdxf.lldxf import const, validator
from ezdxf.entities.table import TableHead
from ezdxf.entities import factory

if TYPE_CHECKING:
    from ezdxf.eztypes import (
        TagWriter,
        EntityDB,
        Drawing,
        DXFEntity,
        Layer,
        Linetype,
    )

logger = logging.getLogger("ezdxf")

TABLENAMES = {
    "LAYER": "LAYERS",
    "LTYPE": "LINETYPES",
    "APPID": "APPIDS",
    "DIMSTYLE": "DIMSTYLES",
    "STYLE": "STYLES",
    "UCS": "UCS",
    "VIEW": "VIEWS",
    "VPORT": "VIEWPORTS",
    "BLOCK_RECORD": "BLOCK_RECORDS",
}

def tablename(dxfname: str) -> str:
    """Translate DXF-table-name to attribute-name. ('LAYER' -> 'LAYERS')"""
    name = dxfname.upper()
    name = TABLENAMES.get(name, name + "S")
    return name

def table_key(name: str) -> str:
    """Unified table entry key."""
    # see also comments for ezdxf.lldxf.validator.is_valid_table_name()
    if not isinstance(name, str):
        raise TypeError("Name has to be a string.")
    return name.lower()  # table key is lower case

class Table:
    def __init__(
        self, doc: "Drawing" = None, entities: Iterable["DXFEntity"] = None
    ):
        self.doc = doc
        self.entries: Dict[str, DXFEntity] = OrderedDict()
        self._head = TableHead()
        if entities is not None:
            self.load(iter(entities))

    def load(self, entities: Iterator["DXFEntity"]) -> None:
        """Loading interface. (internal API)"""
        table_head = next(entities)
        if isinstance(table_head, TableHead):
            self._head = table_head
        else:
            raise const.DXFStructureError(
                "Critical structure error in TABLES section."
            )
        expected_entry_dxftype = self.entry_dxftype
        for table_entry in entities:
            if table_entry.dxftype() == expected_entry_dxftype:
                self._append(table_entry)
            else:
                logger.warning(
                    f"Ignored invalid DXF entity type '{table_entry.dxftype()}'"
                    f" in table '{self.name}'."
                )

    @classmethod
    def new_table(cls, name: str, handle: str, doc: "Drawing") -> "Table":
        """Create new table. (internal API)"""
        table = cls(doc)
        table._set_head(name, handle)
        return table

    def _set_head(self, name: str, handle: str = None) -> None:
        self._head = TableHead.new(
            handle, owner="0", dxfattribs={"name": name}, doc=self.doc
        )

    @property
    def head(self):
        """Returns table head entry."""
        return self._head

    @property
    def entry_dxftype(self) -> str:
        return self._head.dxf.name

    @staticmethod
    def key(name: str) -> str:
        """Unified table entry key."""
        return table_key(name)

    @property
    def name(self) -> str:
        """Table name like ``layers``."""
        return tablename(self.entry_dxftype)

    def has_entry(self, name: str) -> bool:
        """Returns ``True`` if an table entry `name` exist."""
        return self.key(name) in self.entries

    __contains__ = has_entry

    def __len__(self) -> int:
        """Count of table entries."""
        return len(self.entries)

    def __iter__(self) -> Iterator["DXFEntity"]:
        """Iterable of all table entries."""
        for e in self.entries.values():
            if e.is_alive:
                yield e

    def new(self, name: str, dxfattribs: dict = None) -> "DXFEntity":
        """Create a new table entry `name`.

        Args:
            name: name of table entry, case insensitive
            dxfattribs: additional DXF attributes for table entry

        """
        if self.has_entry(name):
            raise const.DXFTableEntryError(
                f"{self._head.dxf.name} {name} already exists!"
            )
        dxfattribs = dxfattribs or {}
        dxfattribs["name"] = name
        dxfattribs["owner"] = self._head.dxf.handle
        return self.new_entry(dxfattribs)

    def get(self, name: str) -> "DXFEntity":
        """Get table entry `name` (case insensitive).
        Raises :class:`DXFValueError` if table entry does not exist.
        """
        key = self.key(name)
        entry = self.entries.get(key, None)
        if entry:
            return entry
        else:
            raise const.DXFTableEntryError(name)

    def remove(self, name: str) -> None:
        """Removes table entry `name`. Raises :class:`DXFValueError`
        if table-entry does not exist.
        """
        key = self.key(name)
        entry = self.get(name)
        self.entitydb.delete_entity(entry)
        self.discard(key)

    def duplicate_entry(self, name: str, new_name: str) -> "DXFEntity":
        """Returns a new table entry `new_name` as copy of `name`,
        replaces entry `new_name` if already exist.

        Raises:
             DXFValueError: `name` does not exist

        """
        entry = self.get(name)
        entitydb = self.entitydb
        if entitydb:
            new_entry = entitydb.duplicate_entity(entry)
        else:  # only for testing!
            new_entry = entry.copy()
        new_entry.dxf.name = new_name
        self._append(new_entry)
        return new_entry

    def discard(self, name: str) -> None:
        """Remove table entry without destroying object. (internal API)"""
        del self.entries[self.key(name)]

    def replace(self, name: str, entry: "DXFEntity") -> None:
        """Replace table entry `name` by new `entry`. (internal API)"""
        self.discard(name)
        self._append(entry)

    @property
    def entitydb(self) -> "EntityDB":
        return self.doc.entitydb  # type: ignore

    def new_entry(self, dxfattribs: dict) -> "DXFEntity":
        """Create and add new table-entry of type 'self.entry_dxftype'.

        Does not check if an entry dxfattribs['name'] already exists!
        Duplicate entries are possible for Viewports.
        """
        entry = factory.create_db_entry(
            self._head.dxf.name, dxfattribs, self.doc  # type: ignore
        )
        self._append(entry)
        return entry

    def _append(self, entry: "DXFEntity") -> None:
        """Add a table entry, replaces existing entries with same name.
        (internal API).
        """
        assert entry.dxftype() == self.entry_dxftype
        self.entries[self.key(entry.dxf.name)] = entry

    def add_entry(self, entry: "DXFEntity") -> None:
        """Add a table `entry`, created by other object than this table.
        (internal API)
        """
        if entry.dxftype() != self.entry_dxftype:
            raise const.DXFTypeError(
                f"Invalid table entry type {entry.dxftype()} "
                f"for table {self.name}"
            )
        name = entry.dxf.name
        if self.has_entry(name):
            raise const.DXFTableEntryError(
                f"{self._head.dxf.name} {name} already exists!"
            )
        entry.doc = self.doc
        entry.dxf.owner = self._head.dxf.handle
        self._append(entry)

    def export_dxf(self, tagwriter: "TagWriter") -> None:
        """Export DXF representation. (internal API)"""

        def prologue():
            self.update_owner_handles()
            # The table head itself has no owner and is therefore always '0':
            self._head.dxf.owner = "0"
            self._head.dxf.count = len(self)
            self._head.export_dxf(tagwriter)

        def content():
            for entry in self.entries.values():
                # VPORT
                if isinstance(entry, list):
                    for e in entry:
                        e.export_dxf(tagwriter)
                else:
                    entry.export_dxf(tagwriter)

        def epilogue():
            tagwriter.write_tag2(0, "ENDTAB")

        prologue()
        content()
        epilogue()

    def update_owner_handles(self) -> None:
        owner_handle = self._head.dxf.handle
        for entry in self.entries.values():
            entry.dxf.owner = owner_handle

    def set_handle(self, handle: str):
        """Set new `handle` for table, updates also :attr:`owner` tag of table
        entries. (internal API)
        """
        if self._head.dxf.handle is None:
            self._head.dxf.handle = handle
            self.update_owner_handles()

class LayerTable(Table):
    def new_entry(self, dxfattribs: dict) -> "DXFEntity":
        layer = cast("Layer", super().new_entry(dxfattribs))
        if self.doc:
            layer.set_required_attributes()
        return layer

    def add(
        self,
        name: str,
        *,
        color: int = const.BYLAYER,
        true_color: int = None,
        linetype: str = "Continuous",
        lineweight: int = const.LINEWEIGHT_BYLAYER,
        plot: bool = True,
        dxfattribs: Dict = None,
    ) -> "Layer":
        """Add a new :class:`~ezdxf.entities.Layer`.

        Args:
            name (str): layer name
            color (int): :ref:`ACI` value, default is BYLAYER
            true_color (int): true color value, use :func:`ezdxf.rgb2int` to
                create ``int`` values from RGB values
            linetype (str): line type name, default is "Continuous"
            lineweight (int): line weight, default is BYLAYER
            plot (bool): plot layer as bool, default is ``True``
            dxfattribs (dict): additional DXF attributes

        .. versionadded:: 0.17

        """
        dxfattribs = dxfattribs or {}
        if validator.is_valid_aci_color(color):
            dxfattribs["color"] = color
        else:
            raise const.DXFValueError(f"invalid color: {color}")
        dxfattribs["linetype"] = linetype
        if validator.is_valid_lineweight(lineweight):
            dxfattribs["lineweight"] = lineweight
        else:
            raise const.DXFValueError(f"invalid lineweight: {lineweight}")
        if true_color is not None:
            dxfattribs["true_color"] = int(true_color)
        dxfattribs["plot"] = int(plot)
        return self.new(name, dxfattribs)  # type: ignore

class LineTypeTable(Table):
    def new_entry(self, dxfattribs: dict) -> "DXFEntity":
        pattern = dxfattribs.pop("pattern", [0.0])
        length = dxfattribs.pop("length", 0)  # required for complex types
        ltype = cast("Linetype", super().new_entry(dxfattribs))
        ltype.setup_pattern(pattern, length)
        return ltype

class StyleTable(Table):
    def get_shx(self, shxname: str) -> "DXFEntity":
        """Get existing shx entry, or create a new entry.

        Args:
            shxname: shape file name like 'ltypeshp.lin'

        """
        shape_file = self.find_shx(shxname)
        if shape_file is None:
            dxfattribs = {
                "font": shxname,
                "flags": 1,
                "name": "",  # shape file entry has no name
                "last_height": 2.5,  # just if this is required by AutoCAD
            }
            return self.new_entry(dxfattribs)
        else:
            return shape_file

    def find_shx(self, shxname: str) -> Optional["DXFEntity"]:
        """Find .shx shape file table entry, by a case insensitive search.

        A .shx shape file table entry has no name, so you have to search by the
        font attribute.

        Args:
            shxname: .shx shape file name

        """
        lower_name = shxname.lower()
        for entry in iter(self):
            if entry.dxf.font.lower() == lower_name:
                return entry
        return None

class ViewportTable(Table):
    # Viewport-Table can have multiple entries with same name
    # each table entry is a list of VPORT entries

    def new(self, name: str, dxfattribs: dict = None) -> "DXFEntity":
        """Create a new table entry."""
        dxfattribs = dxfattribs or {}
        dxfattribs["name"] = name
        return self.new_entry(dxfattribs)

    def remove(self, name: str) -> None:
        """Remove table-entry from table and entitydb by name."""
        key = self.key(name)
        entries = cast(List["DXFEntity"], self.get(name))
        for entry in entries:
            self.entitydb.delete_entity(entry)
        del self.entries[key]

    def __iter__(self) -> Iterator["DXFEntity"]:
        for entries in self.entries.values():
            yield from iter(entries)  # type: ignore

    def _flatten(self) -> Iterator["DXFEntity"]:
        for entries in self.entries.values():
            yield from iter(entries)  # type: ignore

    def __len__(self):
        # calling __iter__() invokes recursion!
        return len(list(self._flatten()))

    def new_entry(self, dxfattribs: dict) -> "DXFEntity":
        """Create and add new table-entry of type 'self.entry_dxftype'.

        Does not check if an entry dxfattribs['name'] already exists!
        Duplicate entries are possible for Viewports.
        """
        entry = factory.create_db_entry(
            self._head.dxf.name, dxfattribs, self.doc  # type: ignore
        )
        self._append(entry)
        return entry

    def duplicate_entry(self, name: str, new_name: str) -> "DXFEntity":
        raise NotImplementedError()

    def _append(self, entry: "DXFEntity") -> None:
        key = self.key(entry.dxf.name)
        if key in self.entries:
            self.entries[key].append(entry)  # type: ignore
        else:
            self.entries[key] = [entry]  # type: ignore # store list of VPORT

    def update_owner_handles(self) -> None:
        owner_handle = self._head.dxf.handle
        for entries in self.entries.values():
            for entry in entries:  # type: ignore
                entry.dxf.owner = owner_handle

    def get_config(self, name: str) -> List["DXFEntity"]:
        """Returns a list of :class:`~ezdxf.entities.Viewport` objects, for
        the multi-viewport configuration `name`.
        """
        try:
            return self.entries[self.key(name)]  # type: ignore
        except KeyError:
            raise const.DXFTableEntryError(name)

    def delete_config(self, name: str) -> None:
        """Delete all :class:`~ezdxf.entities.Viewport` objects of the
        multi-viewport configuration `name`.
        """
        self.remove(name)

class AppIDTable(Table):
    pass

class ViewTable(Table):
    pass

class BlockRecordTable(Table):
    pass

class DimStyleTable(Table):
    pass

class UCSTable(Table):
    pass
<commit_msg>copy dxfattribs<commit_after># Copyright (c) 2011-2021, Manfred Moitzi
# License: MIT License
from typing import TYPE_CHECKING, Iterable, Iterator, Optional, List, Dict, cast
from collections import OrderedDict
import logging

from ezdxf.lldxf import const, validator
from ezdxf.entities.table import TableHead
from ezdxf.entities import factory

if TYPE_CHECKING:
    from ezdxf.eztypes import (
        TagWriter,
        EntityDB,
        Drawing,
        DXFEntity,
        Layer,
        Linetype,
    )

logger = logging.getLogger("ezdxf")

TABLENAMES = {
    "LAYER": "LAYERS",
    "LTYPE": "LINETYPES",
    "APPID": "APPIDS",
    "DIMSTYLE": "DIMSTYLES",
    "STYLE": "STYLES",
    "UCS": "UCS",
    "VIEW": "VIEWS",
    "VPORT": "VIEWPORTS",
    "BLOCK_RECORD": "BLOCK_RECORDS",
}

def tablename(dxfname: str) -> str:
    """Translate DXF-table-name to attribute-name. ('LAYER' -> 'LAYERS')"""
    name = dxfname.upper()
    name = TABLENAMES.get(name, name + "S")
    return name

def table_key(name: str) -> str:
    """Unified table entry key."""
    # see also comments for ezdxf.lldxf.validator.is_valid_table_name()
    if not isinstance(name, str):
        raise TypeError("Name has to be a string.")
    return name.lower()  # table key is lower case

class Table:
    def __init__(
        self, doc: "Drawing" = None, entities: Iterable["DXFEntity"] = None
    ):
        self.doc = doc
        self.entries: Dict[str, DXFEntity] = OrderedDict()
        self._head = TableHead()
        if entities is not None:
            self.load(iter(entities))

    def load(self, entities: Iterator["DXFEntity"]) -> None:
        """Loading interface. (internal API)"""
        table_head = next(entities)
        if isinstance(table_head, TableHead):
            self._head = table_head
        else:
            raise const.DXFStructureError(
                "Critical structure error in TABLES section."
            )
        expected_entry_dxftype = self.entry_dxftype
        for table_entry in entities:
            if table_entry.dxftype() == expected_entry_dxftype:
                self._append(table_entry)
            else:
                logger.warning(
                    f"Ignored invalid DXF entity type '{table_entry.dxftype()}'"
                    f" in table '{self.name}'."
                )

    @classmethod
    def new_table(cls, name: str, handle: str, doc: "Drawing") -> "Table":
        """Create new table. (internal API)"""
        table = cls(doc)
        table._set_head(name, handle)
        return table

    def _set_head(self, name: str, handle: str = None) -> None:
        self._head = TableHead.new(
            handle, owner="0", dxfattribs={"name": name}, doc=self.doc
        )

    @property
    def head(self):
        """Returns table head entry."""
        return self._head

    @property
    def entry_dxftype(self) -> str:
        return self._head.dxf.name

    @staticmethod
    def key(name: str) -> str:
        """Unified table entry key."""
        return table_key(name)

    @property
    def name(self) -> str:
        """Table name like ``layers``."""
        return tablename(self.entry_dxftype)

    def has_entry(self, name: str) -> bool:
        """Returns ``True`` if an table entry `name` exist."""
        return self.key(name) in self.entries

    __contains__ = has_entry

    def __len__(self) -> int:
        """Count of table entries."""
        return len(self.entries)

    def __iter__(self) -> Iterator["DXFEntity"]:
        """Iterable of all table entries."""
        for e in self.entries.values():
            if e.is_alive:
                yield e

    def new(self, name: str, dxfattribs: dict = None) -> "DXFEntity":
        """Create a new table entry `name`.

        Args:
            name: name of table entry, case insensitive
            dxfattribs: additional DXF attributes for table entry

        """
        if self.has_entry(name):
            raise const.DXFTableEntryError(
                f"{self._head.dxf.name} {name} already exists!"
            )
        dxfattribs = dxfattribs or {}
        dxfattribs["name"] = name
        dxfattribs["owner"] = self._head.dxf.handle
        return self.new_entry(dxfattribs)

    def get(self, name: str) -> "DXFEntity":
        """Get table entry `name` (case insensitive).
        Raises :class:`DXFValueError` if table entry does not exist.
        """
        key = self.key(name)
        entry = self.entries.get(key, None)
        if entry:
            return entry
        else:
            raise const.DXFTableEntryError(name)

    def remove(self, name: str) -> None:
        """Removes table entry `name`. Raises :class:`DXFValueError`
        if table-entry does not exist.
        """
        key = self.key(name)
        entry = self.get(name)
        self.entitydb.delete_entity(entry)
        self.discard(key)

    def duplicate_entry(self, name: str, new_name: str) -> "DXFEntity":
        """Returns a new table entry `new_name` as copy of `name`,
        replaces entry `new_name` if already exist.

        Raises:
             DXFValueError: `name` does not exist

        """
        entry = self.get(name)
        entitydb = self.entitydb
        if entitydb:
            new_entry = entitydb.duplicate_entity(entry)
        else:  # only for testing!
            new_entry = entry.copy()
        new_entry.dxf.name = new_name
        self._append(new_entry)
        return new_entry

    def discard(self, name: str) -> None:
        """Remove table entry without destroying object. (internal API)"""
        del self.entries[self.key(name)]

    def replace(self, name: str, entry: "DXFEntity") -> None:
        """Replace table entry `name` by new `entry`. (internal API)"""
        self.discard(name)
        self._append(entry)

    @property
    def entitydb(self) -> "EntityDB":
        return self.doc.entitydb  # type: ignore

    def new_entry(self, dxfattribs: dict) -> "DXFEntity":
        """Create and add new table-entry of type 'self.entry_dxftype'.

        Does not check if an entry dxfattribs['name'] already exists!
        Duplicate entries are possible for Viewports.
        """
        entry = factory.create_db_entry(
            self._head.dxf.name, dxfattribs, self.doc  # type: ignore
        )
        self._append(entry)
        return entry

    def _append(self, entry: "DXFEntity") -> None:
        """Add a table entry, replaces existing entries with same name.
        (internal API).
        """
        assert entry.dxftype() == self.entry_dxftype
        self.entries[self.key(entry.dxf.name)] = entry

    def add_entry(self, entry: "DXFEntity") -> None:
        """Add a table `entry`, created by other object than this table.
        (internal API)
        """
        if entry.dxftype() != self.entry_dxftype:
            raise const.DXFTypeError(
                f"Invalid table entry type {entry.dxftype()} "
                f"for table {self.name}"
            )
        name = entry.dxf.name
        if self.has_entry(name):
            raise const.DXFTableEntryError(
                f"{self._head.dxf.name} {name} already exists!"
            )
        entry.doc = self.doc
        entry.dxf.owner = self._head.dxf.handle
        self._append(entry)

    def export_dxf(self, tagwriter: "TagWriter") -> None:
        """Export DXF representation. (internal API)"""

        def prologue():
            self.update_owner_handles()
            # The table head itself has no owner and is therefore always '0':
            self._head.dxf.owner = "0"
            self._head.dxf.count = len(self)
            self._head.export_dxf(tagwriter)

        def content():
            for entry in self.entries.values():
                # VPORT
                if isinstance(entry, list):
                    for e in entry:
                        e.export_dxf(tagwriter)
                else:
                    entry.export_dxf(tagwriter)

        def epilogue():
            tagwriter.write_tag2(0, "ENDTAB")

        prologue()
        content()
        epilogue()

    def update_owner_handles(self) -> None:
        owner_handle = self._head.dxf.handle
        for entry in self.entries.values():
            entry.dxf.owner = owner_handle

    def set_handle(self, handle: str):
        """Set new `handle` for table, updates also :attr:`owner` tag of table
        entries. (internal API)
        """
        if self._head.dxf.handle is None:
            self._head.dxf.handle = handle
            self.update_owner_handles()

class LayerTable(Table):
    def new_entry(self, dxfattribs: dict) -> "DXFEntity":
        layer = cast("Layer", super().new_entry(dxfattribs))
        if self.doc:
            layer.set_required_attributes()
        return layer

    def add(
        self,
        name: str,
        *,
        color: int = const.BYLAYER,
        true_color: int = None,
        linetype: str = "Continuous",
        lineweight: int = const.LINEWEIGHT_BYLAYER,
        plot: bool = True,
        dxfattribs: Dict = None,
    ) -> "Layer":
        """Add a new :class:`~ezdxf.entities.Layer`.

        Args:
            name (str): layer name
            color (int): :ref:`ACI` value, default is BYLAYER
            true_color (int): true color value, use :func:`ezdxf.rgb2int` to
                create ``int`` values from RGB values
            linetype (str): line type name, default is "Continuous"
            lineweight (int): line weight, default is BYLAYER
            plot (bool): plot layer as bool, default is ``True``
            dxfattribs (dict): additional DXF attributes

        .. versionadded:: 0.17

        """
        dxfattribs = dict(dxfattribs or {})
        if validator.is_valid_aci_color(color):
            dxfattribs["color"] = color
        else:
            raise const.DXFValueError(f"invalid color: {color}")
        dxfattribs["linetype"] = linetype
        if validator.is_valid_lineweight(lineweight):
            dxfattribs["lineweight"] = lineweight
        else:
            raise const.DXFValueError(f"invalid lineweight: {lineweight}")
        if true_color is not None:
            dxfattribs["true_color"] = int(true_color)
        dxfattribs["plot"] = int(plot)
        return self.new(name, dxfattribs)  # type: ignore

class LineTypeTable(Table):
    def new_entry(self, dxfattribs: dict) -> "DXFEntity":
        pattern = dxfattribs.pop("pattern", [0.0])
        length = dxfattribs.pop("length", 0)  # required for complex types
        ltype = cast("Linetype", super().new_entry(dxfattribs))
        ltype.setup_pattern(pattern, length)
        return ltype

class StyleTable(Table):
    def get_shx(self, shxname: str) -> "DXFEntity":
        """Get existing shx entry, or create a new entry.

        Args:
            shxname: shape file name like 'ltypeshp.lin'

        """
        shape_file = self.find_shx(shxname)
        if shape_file is None:
            dxfattribs = {
                "font": shxname,
                "flags": 1,
                "name": "",  # shape file entry has no name
                "last_height": 2.5,  # just if this is required by AutoCAD
            }
            return self.new_entry(dxfattribs)
        else:
            return shape_file

    def find_shx(self, shxname: str) -> Optional["DXFEntity"]:
        """Find .shx shape file table entry, by a case insensitive search.

        A .shx shape file table entry has no name, so you have to search by the
        font attribute.

        Args:
            shxname: .shx shape file name

        """
        lower_name = shxname.lower()
        for entry in iter(self):
            if entry.dxf.font.lower() == lower_name:
                return entry
        return None

class ViewportTable(Table):
    # Viewport-Table can have multiple entries with same name
    # each table entry is a list of VPORT entries

    def new(self, name: str, dxfattribs: dict = None) -> "DXFEntity":
        """Create a new table entry."""
        dxfattribs = dxfattribs or {}
        dxfattribs["name"] = name
        return self.new_entry(dxfattribs)

    def remove(self, name: str) -> None:
        """Remove table-entry from table and entitydb by name."""
        key = self.key(name)
        entries = cast(List["DXFEntity"], self.get(name))
        for entry in entries:
            self.entitydb.delete_entity(entry)
        del self.entries[key]

    def __iter__(self) -> Iterator["DXFEntity"]:
        for entries in self.entries.values():
            yield from iter(entries)  # type: ignore

    def _flatten(self) -> Iterator["DXFEntity"]:
        for entries in self.entries.values():
            yield from iter(entries)  # type: ignore

    def __len__(self):
        # calling __iter__() invokes recursion!
        return len(list(self._flatten()))

    def new_entry(self, dxfattribs: dict) -> "DXFEntity":
        """Create and add new table-entry of type 'self.entry_dxftype'.

        Does not check if an entry dxfattribs['name'] already exists!
        Duplicate entries are possible for Viewports.
        """
        entry = factory.create_db_entry(
            self._head.dxf.name, dxfattribs, self.doc  # type: ignore
        )
        self._append(entry)
        return entry

    def duplicate_entry(self, name: str, new_name: str) -> "DXFEntity":
        raise NotImplementedError()

    def _append(self, entry: "DXFEntity") -> None:
        key = self.key(entry.dxf.name)
        if key in self.entries:
            self.entries[key].append(entry)  # type: ignore
        else:
            self.entries[key] = [entry]  # type: ignore # store list of VPORT

    def update_owner_handles(self) -> None:
        owner_handle = self._head.dxf.handle
        for entries in self.entries.values():
            for entry in entries:  # type: ignore
                entry.dxf.owner = owner_handle

    def get_config(self, name: str) -> List["DXFEntity"]:
        """Returns a list of :class:`~ezdxf.entities.Viewport` objects, for
        the multi-viewport configuration `name`.
        """
        try:
            return self.entries[self.key(name)]  # type: ignore
        except KeyError:
            raise const.DXFTableEntryError(name)

    def delete_config(self, name: str) -> None:
        """Delete all :class:`~ezdxf.entities.Viewport` objects of the
        multi-viewport configuration `name`.
        """
        self.remove(name)

class AppIDTable(Table):
    pass

class ViewTable(Table):
    pass

class BlockRecordTable(Table):
    pass

class DimStyleTable(Table):
    pass

class UCSTable(Table):
    pass
<|endoftext|>


<commit_before>#
# This file is part of Mapnik (c++ mapping toolkit)
#
# Copyright (C) 2013 Artem Pavlenko
#
# Mapnik is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
#

Import ('plugin_base')
Import ('env')

PLUGIN_NAME = 'rasterlite'

plugin_env = plugin_base.Clone()

plugin_sources = Split(
  """
  %(PLUGIN_NAME)s_datasource.cpp
  %(PLUGIN_NAME)s_featureset.cpp
  """ % locals()
)

# Link Library to Dependencies
libraries = [env['PLUGINS']['rasterlite']['lib']]
libraries.append(env['ICU_LIB_NAME'])
libraries.append('boost_system%s' % env['BOOST_APPEND'])

if env['PLUGIN_LINKING'] == 'shared':
    libraries.append(env['MAPNIK_NAME'])

    TARGET = plugin_env.SharedLibrary('../%s' % PLUGIN_NAME,
                                      SHLIBPREFIX='',
                                      SHLIBSUFFIX='.input',
                                      source=plugin_sources,
                                      LIBS=libraries)

    # if the plugin links to libmapnik ensure it is built first
    Depends(TARGET, env.subst('../../../src/%s' % env['MAPNIK_LIB_NAME']))

    if 'uninstall' not in COMMAND_LINE_TARGETS:
        env.Install(env['MAPNIK_INPUT_PLUGINS_DEST'], TARGET)
        env.Alias('install', env['MAPNIK_INPUT_PLUGINS_DEST'])

plugin_obj = {
  'LIBS': libraries,
  'SOURCES': plugin_sources,
}

Return('plugin_obj')
<commit_msg>fix rasterlite linking when deps are static libs<commit_after>#
# This file is part of Mapnik (c++ mapping toolkit)
#
# Copyright (C) 2013 Artem Pavlenko
#
# Mapnik is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
#

Import ('plugin_base')
Import ('env')

PLUGIN_NAME = 'rasterlite'

plugin_env = plugin_base.Clone()

plugin_sources = Split(
  """
  %(PLUGIN_NAME)s_datasource.cpp
  %(PLUGIN_NAME)s_featureset.cpp
  """ % locals()
)

# Link Library to Dependencies
libraries = [env['PLUGINS']['rasterlite']['lib']]
libraries.append(env['ICU_LIB_NAME'])
libraries.append('boost_system%s' % env['BOOST_APPEND'])

if env['RUNTIME_LINK'] == 'static':
    libraries.append('geotiff')
    libraries.append('spatialite')
    libraries.append('sqlite3')
    libraries.append('geos_c')
    libraries.append('geos')
    libraries.append('proj')
    libraries.append('z')

if env['PLUGIN_LINKING'] == 'shared':
    libraries.append(env['MAPNIK_NAME'])

    TARGET = plugin_env.SharedLibrary('../%s' % PLUGIN_NAME,
                                      SHLIBPREFIX='',
                                      SHLIBSUFFIX='.input',
                                      source=plugin_sources,
                                      LIBS=libraries)

    # if the plugin links to libmapnik ensure it is built first
    Depends(TARGET, env.subst('../../../src/%s' % env['MAPNIK_LIB_NAME']))

    if 'uninstall' not in COMMAND_LINE_TARGETS:
        env.Install(env['MAPNIK_INPUT_PLUGINS_DEST'], TARGET)
        env.Alias('install', env['MAPNIK_INPUT_PLUGINS_DEST'])

plugin_obj = {
  'LIBS': libraries,
  'SOURCES': plugin_sources,
}

Return('plugin_obj')
<|endoftext|>


<commit_before>#!/usr/bin/env python

"""
This is a simple installation script for casual users of pymatgen who simply
plan to use pymatgen as a basic analysis library and is not planning to
develop on it. This script should work on most Linux and Mac systems that
have Python 2.7+ installed and setuptools installed. These are the only
required pre-requisites. Once those are installed, the script should take
care of the remainder of the installation process.

There are only a few options in this script. Please note that you probably
have to *run all commands with sudo* for the installation to proceed correctly.

Simply running:

    ./pmg_install

will install pymatgen with the basic dependencies.

Running:

    ./pmg_install -f

will install pymatgen with a few more optional packages and also start an
initial setup process that guides you through basic configuration
for POTCAR and Materials API support.

Report any issues or suggestions for this script to shyuep@gmail.com.
"""

__author__ = "Shyue Ping Ong"
__version__ = "1.0"
__email__ = "shyuep@gmail.com"
__date__ = "Apr 28, 2013"

import sys
import subprocess
import urllib
import os
import shutil

def build_enum():
    enumlib_url = "http://downloads.sourceforge.net/project/enum/enum/enum.tar.gz?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fenum%2Ffiles%2Fenum%2F&ts=1367333150&use_mirror=iweb"
    currdir = os.getcwd()
    state = True
    try:
        os.makedirs("enumlib")
        os.chdir("enumlib")
        urllib.urlretrieve(enumlib_url, "enum.tar.gz")
        subprocess.call(["tar", "-zxf", "enum.tar.gz"])
        os.chdir("celib")
        os.chdir("trunk")
        os.environ["F90"] = "gfortran"
        subprocess.call(["make"])
        os.chdir(os.path.join("..", ".."))
        enumpath = os.path.join("enumlib", "trunk")
        os.chdir(enumpath)
        subprocess.call(["make"])
        for f in ["multienum.x", "makestr.x"]:
            subprocess.call(["make", f])
            shutil.copy(f, os.path.join("..", "..", ".."))
    except Exception as ex:
        print(str(ex))
        state = False
    finally:
        os.chdir(currdir)
        shutil.rmtree("enumlib")
    return state

def build_bader():
    bader_url = "http://theory.cm.utexas.edu/bader/download/bader.tar.gz"
    currdir = os.getcwd()
    state = True
    try:
        urllib.urlretrieve(bader_url, "bader.tar.gz")
        subprocess.call(["tar", "-zxf", "bader.tar.gz"])
        os.chdir("bader")
        subprocess.call(["cp", "makefile.osx_gfortran", "makefile"])
        subprocess.call(["make"])
        shutil.copy("bader", os.path.join("..", "bader_exe"))
        os.chdir("..")
        shutil.rmtree("bader")
        os.remove("bader.tar.gz")
        shutil.move("bader_exe", "bader")
    except Exception as ex:
        print(str(ex))
        state = False
    finally:
        os.chdir(currdir)
    return state

py_ver = sys.version_info
print("Detected Python version {}".format(".".join(map(str, py_ver))))

if py_ver < (2, 7) or py_ver >= (2, 8):
    print("Python version 2.7+ required. Download and install the necessary "
          "python version from http://www.python.org/download/.")
    sys.exit(-1)

try:
    import setuptools
    print("Detected setuptools version {}".format(setuptools.__version__))
except ImportError:
    print("setuptools not detected. Get it from https://pypi.python"
          ".org/pypi/setuptools and follow the instructions to install first.")
    sys.exit(-1)

try:
    gcc_ver = subprocess.Popen(["gcc", "--version"], stdout=subprocess.PIPE)\
        .communicate()[0]
except:
    print("gcc not found in PATH. gcc is needed for installation of numpy "
          "and C extensions. For Mac users, please install Xcode and its "
          "corresponding command-line tools first.")
    sys.exit(-1)

try:
    import pip
    print("Detected pip version {}".format(pip.__version__))
except ImportError:
    print("pip not detected. Installing...")
    subprocess.call(["easy_install", "pip"])

try:
    import numpy
    from numpy.distutils.misc_util import get_numpy_include_dirs
    print("Detected numpy version {}".format(numpy.__version__))
except ImportError:
    print("numpy.distutils.misc_util cannot be imported. Installing...")
    subprocess.call(["pip", "install", "-q", "numpy>=1.6.0"])
    from numpy.distutils.misc_util import get_numpy_include_dirs

for pk in ["pyhull>=1.3.6", "PyCifRW>=3.3", "requests>=1.0", "pybtex>=0.16"]:
    print("Installing {}".format(pk))
    if subprocess.call(["pip", "install", "-q", pk]) != 0:
        print("Error installing required dependency {}".format(pk))
        sys.exit(-1)
    print

if subprocess.call(["pip", "install", "pymatgen"]) != 0:
    print("Error installing pymatgen")
    sys.exit(-1)
print

enum = False
bader = False

if "-f" in sys.argv:
    # for pk in ["matplotlib>1.1", "scipy"]:
    #     if subprocess.call(["pip", "install", pk]) != 0:
    #         print("Unable to install {}. Skipping...".format(pk))

    if subprocess.call([
            "pip", "install", "-Ivq",
            "https://wiki.fysik.dtu.dk/ase-files/python-ase-3.6.0.2515.tar.gz"]
    ) != 0:
        print("Unable to install ASE. Skipping...")
    print

    print("Building enumlib")
    enum = build_enum()
    print
    print("Building bader")
    bader = build_bader()
    print

    print("Performing POTCAR setup. Press Ctrl-C at any prompt to skip this "
          "step.")
    try:
        subprocess.call(["potcar_setup.py"])
    except:
        print("Skipping POTCAR setup.")
    print

print("------------ Setup complete --------------")
print("You still need to perform a few manual changes.")
print
if enum or bader:
    print("Please add {} to your PATH or move the executables multinum.x, "
          "makestr.x and bader to a location in your PATH."
          .format(os.path.abspath(".")))
    print

print("To use the Materials API, get your Materials API key at "
      "https://www.materialsproject.org/profile and add it to your "
      "environment")
print("export MAPI_KEY=YOUR_API_KEY")
print<commit_msg>Remove comment.<commit_after>#!/usr/bin/env python

"""
This is a simple installation script for casual users of pymatgen who simply
plan to use pymatgen as a basic analysis library and is not planning to
develop on it. This script should work on most Linux and Mac systems that
have Python 2.7+ installed and setuptools installed. These are the only
required pre-requisites. Once those are installed, the script should take
care of the remainder of the installation process.

There are only a few options in this script. Please note that you probably
have to *run all commands with sudo* for the installation to proceed correctly.

Simply running:

    ./pmg_install

will install pymatgen with the basic dependencies.

Running:

    ./pmg_install -f

will install pymatgen with a few more optional packages and also start an
initial setup process that guides you through basic configuration
for POTCAR and Materials API support.

Report any issues or suggestions for this script to shyuep@gmail.com.
"""

__author__ = "Shyue Ping Ong"
__version__ = "1.0"
__email__ = "shyuep@gmail.com"
__date__ = "Apr 28, 2013"

import sys
import subprocess
import urllib
import os
import shutil

def build_enum():
    enumlib_url = "http://downloads.sourceforge.net/project/enum/enum/enum.tar.gz?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fenum%2Ffiles%2Fenum%2F&ts=1367333150&use_mirror=iweb"
    currdir = os.getcwd()
    state = True
    try:
        os.makedirs("enumlib")
        os.chdir("enumlib")
        urllib.urlretrieve(enumlib_url, "enum.tar.gz")
        subprocess.call(["tar", "-zxf", "enum.tar.gz"])
        os.chdir("celib")
        os.chdir("trunk")
        os.environ["F90"] = "gfortran"
        subprocess.call(["make"])
        os.chdir(os.path.join("..", ".."))
        enumpath = os.path.join("enumlib", "trunk")
        os.chdir(enumpath)
        subprocess.call(["make"])
        for f in ["multienum.x", "makestr.x"]:
            subprocess.call(["make", f])
            shutil.copy(f, os.path.join("..", "..", ".."))
    except Exception as ex:
        print(str(ex))
        state = False
    finally:
        os.chdir(currdir)
        shutil.rmtree("enumlib")
    return state

def build_bader():
    bader_url = "http://theory.cm.utexas.edu/bader/download/bader.tar.gz"
    currdir = os.getcwd()
    state = True
    try:
        urllib.urlretrieve(bader_url, "bader.tar.gz")
        subprocess.call(["tar", "-zxf", "bader.tar.gz"])
        os.chdir("bader")
        subprocess.call(["cp", "makefile.osx_gfortran", "makefile"])
        subprocess.call(["make"])
        shutil.copy("bader", os.path.join("..", "bader_exe"))
        os.chdir("..")
        shutil.rmtree("bader")
        os.remove("bader.tar.gz")
        shutil.move("bader_exe", "bader")
    except Exception as ex:
        print(str(ex))
        state = False
    finally:
        os.chdir(currdir)
    return state

py_ver = sys.version_info
print("Detected Python version {}".format(".".join(map(str, py_ver))))

if py_ver < (2, 7) or py_ver >= (2, 8):
    print("Python version 2.7+ required. Download and install the necessary "
          "python version from http://www.python.org/download/.")
    sys.exit(-1)

try:
    import setuptools
    print("Detected setuptools version {}".format(setuptools.__version__))
except ImportError:
    print("setuptools not detected. Get it from https://pypi.python"
          ".org/pypi/setuptools and follow the instructions to install first.")
    sys.exit(-1)

try:
    gcc_ver = subprocess.Popen(["gcc", "--version"], stdout=subprocess.PIPE)\
        .communicate()[0]
except:
    print("gcc not found in PATH. gcc is needed for installation of numpy "
          "and C extensions. For Mac users, please install Xcode and its "
          "corresponding command-line tools first.")
    sys.exit(-1)

try:
    import pip
    print("Detected pip version {}".format(pip.__version__))
except ImportError:
    print("pip not detected. Installing...")
    subprocess.call(["easy_install", "pip"])

try:
    import numpy
    from numpy.distutils.misc_util import get_numpy_include_dirs
    print("Detected numpy version {}".format(numpy.__version__))
except ImportError:
    print("numpy.distutils.misc_util cannot be imported. Installing...")
    subprocess.call(["pip", "install", "-q", "numpy>=1.6.0"])
    from numpy.distutils.misc_util import get_numpy_include_dirs

for pk in ["pyhull>=1.3.6", "PyCifRW>=3.3", "requests>=1.0", "pybtex>=0.16"]:
    print("Installing {}".format(pk))
    if subprocess.call(["pip", "install", "-q", pk]) != 0:
        print("Error installing required dependency {}".format(pk))
        sys.exit(-1)
    print

if subprocess.call(["pip", "install", "pymatgen"]) != 0:
    print("Error installing pymatgen")
    sys.exit(-1)
print

enum = False
bader = False

if "-f" in sys.argv:
    for pk in ["matplotlib>1.1", "scipy"]:
        if subprocess.call(["pip", "install", pk]) != 0:
            print("Unable to install {}. Skipping...".format(pk))

    if subprocess.call([
            "pip", "install", "-Ivq",
            "https://wiki.fysik.dtu.dk/ase-files/python-ase-3.6.0.2515.tar.gz"]
    ) != 0:
        print("Unable to install ASE. Skipping...")
    print

    print("Building enumlib")
    enum = build_enum()
    print
    print("Building bader")
    bader = build_bader()
    print

    print("Performing POTCAR setup. Press Ctrl-C at any prompt to skip this "
          "step.")
    try:
        subprocess.call(["potcar_setup.py"])
    except:
        print("Skipping POTCAR setup.")
    print

print("------------ Setup complete --------------")
print("You still need to perform a few manual changes.")
print
if enum or bader:
    print("Please add {} to your PATH or move the executables multinum.x, "
          "makestr.x and bader to a location in your PATH."
          .format(os.path.abspath(".")))
    print

print("To use the Materials API, get your Materials API key at "
      "https://www.materialsproject.org/profile and add it to your "
      "environment")
print("export MAPI_KEY=YOUR_API_KEY")
print<|endoftext|>


<commit_before>"""
The ``wind_speed`` module contains functions to calculate the wind speed at
hub height of a wind turbine.

"""

__copyright__ = "Copyright oemof developer group"
__license__ = "GPLv3"

import numpy as np

def logarithmic_wind_profile(v_wind, v_wind_height, hub_height, z_0,
                             obstacle_height=0):
    r"""
    Calculates the wind speed at hub height using a logarithmic wind profile.

    The logarithmic height equation is used. There is the possibility of
    including the height of the surrounding obstacles in the calculation. This
    function is carried out when the parameter `wind_model` of an instance of
    the :class:`~.modelchain.ModelChain` class is 'logarithmic' or
    'logarithmic_closest'.

    Parameters
    ----------
    v_wind : pandas.Series or array
        Wind speed time series.
    v_wind_height : float
        Height for which the parameter `v_wind` applies.
    hub_height : float
        Hub height of wind turbine.
    z_0 : pandas.Series or array or float
        Roughness length.
    obstacle_height : float
        Height of obstacles in the surrounding area of the wind turbine. Set
        `obstacle_height` to zero for wide spread obstacles. Default: 0.

    Returns
    -------
    pandas.Series or array
        Wind speed at hub height.

    Notes
    -----
    The following equation is used [1]_:

    .. math:: v_{wind,hub}=v_{wind,data}\cdot
        \frac{\ln\left(\frac{h_{hub}-d}{z_{0}}\right)}{\ln\left(
        \frac{h_{data}-d}{z_{0}}\right)}

    with:
        v: wind speed, h: height, :math:`z_{0}`: roughness length,
        d: boundary layer offset (estimated by d = 0.7 * `obstacle_height`)

    For  d = 0 it results in the following equation [2]_, [3]_:

    .. math:: v_{wind,hub}=v_{wind,data}\cdot\frac{\ln\left(\frac{h_{hub}}
        {z_{0}}\right)}{\ln\left(\frac{h_{data}}{z_{0}}\right)}

    :math:`h_{data}` is the height at which the wind speed
    :math:`v_{wind,data}` is measured.

    Parameters `v_wind_height`, `z_0`, `hub_height` and `obstacle_height` have
    to be of the same unit.

    References
    ----------
    .. [1] Quaschning V.: "Regenerative Energiesysteme". München, Hanser
            Verlag, 2011, p. 278
    .. [2] Gasch, R., Twele, J.: "Windkraftanlagen". 6. Auflage, Wiesbaden,
            Vieweg + Teubner, 2010, p. 129
    .. [3] Hau, E.: "Windkraftanlagen - Grundlagen, Technik, Einsatz,
            Wirtschaftlichkeit". 4. Auflage, Springer-Verlag, 2008, p. 515

    """
    if 0.7 * obstacle_height > v_wind_height:
        raise ValueError("To take an obstacle height of {0} m".format(
                         obstacle_height) + " into consideration, wind" +
                         " speed data of a higher height is needed.")
    return (v_wind * np.log((hub_height - 0.7 * obstacle_height) / z_0) /
            np.log((v_wind_height - 0.7 * obstacle_height) / z_0))

def v_wind_hellman(v_wind, v_wind_height, hub_height, hellman_exp=None,
                   z_0=None):
    r"""
    Calculates the wind speed at hub height using the hellman equation.

    It is assumed that the wind profile follows a power law. This fuction is
    carried out when the parameter `wind_model` of an instance of
    the :class:`~.modelchain.ModelChain` class is 'hellman'.

    Parameters
    ----------
    v_wind : pandas.Series or array
        Wind speed time series.
    v_wind_height : float
        Height for which the parameter `v_wind` applies.
    hub_height : float
        Hub height of wind turbine.
    hellman_exp : float
        The Hellman exponent, which combines the increase in wind speed due to
        stability of atmospheric conditions and surface roughness into one
        constant. Default: 1/7. If roughness length is given
        `hellman_exp` = 1 / ln(h_hub/z_0).
    z_0 : pandas.Series or array or float
        Roughness length. Default: None.

    Returns
    -------
    pandas.Series or array
        Wind speed at hub height.

    Notes
    -----
    The following equation is used [1]_,
    [2]_, [3]_:

    .. math:: v_{wind,hub}=v_{wind,data}\cdot \left(\frac{h_{hub}}{h_{data}}
        \right)^\alpha

    with:
        v: wind speed, h: height, :math:`\alpha`: Hellman exponent

    :math:`h_{data}` is the height in which the wind speed
    :math:`v_{wind,data}` is measured and :math:`h_{hub}` is the hub height of
    the wind turbine.

    For the Hellman exponent :math:`\alpha` many studies use a value of 1/7 for
    onshore and a value of 1/9 for offshore. The Hellman exponent can also
    be calulated by the following equation [2]_, [3]_:

    .. math:: \alpha = \frac{1}{ln\left(\frac{h_{hub}}{z_0} \right)}

    with:
        :math:`z_{0}`: roughness length

    References
    ----------
    .. [1] Sharp, E.: "Spatiotemporal disaggregation of GB scenarios depicting
            increased wind capacity and electrified heat demand in dwellings".
            UCL, Energy Institute, 2015, p. 83
    .. [2] Hau, E.: "Windkraftanlagen - Grundlagen, Technik, Einsatz,
            Wirtschaftlichkeit". 4. Auflage, Springer-Verlag, 2008, p. 517
    .. [3] Quaschning V.: "Regenerative Energiesysteme". München, Hanser
            Verlag, 2011, p. 279

    """
    if hellman_exp is None:
        if z_0 is not None:
            hellman_exp = 1 / np.log(hub_height / z_0)
        else:
            hellman_exp = 1/7
    else:
        if not isinstance(hellman_exp, float):
            raise TypeError("'{0}' is an invalid type.".format(
                            type(hellman_exp)) +
                            "`hellman_exp` must be float.")
    return v_wind * (hub_height / v_wind_height) ** hellman_exp
<commit_msg>Correct docstring of v_wind_hellman<commit_after>"""
The ``wind_speed`` module contains functions to calculate the wind speed at
hub height of a wind turbine.

"""

__copyright__ = "Copyright oemof developer group"
__license__ = "GPLv3"

import numpy as np

def logarithmic_wind_profile(v_wind, v_wind_height, hub_height, z_0,
                             obstacle_height=0):
    r"""
    Calculates the wind speed at hub height using a logarithmic wind profile.

    The logarithmic height equation is used. There is the possibility of
    including the height of the surrounding obstacles in the calculation. This
    function is carried out when the parameter `wind_model` of an instance of
    the :class:`~.modelchain.ModelChain` class is 'logarithmic' or
    'logarithmic_closest'.

    Parameters
    ----------
    v_wind : pandas.Series or array
        Wind speed time series.
    v_wind_height : float
        Height for which the parameter `v_wind` applies.
    hub_height : float
        Hub height of wind turbine.
    z_0 : pandas.Series or array or float
        Roughness length.
    obstacle_height : float
        Height of obstacles in the surrounding area of the wind turbine. Set
        `obstacle_height` to zero for wide spread obstacles. Default: 0.

    Returns
    -------
    pandas.Series or array
        Wind speed at hub height.

    Notes
    -----
    The following equation is used [1]_:

    .. math:: v_{wind,hub}=v_{wind,data}\cdot
        \frac{\ln\left(\frac{h_{hub}-d}{z_{0}}\right)}{\ln\left(
        \frac{h_{data}-d}{z_{0}}\right)}

    with:
        v: wind speed, h: height, :math:`z_{0}`: roughness length,
        d: boundary layer offset (estimated by d = 0.7 * `obstacle_height`)

    For  d = 0 it results in the following equation [2]_, [3]_:

    .. math:: v_{wind,hub}=v_{wind,data}\cdot\frac{\ln\left(\frac{h_{hub}}
        {z_{0}}\right)}{\ln\left(\frac{h_{data}}{z_{0}}\right)}

    :math:`h_{data}` is the height at which the wind speed
    :math:`v_{wind,data}` is measured.

    Parameters `v_wind_height`, `z_0`, `hub_height` and `obstacle_height` have
    to be of the same unit.

    References
    ----------
    .. [1] Quaschning V.: "Regenerative Energiesysteme". München, Hanser
            Verlag, 2011, p. 278
    .. [2] Gasch, R., Twele, J.: "Windkraftanlagen". 6. Auflage, Wiesbaden,
            Vieweg + Teubner, 2010, p. 129
    .. [3] Hau, E.: "Windkraftanlagen - Grundlagen, Technik, Einsatz,
            Wirtschaftlichkeit". 4. Auflage, Springer-Verlag, 2008, p. 515

    """
    if 0.7 * obstacle_height > v_wind_height:
        raise ValueError("To take an obstacle height of {0} m".format(
                         obstacle_height) + " into consideration, wind" +
                         " speed data of a higher height is needed.")
    return (v_wind * np.log((hub_height - 0.7 * obstacle_height) / z_0) /
            np.log((v_wind_height - 0.7 * obstacle_height) / z_0))

def v_wind_hellman(v_wind, v_wind_height, hub_height, hellman_exp=None,
                   z_0=None):
    r"""
    Calculates the wind speed at hub height using the hellman equation.

    It is assumed that the wind profile follows a power law. This fuction is
    carried out when the parameter `wind_model` of an instance of
    the :class:`~.modelchain.ModelChain` class is 'hellman'.

    Parameters
    ----------
    v_wind : pandas.Series or array
        Wind speed time series.
    v_wind_height : float
        Height for which the parameter `v_wind` applies.
    hub_height : float
        Hub height of wind turbine.
    hellman_exp : float
        The Hellman exponent, which combines the increase in wind speed due to
        stability of atmospheric conditions and surface roughness into one
        constant. Default: None. If None and roughness length is given
        `hellman_exp` = 1 / ln(h_hub/z_0).
    z_0 : pandas.Series or array or float
        Roughness length. Default: None.

    Returns
    -------
    pandas.Series or array
        Wind speed at hub height.

    Notes
    -----
    The following equation is used [1]_,
    [2]_, [3]_:

    .. math:: v_{wind,hub}=v_{wind,data}\cdot \left(\frac{h_{hub}}{h_{data}}
        \right)^\alpha

    with:
        v: wind speed, h: height, :math:`\alpha`: Hellman exponent

    :math:`h_{data}` is the height in which the wind speed
    :math:`v_{wind,data}` is measured and :math:`h_{hub}` is the hub height of
    the wind turbine.

    For the Hellman exponent :math:`\alpha` many studies use a value of 1/7 for
    onshore and a value of 1/9 for offshore. The Hellman exponent can also
    be calulated by the following equation [2]_, [3]_:

    .. math:: \alpha = \frac{1}{ln\left(\frac{h_{hub}}{z_0} \right)}

    with:
        :math:`z_{0}`: roughness length

    References
    ----------
    .. [1] Sharp, E.: "Spatiotemporal disaggregation of GB scenarios depicting
            increased wind capacity and electrified heat demand in dwellings".
            UCL, Energy Institute, 2015, p. 83
    .. [2] Hau, E.: "Windkraftanlagen - Grundlagen, Technik, Einsatz,
            Wirtschaftlichkeit". 4. Auflage, Springer-Verlag, 2008, p. 517
    .. [3] Quaschning V.: "Regenerative Energiesysteme". München, Hanser
            Verlag, 2011, p. 279

    """
    if hellman_exp is None:
        if z_0 is not None:
            hellman_exp = 1 / np.log(hub_height / z_0)
        else:
            hellman_exp = 1/7
    else:
        if not isinstance(hellman_exp, float):
            raise TypeError("'{0}' is an invalid type.".format(
                            type(hellman_exp)) +
                            "`hellman_exp` must be float.")
    return v_wind * (hub_height / v_wind_height) ** hellman_exp
<|endoftext|>