<commit_before># encoding: utf-8
"""
The layout engine. This builds the prompt_toolkit layout.
"""
from __future__ import unicode_literals

from prompt_toolkit.application.current import get_app, NoRunningApplicationError
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.document import Document
from prompt_toolkit.filters import Condition
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.keys import Keys
from prompt_toolkit.layout.containers import Window, HSplit, VSplit, ConditionalContainer
from prompt_toolkit.layout.controls import UIControl, UIContent, BufferControl
from prompt_toolkit.layout.processors import Processor, HighlightSearchProcessor, HighlightIncrementalSearchProcessor, merge_processors, Transformation
from prompt_toolkit.layout.screen import Point
from prompt_toolkit.widgets.toolbars import SearchToolbar
from prompt_toolkit.mouse_events import MouseEventType
from prompt_toolkit.utils import Event

import six
from six.moves import range

from .process import Process

__all__ = (
    'TerminalControl',
    'Terminal',
)

class TerminalControl(UIControl):
    def __init__(self, command=['/bin/bash'], done_callback=None,
                 before_exec_func=None, bell_func=None):
        assert isinstance(command, list)
        assert done_callback is None or callable(done_callback)
        assert before_exec_func is None or callable(before_exec_func)
        assert bell_func is None or callable(bell_func)

        def has_priority():
            # Give priority to the processing of this terminal output, if this
            # user control has the focus.
            try:
                app = get_app(raise_exception=True)
            except NoRunningApplicationError:
                # The application has terminated before this process ended.
                return False
            else:
                return app.layout.has_focus(self)

        self.process = Process(
            lambda: self.on_content_changed.fire(),
            command=command,
            before_exec_func=before_exec_func,
            done_callback=done_callback,
            bell_func=bell_func,
            has_priority=has_priority)

        self.on_content_changed = Event(self)
        self._running = False

    def create_content(self, width, height):
        # Report dimensions to the process.
        self.process.set_size(width, height)

        # The first time that this user control is rendered. Keep track of the
        # 'app' object and start the process.
        if not self._running:
            self.process.start()
            self._running = True

        if not self.process.screen:
            return UIContent()

        pt_screen = self.process.screen.pt_screen
        data_buffer = pt_screen.data_buffer
        cursor_y = pt_screen.cursor_position.y

        # Prompt_toolkit needs the amount of characters before the cursor in a
        # UIControl.  This doesn't correspond with the xpos in case of double
        # width characters. That's why we compute the wcwidth.
        cursor_row = data_buffer[pt_screen.cursor_position.y]
        text_before_cursor = ''.join(cursor_row[x].char for x in range(0, pt_screen.cursor_position.x))
        cursor_x = len(text_before_cursor)

        def get_line(number):
            row = data_buffer[number]
            empty = True
            if row:
                max_column = max(row)
                empty = False
            else:
                max_column = 0

            if number == cursor_y:
                max_column = max(max_column, cursor_x)
                empty = False

            if empty:
                return [('', ' ')]
            else:
                cells = [row[i] for i in range(max_column + 1)]
                return [(cell.style, cell.char) for cell in cells]

        if data_buffer:
            line_count = max(data_buffer) + 1    # TODO: substract all empty lines from the beginning. (If we need to. Not sure.)
        else:
            line_count = 1

        return UIContent(
            get_line, line_count=line_count,
            show_cursor=pt_screen.show_cursor,
            cursor_position=Point(x=cursor_x, y=cursor_y))

    def get_key_bindings(self):
        bindings = KeyBindings()

        @bindings.add(Keys.Any)
        def handle_key(event):
            """
            Handle any key binding -> write it to the stdin of this terminal.
            """
            self.process.write_key(event.key_sequence[0].key)

        @bindings.add(Keys.BracketedPaste)
        def _(event):
            self.process.write_input(event.data, paste=True)

        return bindings

    def get_invalidate_events(self):
        yield self.on_content_changed

    def mouse_handler(self, mouse_event):
        """
        Handle mouse events in a pane. A click in a non-active pane will select
        it. A click in active pane will send the mouse event to the application
        running inside it.
        """
        app = get_app()

        process = self.process
        x = mouse_event.position.x
        y = mouse_event.position.y

        # The containing Window translates coordinates to the absolute position
        # of the whole screen, but in this case, we need the relative
        # coordinates of the visible area.
        y -= self.process.screen.line_offset

        if not app.layout.has_focus(self):
            # Focus this process when the mouse has been clicked.
            if mouse_event.event_type == MouseEventType.MOUSE_UP:
                app.layout.focus(self)
        else:
            # Already focussed, send event to application when it requested
            # mouse support.
            if process.screen.sgr_mouse_support_enabled:
                # Xterm SGR mode.
                ev, m = {
                    MouseEventType.MOUSE_DOWN: ('0', 'M'),
                    MouseEventType.MOUSE_UP: ('0', 'm'),
                    MouseEventType.SCROLL_UP: ('64', 'M'),
                    MouseEventType.SCROLL_DOWN: ('65', 'M'),
                }.get(mouse_event.event_type)

                self.process.write_input(
                    '\x1b[<%s;%s;%s%s' % (ev, x + 1, y + 1, m))

            elif process.screen.urxvt_mouse_support_enabled:
                # Urxvt mode.
                ev = {
                    MouseEventType.MOUSE_DOWN: 32,
                    MouseEventType.MOUSE_UP: 35,
                    MouseEventType.SCROLL_UP: 96,
                    MouseEventType.SCROLL_DOWN: 97,
                }.get(mouse_event.event_type)

                self.process.write_input(
                    '\x1b[%s;%s;%sM' % (ev, x + 1, y + 1))

            elif process.screen.mouse_support_enabled:
                # Fall back to old mode.
                if x < 96 and y < 96:
                    ev = {
                            MouseEventType.MOUSE_DOWN: 32,
                            MouseEventType.MOUSE_UP: 35,
                            MouseEventType.SCROLL_UP: 96,
                            MouseEventType.SCROLL_DOWN: 97,
                    }.get(mouse_event.event_type)

                    self.process.write_input('\x1b[M%s%s%s' % (
                        six.unichr(ev),
                        six.unichr(x + 33),
                        six.unichr(y + 33)))

    def is_focusable(self):
        return not self.process.suspended

class _Window(Window):
    """
    """
    def __init__(self, terminal_control, **kw):
        self.terminal_control = terminal_control
        super(_Window, self).__init__(**kw)

    def write_to_screen(self, *a, **kw):
        # Make sure that the bottom of the terminal is always visible.
        screen = self.terminal_control.process.screen

        # NOTE: the +1 is required because max_y starts counting at 0, while
        #       lines counts the numbers of lines, starting at 1 for one line.
        self.vertical_scroll = screen.max_y - screen.lines + 1

        super(_Window, self).write_to_screen(*a, **kw)

class Terminal(object):
    def __init__(self, command=['/bin/bash'], before_exec_func=None,
                 bell_func=None, style='', width=None, height=None,
                 done_callback=None):

        self.terminal_control = TerminalControl(
            command=command, before_exec_func=before_exec_func,
            bell_func=bell_func, done_callback=done_callback)

        self.terminal_window = _Window(
            terminal_control=self.terminal_control,
            content=self.terminal_control,
            wrap_lines=False)

        # Key bindigns for copy buffer.
        kb = KeyBindings()
        @kb.add('c-c')
        def _(event):
            self.exit_copy_mode()

        self.search_toolbar = SearchToolbar(
            forward_search_prompt='Search down: ',
            backward_search_prompt='Search up: ')

        self.copy_buffer = Buffer(read_only=True)
        self.copy_buffer_control = BufferControl(
            buffer=self.copy_buffer,
            search_buffer_control=self.search_toolbar.control,
            input_processors=[
                _UseStyledTextrocessor(self),
                HighlightSearchProcessor(),
                HighlightIncrementalSearchProcessor(),
            ],
            preview_search=True,  # XXX: not sure why we need twice preview_search.
            key_bindings=kb)

        self.copy_window = Window(content=self.copy_buffer_control, wrap_lines=False)

        self.is_copying = False
        self.styled_copy_lines = []  # List of lists of (style, text) tuples, for each line.

        @Condition
        def is_copying():
            return self.is_copying

        self.container = HSplit([
            # Either show terminal window or copy buffer.
            VSplit([  # XXX: this nested VSplit should not have been necessary,
                        # but the ConditionalContainer which width can become
                        # zero will collapse the other elements.
                ConditionalContainer(self.terminal_window, filter=~is_copying),
                ConditionalContainer(self.copy_window, filter=is_copying),
            ]),
            ConditionalContainer(self.search_toolbar, filter=is_copying),
        ], style=style, width=width, height=height)

    def enter_copy_mode(self):
        # Suspend process.
        self.terminal_control.process.suspend()

        # Copy content into copy buffer.
        data_buffer = self.terminal_control.process.screen.pt_screen.data_buffer

        text = []
        styled_lines = []

        if data_buffer:
            for line_index in range(min(data_buffer), max(data_buffer) + 1):
                line = data_buffer[line_index]
                styled_line = []

                if line:
                    for column_index in range(0, max(line) + 1):
                        char = line[column_index]
                        text.append(char.char)
                        styled_line.append((char.style, char.char))

                text.append('\n')
                styled_lines.append(styled_line)
            text.pop()  # Drop last line ending.

        text = ''.join(text)

        self.copy_buffer.set_document(
            Document(text=text, cursor_position=len(text)),
            bypass_readonly=True)

        self.styled_lines = styled_lines

        # Enter copy mode.
        self.is_copying = True
        get_app().layout.focus(self.copy_window)

    def exit_copy_mode(self):
        # Resume process.
        self.terminal_control.process.resume()

        # focus terminal again.
        self.is_copying = False
        get_app().layout.focus(self.terminal_window)

    def __pt_container__(self):
        return self.container

    @property
    def process(self):
        return self.terminal_control.process

class _UseStyledTextrocessor(Processor):
    """
    In order to allow highlighting of the copy region, we use a preprocessed
    list of (style, text) tuples. This processor returns just that list for the
    given pane.

    This processor should go before all others, because it replaces the list of
    (style, text) tuples.
    """
    def __init__(self, terminal):
        self.terminal = terminal

    def apply_transformation(self, transformation_input):
        try:
            line = self.terminal.styled_lines[transformation_input.lineno]
        except IndexError:
            line = []
        return Transformation(line)
<commit_msg>Removed unused import.<commit_after># encoding: utf-8
"""
The layout engine. This builds the prompt_toolkit layout.
"""
from __future__ import unicode_literals

from prompt_toolkit.application.current import get_app, NoRunningApplicationError
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.document import Document
from prompt_toolkit.filters import Condition
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.keys import Keys
from prompt_toolkit.layout.containers import Window, HSplit, VSplit, ConditionalContainer
from prompt_toolkit.layout.controls import UIControl, UIContent, BufferControl
from prompt_toolkit.layout.processors import Processor, HighlightSearchProcessor, HighlightIncrementalSearchProcessor, Transformation
from prompt_toolkit.layout.screen import Point
from prompt_toolkit.widgets.toolbars import SearchToolbar
from prompt_toolkit.mouse_events import MouseEventType
from prompt_toolkit.utils import Event

import six
from six.moves import range

from .process import Process

__all__ = (
    'TerminalControl',
    'Terminal',
)

class TerminalControl(UIControl):
    def __init__(self, command=['/bin/bash'], done_callback=None,
                 before_exec_func=None, bell_func=None):
        assert isinstance(command, list)
        assert done_callback is None or callable(done_callback)
        assert before_exec_func is None or callable(before_exec_func)
        assert bell_func is None or callable(bell_func)

        def has_priority():
            # Give priority to the processing of this terminal output, if this
            # user control has the focus.
            try:
                app = get_app(raise_exception=True)
            except NoRunningApplicationError:
                # The application has terminated before this process ended.
                return False
            else:
                return app.layout.has_focus(self)

        self.process = Process(
            lambda: self.on_content_changed.fire(),
            command=command,
            before_exec_func=before_exec_func,
            done_callback=done_callback,
            bell_func=bell_func,
            has_priority=has_priority)

        self.on_content_changed = Event(self)
        self._running = False

    def create_content(self, width, height):
        # Report dimensions to the process.
        self.process.set_size(width, height)

        # The first time that this user control is rendered. Keep track of the
        # 'app' object and start the process.
        if not self._running:
            self.process.start()
            self._running = True

        if not self.process.screen:
            return UIContent()

        pt_screen = self.process.screen.pt_screen
        data_buffer = pt_screen.data_buffer
        cursor_y = pt_screen.cursor_position.y

        # Prompt_toolkit needs the amount of characters before the cursor in a
        # UIControl.  This doesn't correspond with the xpos in case of double
        # width characters. That's why we compute the wcwidth.
        cursor_row = data_buffer[pt_screen.cursor_position.y]
        text_before_cursor = ''.join(cursor_row[x].char for x in range(0, pt_screen.cursor_position.x))
        cursor_x = len(text_before_cursor)

        def get_line(number):
            row = data_buffer[number]
            empty = True
            if row:
                max_column = max(row)
                empty = False
            else:
                max_column = 0

            if number == cursor_y:
                max_column = max(max_column, cursor_x)
                empty = False

            if empty:
                return [('', ' ')]
            else:
                cells = [row[i] for i in range(max_column + 1)]
                return [(cell.style, cell.char) for cell in cells]

        if data_buffer:
            line_count = max(data_buffer) + 1    # TODO: substract all empty lines from the beginning. (If we need to. Not sure.)
        else:
            line_count = 1

        return UIContent(
            get_line, line_count=line_count,
            show_cursor=pt_screen.show_cursor,
            cursor_position=Point(x=cursor_x, y=cursor_y))

    def get_key_bindings(self):
        bindings = KeyBindings()

        @bindings.add(Keys.Any)
        def handle_key(event):
            """
            Handle any key binding -> write it to the stdin of this terminal.
            """
            self.process.write_key(event.key_sequence[0].key)

        @bindings.add(Keys.BracketedPaste)
        def _(event):
            self.process.write_input(event.data, paste=True)

        return bindings

    def get_invalidate_events(self):
        yield self.on_content_changed

    def mouse_handler(self, mouse_event):
        """
        Handle mouse events in a pane. A click in a non-active pane will select
        it. A click in active pane will send the mouse event to the application
        running inside it.
        """
        app = get_app()

        process = self.process
        x = mouse_event.position.x
        y = mouse_event.position.y

        # The containing Window translates coordinates to the absolute position
        # of the whole screen, but in this case, we need the relative
        # coordinates of the visible area.
        y -= self.process.screen.line_offset

        if not app.layout.has_focus(self):
            # Focus this process when the mouse has been clicked.
            if mouse_event.event_type == MouseEventType.MOUSE_UP:
                app.layout.focus(self)
        else:
            # Already focussed, send event to application when it requested
            # mouse support.
            if process.screen.sgr_mouse_support_enabled:
                # Xterm SGR mode.
                ev, m = {
                    MouseEventType.MOUSE_DOWN: ('0', 'M'),
                    MouseEventType.MOUSE_UP: ('0', 'm'),
                    MouseEventType.SCROLL_UP: ('64', 'M'),
                    MouseEventType.SCROLL_DOWN: ('65', 'M'),
                }.get(mouse_event.event_type)

                self.process.write_input(
                    '\x1b[<%s;%s;%s%s' % (ev, x + 1, y + 1, m))

            elif process.screen.urxvt_mouse_support_enabled:
                # Urxvt mode.
                ev = {
                    MouseEventType.MOUSE_DOWN: 32,
                    MouseEventType.MOUSE_UP: 35,
                    MouseEventType.SCROLL_UP: 96,
                    MouseEventType.SCROLL_DOWN: 97,
                }.get(mouse_event.event_type)

                self.process.write_input(
                    '\x1b[%s;%s;%sM' % (ev, x + 1, y + 1))

            elif process.screen.mouse_support_enabled:
                # Fall back to old mode.
                if x < 96 and y < 96:
                    ev = {
                            MouseEventType.MOUSE_DOWN: 32,
                            MouseEventType.MOUSE_UP: 35,
                            MouseEventType.SCROLL_UP: 96,
                            MouseEventType.SCROLL_DOWN: 97,
                    }.get(mouse_event.event_type)

                    self.process.write_input('\x1b[M%s%s%s' % (
                        six.unichr(ev),
                        six.unichr(x + 33),
                        six.unichr(y + 33)))

    def is_focusable(self):
        return not self.process.suspended

class _Window(Window):
    """
    """
    def __init__(self, terminal_control, **kw):
        self.terminal_control = terminal_control
        super(_Window, self).__init__(**kw)

    def write_to_screen(self, *a, **kw):
        # Make sure that the bottom of the terminal is always visible.
        screen = self.terminal_control.process.screen

        # NOTE: the +1 is required because max_y starts counting at 0, while
        #       lines counts the numbers of lines, starting at 1 for one line.
        self.vertical_scroll = screen.max_y - screen.lines + 1

        super(_Window, self).write_to_screen(*a, **kw)

class Terminal(object):
    def __init__(self, command=['/bin/bash'], before_exec_func=None,
                 bell_func=None, style='', width=None, height=None,
                 done_callback=None):

        self.terminal_control = TerminalControl(
            command=command, before_exec_func=before_exec_func,
            bell_func=bell_func, done_callback=done_callback)

        self.terminal_window = _Window(
            terminal_control=self.terminal_control,
            content=self.terminal_control,
            wrap_lines=False)

        # Key bindigns for copy buffer.
        kb = KeyBindings()
        @kb.add('c-c')
        def _(event):
            self.exit_copy_mode()

        self.search_toolbar = SearchToolbar(
            forward_search_prompt='Search down: ',
            backward_search_prompt='Search up: ')

        self.copy_buffer = Buffer(read_only=True)
        self.copy_buffer_control = BufferControl(
            buffer=self.copy_buffer,
            search_buffer_control=self.search_toolbar.control,
            input_processors=[
                _UseStyledTextrocessor(self),
                HighlightSearchProcessor(),
                HighlightIncrementalSearchProcessor(),
            ],
            preview_search=True,  # XXX: not sure why we need twice preview_search.
            key_bindings=kb)

        self.copy_window = Window(content=self.copy_buffer_control, wrap_lines=False)

        self.is_copying = False
        self.styled_copy_lines = []  # List of lists of (style, text) tuples, for each line.

        @Condition
        def is_copying():
            return self.is_copying

        self.container = HSplit([
            # Either show terminal window or copy buffer.
            VSplit([  # XXX: this nested VSplit should not have been necessary,
                        # but the ConditionalContainer which width can become
                        # zero will collapse the other elements.
                ConditionalContainer(self.terminal_window, filter=~is_copying),
                ConditionalContainer(self.copy_window, filter=is_copying),
            ]),
            ConditionalContainer(self.search_toolbar, filter=is_copying),
        ], style=style, width=width, height=height)

    def enter_copy_mode(self):
        # Suspend process.
        self.terminal_control.process.suspend()

        # Copy content into copy buffer.
        data_buffer = self.terminal_control.process.screen.pt_screen.data_buffer

        text = []
        styled_lines = []

        if data_buffer:
            for line_index in range(min(data_buffer), max(data_buffer) + 1):
                line = data_buffer[line_index]
                styled_line = []

                if line:
                    for column_index in range(0, max(line) + 1):
                        char = line[column_index]
                        text.append(char.char)
                        styled_line.append((char.style, char.char))

                text.append('\n')
                styled_lines.append(styled_line)
            text.pop()  # Drop last line ending.

        text = ''.join(text)

        self.copy_buffer.set_document(
            Document(text=text, cursor_position=len(text)),
            bypass_readonly=True)

        self.styled_lines = styled_lines

        # Enter copy mode.
        self.is_copying = True
        get_app().layout.focus(self.copy_window)

    def exit_copy_mode(self):
        # Resume process.
        self.terminal_control.process.resume()

        # focus terminal again.
        self.is_copying = False
        get_app().layout.focus(self.terminal_window)

    def __pt_container__(self):
        return self.container

    @property
    def process(self):
        return self.terminal_control.process

class _UseStyledTextrocessor(Processor):
    """
    In order to allow highlighting of the copy region, we use a preprocessed
    list of (style, text) tuples. This processor returns just that list for the
    given pane.

    This processor should go before all others, because it replaces the list of
    (style, text) tuples.
    """
    def __init__(self, terminal):
        self.terminal = terminal

    def apply_transformation(self, transformation_input):
        try:
            line = self.terminal.styled_lines[transformation_input.lineno]
        except IndexError:
            line = []
        return Transformation(line)
<|endoftext|>


<commit_before>import sys, os
import logging
import html
import json
from base64 import b64decode, b64encode
from PyQt5.QtCore import Qt, QRectF
from PyQt5.QtGui import QTextCursor, QKeySequence
from PyQt5.QtWidgets import \
  QMainWindow, QTextEdit, QDockWidget, \
  QMenuBar, QMenu, QAction

import log
import fileloader
from sheetwidgets import SheetWidget
from graphwidget import GraphWidget
from tools import NopTool
from toolwidgets import FitToolWidget, IADToolWidget
from commonwidgets import TabWidgetWithCheckBox
from sourceswidget import SourcesWidget
from dialogs import FileDialog

class MainWindow(QMainWindow):
  saveStateVersion = 1

  def __init__(self, configFilename):
    super().__init__()
    self.configFilename = configFilename

    actions = [
      ('act_file_import',     '&Import file',    QKeySequence(Qt.CTRL | Qt.Key_I), self.showImportFileDialog),
      ('act_session_open',    '&Open session',   QKeySequence.Open, self.showOpenSessionDialog),
      ('act_session_save',    '&Save session',   QKeySequence.Save, self.saveSession),
      ('act_session_save_as', 'Save session as', QKeySequence.SaveAs, lambda: self.saveSession(True)),
    ]

    for name, label, key, func in actions:
      act = QAction(label)
      act.setShortcut(key)
      act.triggered.connect(func)
      setattr(self, name, act)

    menubar = self.menuBar()
    filemenu = menubar.addMenu('&File')
    filemenu.addAction(self.act_file_import)

    sessionmenu = menubar.addMenu('&Session')
    sessionmenu.addAction(self.act_session_open)
    sessionmenu.addAction(self.act_session_save)
    sessionmenu.addAction(self.act_session_save_as)

    self.graphWidget = GraphWidget()
    self.setCentralWidget(self.graphWidget)

    self.sourcesWidget = SourcesWidget()
    self.sourcesWidget.updateRequested.connect(self.update)
    self.sourcesWidget.hide()
    self.sourcesDockWidget = QDockWidget('Sources')
    self.sourcesDockWidget.setObjectName('dock_sources')
    self.sourcesDockWidget.setWidget(self.sourcesWidget)

    self.logTextEdit = QTextEdit()
    self.logTextEdit.setReadOnly(True)
    self.logDockWidget = QDockWidget('Log')
    self.logDockWidget.setObjectName('dock_log')
    self.logDockWidget.setWidget(self.logTextEdit)

    dock_p = None
    toolDockWidgets = []
    self.toolWidgets = [IADToolWidget()]
    self.tools = []
    self.curTool = self.toolWidgets[0].tool
    for t in self.toolWidgets:
      t.plotRequested.connect(self.plotRequested)
      dock = QDockWidget(t.label())
      dock.setObjectName('dock_%s' % t.name())
      dock.setWidget(t)
      toolDockWidgets.append(dock)
      self.tools.append(t.tool)
      self.addDockWidget(Qt.RightDockWidgetArea, dock)
      if dock_p:
        self.tabifyDockWidget(dock_p, dock)
      dock_p = dock

    self.addDockWidget(Qt.BottomDockWidgetArea, self.sourcesDockWidget)
    self.resizeDocks(toolDockWidgets, [400] * len(toolDockWidgets), Qt.Horizontal)
    self.toolWidgets[0].raise_()

    self.addDockWidget(Qt.BottomDockWidgetArea, self.logDockWidget)
    self.tabifyDockWidget(self.logDockWidget, self.sourcesDockWidget)
    self.resizeDocks([self.sourcesDockWidget, self.logDockWidget], [200, 200], Qt.Vertical)
    self.logDockWidget.raise_()

    self.resize(1000, 800)
    self.setAcceptDrops(True)

    self._prevXY = None

    self.sessionFilename = None
    self.loadConfig()

    logging.info('Drag and drop here to open multiple files')

  def dragEnterEvent(self, ev):
    if not ev.mimeData().hasUrls():
      return
    for url in ev.mimeData().urls():
      if url.isLocalFile():
        ev.acceptProposedAction()
        return

  def dropEvent(self, ev):
    if not ev.mimeData().hasUrls():
      return

    files = []
    for url in ev.mimeData().urls():
      if url.isLocalFile():
        files.append(url.toLocalFile())

    if files:
      ev.acceptProposedAction()
      self.importFiles(files)

  def showImportFileDialog(self):
    dlg = FileDialog('file_import')
    if dlg.exec_() == dlg.Accepted:
      self.importFiles([os.path.realpath(f) for f in dlg.selectedFiles()])

  def importFiles(self, filenames):
    for filename in filenames:
      try:
        f = fileloader.load(filename)
      except fileloader.UnsupportedFileException as ex:
        logging.error('Unsupported file: %s %s' % (ex.mimetype, ex.filename))
        continue
      self.addFile(filename, True, [(s, True, 'A', 'B') for s in f])
    self.update()
    self.sourcesDockWidget.raise_()

  def addFile(self, filename, checked, sheets):
    logging.debug('Add file: %s' % filename)
    self.sourcesWidget.show()
    self.sourcesWidget.addFile(filename, checked, sheets)

  def update(self, autoRange=True):
    for t in self.tools:
      t.clear()

    for sw in self.sourcesWidget.enabledSheetWidgets():
      x_ = sw.xvalues()
      y_ = sw.yvalues()
      if len(x_) == 0: x_ = list(x_) * len(y_)
      if len(x_) != len(y_): raise RuntimeError('X and Y formulae count mismatch')

      for i, (x, y) in enumerate(zip(x_, y_)):
        name = sw.sheet.name
        if len(y_) > 1: name += ':%s' % i
        for t in self.tools:
          t.add(x, y, name)

    self.updateGraph()
    if autoRange:
      self.graphWidget.autoRange()

  def plotRequested(self, tool, autoRange):
    self.curTool = tool

    XY = [(sw.x(), sw.y()) for sw in self.sourcesWidget.enabledSheetWidgets()]
    if XY != self._prevXY:
      self._prevXY = XY
      self.update()
      return

    self.updateGraph()
    if autoRange:
      self.graphWidget.autoRange()

  def updateGraph(self):
    self.graphWidget.clearItems()
    for l in self.curTool.getLines():
      self.graphWidget.add(l)

  def log_(self, html, activate=False):
    self.logTextEdit.moveCursor(QTextCursor.End)
    self.logTextEdit.insertHtml(html)
    s = self.logTextEdit.verticalScrollBar()
    s.setValue(s.maximum())
    if activate:
      self.logDockWidget.raise_()

  def closeEvent(self, ev):
    self.saveConfig()
    ev.accept()

  def showOpenSessionDialog(self):
    dlg = FileDialog('session_open')
    if dlg.exec_() != dlg.Accepted:
      return

    filename = os.path.realpath(dlg.selectedFiles()[0])
    try:
      self.loadSession(json.load(open(filename)))
    except:
      log.excepthook(*sys.exc_info())
      return

    self.sessionFilename = filename
    self.update()

  def saveSession(self, forceShowDialog=False):
    if self.sessionFilename is None or forceShowDialog:
      dlg = FileDialog('session_save')
      if dlg.exec_() != dlg.Accepted:
        return
      self.sessionFilename = os.path.realpath(dlg.selectedFiles()[0])

    obj = self.createSessionData()
    json.dump(obj, open(self.sessionFilename, 'w'))

  def loadSession(self, sess):
    logging.debug('Loading session')

    self.sourcesWidget.removeAllFiles()

    if 'files' in sess:
      for f in sess['files']:
        checked = f['enabled']
        filename = f['filename']
        sheets = []

        try:
          book = fileloader.load(filename)
        except:
          log.excepthook(*sys.exc_info())
          continue

        for s in f['sheets']:
          c = s['enabled']
          i = s['index']
          x = s['x']
          y = s['y']
          sheets.append((book.getSheet(i), c, x, y))

        self.addFile(filename, checked, sheets)

    if 'graph' in sess:
      graph = sess['graph']
      if 'range' in graph:
        self.graphWidget.setRange(QRectF(*graph['range']))

    if 'tools' in sess:
      tools = sess['tools']
      for t in self.toolWidgets:
        if t.name() in tools:
          try:
            t.restoreState(tools[t.name()])
          except:
            log.excepthook(*sys.exc_info())
            pass

  def createSessionData(self):
    files = []
    for fn, fc, sw_ in self.sourcesWidget.files():
      sheets = []
      for sw, sc in sw_:
        sheets.append({
          'enabled': sc,
          'index': sw.sheet.idx,
          'x': sw.x(),
          'y': sw.y()
        })
      files.append({
        'enabled': fc,
        'filename': fn,
        'sheets': sheets
      })

    r = self.graphWidget.viewRect()
    obj = {
      'files': files,
      'graph': {
        'range': [r.x(), r.y(), r.width(), r.height()]
      },
      'tools': dict([(t.name(), t.saveState()) for t in self.toolWidgets]),
    }

    return obj

  def loadConfig(self):
    if not os.path.exists(self.configFilename):
      return

    logging.debug('Loading config file')
    obj = json.load(open(self.configFilename))

    if 'mainwindow' in obj:
      mw = obj['mainwindow']
      if 'state' in mw:
        state = b64decode(mw['state'])
        try:
          self.restoreState(state, self.saveStateVersion)
        except:
          log.excepthook(*sys.exc_info())

    if 'filedialogs' in obj:
      fd = obj['filedialogs']
      if 'state' in fd and fd['state'] is not None:
        FileDialog.state = b64decode(fd['state'])
      if 'states' in fd: FileDialog.states.update(fd['states'])

    if 'session' in obj:
      self.loadSession(obj['session'])

    self.update()
    self.sourcesDockWidget.raise_()

  def saveConfig(self):
    fdstate = None
    if FileDialog.state is not None: fdstate = str(b64encode(FileDialog.state), 'ascii')
    obj = {
      'session': self.createSessionData(),
      'mainwindow': {
        'state': str(b64encode(self.saveState(self.saveStateVersion)), 'ascii')
      },
      'filedialogs': {
        'state': fdstate,
        'states': FileDialog.states
      }
    }
    json.dump(obj, open(self.configFilename, 'w'))
<commit_msg>[IAD] Force change plot mode to "orig" when imported a new file<commit_after>import sys, os
import logging
import html
import json
from base64 import b64decode, b64encode
from PyQt5.QtCore import Qt, QRectF
from PyQt5.QtGui import QTextCursor, QKeySequence
from PyQt5.QtWidgets import \
  QMainWindow, QTextEdit, QDockWidget, \
  QMenuBar, QMenu, QAction

import log
import fileloader
from sheetwidgets import SheetWidget
from graphwidget import GraphWidget
from tools import NopTool
from toolwidgets import FitToolWidget, IADToolWidget
from commonwidgets import TabWidgetWithCheckBox
from sourceswidget import SourcesWidget
from dialogs import FileDialog

class MainWindow(QMainWindow):
  saveStateVersion = 1

  def __init__(self, configFilename):
    super().__init__()
    self.configFilename = configFilename

    actions = [
      ('act_file_import',     '&Import file',    QKeySequence(Qt.CTRL | Qt.Key_I), self.showImportFileDialog),
      ('act_session_open',    '&Open session',   QKeySequence.Open, self.showOpenSessionDialog),
      ('act_session_save',    '&Save session',   QKeySequence.Save, self.saveSession),
      ('act_session_save_as', 'Save session as', QKeySequence.SaveAs, lambda: self.saveSession(True)),
    ]

    for name, label, key, func in actions:
      act = QAction(label)
      act.setShortcut(key)
      act.triggered.connect(func)
      setattr(self, name, act)

    menubar = self.menuBar()
    filemenu = menubar.addMenu('&File')
    filemenu.addAction(self.act_file_import)

    sessionmenu = menubar.addMenu('&Session')
    sessionmenu.addAction(self.act_session_open)
    sessionmenu.addAction(self.act_session_save)
    sessionmenu.addAction(self.act_session_save_as)

    self.graphWidget = GraphWidget()
    self.setCentralWidget(self.graphWidget)

    self.sourcesWidget = SourcesWidget()
    self.sourcesWidget.updateRequested.connect(self.update)
    self.sourcesWidget.hide()
    self.sourcesDockWidget = QDockWidget('Sources')
    self.sourcesDockWidget.setObjectName('dock_sources')
    self.sourcesDockWidget.setWidget(self.sourcesWidget)

    self.logTextEdit = QTextEdit()
    self.logTextEdit.setReadOnly(True)
    self.logDockWidget = QDockWidget('Log')
    self.logDockWidget.setObjectName('dock_log')
    self.logDockWidget.setWidget(self.logTextEdit)

    dock_p = None
    toolDockWidgets = []
    self.toolIAD = IADToolWidget()
    self.toolWidgets = [self.toolIAD]
    self.tools = []
    self.curTool = self.toolWidgets[0].tool
    for t in self.toolWidgets:
      t.plotRequested.connect(self.plotRequested)
      dock = QDockWidget(t.label())
      dock.setObjectName('dock_%s' % t.name())
      dock.setWidget(t)
      toolDockWidgets.append(dock)
      self.tools.append(t.tool)
      self.addDockWidget(Qt.RightDockWidgetArea, dock)
      if dock_p:
        self.tabifyDockWidget(dock_p, dock)
      dock_p = dock

    self.addDockWidget(Qt.BottomDockWidgetArea, self.sourcesDockWidget)
    self.resizeDocks(toolDockWidgets, [400] * len(toolDockWidgets), Qt.Horizontal)
    self.toolWidgets[0].raise_()

    self.addDockWidget(Qt.BottomDockWidgetArea, self.logDockWidget)
    self.tabifyDockWidget(self.logDockWidget, self.sourcesDockWidget)
    self.resizeDocks([self.sourcesDockWidget, self.logDockWidget], [200, 200], Qt.Vertical)
    self.logDockWidget.raise_()

    self.resize(1000, 800)
    self.setAcceptDrops(True)

    self._prevXY = None

    self.sessionFilename = None
    self.loadConfig()

    logging.info('Drag and drop here to open multiple files')

  def dragEnterEvent(self, ev):
    if not ev.mimeData().hasUrls():
      return
    for url in ev.mimeData().urls():
      if url.isLocalFile():
        ev.acceptProposedAction()
        return

  def dropEvent(self, ev):
    if not ev.mimeData().hasUrls():
      return

    files = []
    for url in ev.mimeData().urls():
      if url.isLocalFile():
        files.append(url.toLocalFile())

    if files:
      ev.acceptProposedAction()
      self.importFiles(files)

  def showImportFileDialog(self):
    dlg = FileDialog('file_import')
    if dlg.exec_() == dlg.Accepted:
      self.importFiles([os.path.realpath(f) for f in dlg.selectedFiles()])

  def importFiles(self, filenames):
    for filename in filenames:
      try:
        f = fileloader.load(filename)
      except fileloader.UnsupportedFileException as ex:
        logging.error('Unsupported file: %s %s' % (ex.mimetype, ex.filename))
        continue
      self.addFile(filename, True, [(s, True, 'A', 'B') for s in f])
    self.toolIAD.mode = 'orig'
    self.update()
    self.sourcesDockWidget.raise_()

  def addFile(self, filename, checked, sheets):
    logging.debug('Add file: %s' % filename)
    self.sourcesWidget.show()
    self.sourcesWidget.addFile(filename, checked, sheets)

  def update(self, autoRange=True):
    for t in self.tools:
      t.clear()

    for sw in self.sourcesWidget.enabledSheetWidgets():
      x_ = sw.xvalues()
      y_ = sw.yvalues()
      if len(x_) == 0: x_ = list(x_) * len(y_)
      if len(x_) != len(y_): raise RuntimeError('X and Y formulae count mismatch')

      for i, (x, y) in enumerate(zip(x_, y_)):
        name = sw.sheet.name
        if len(y_) > 1: name += ':%s' % i
        for t in self.tools:
          t.add(x, y, name)

    self.updateGraph()
    if autoRange:
      self.graphWidget.autoRange()

  def plotRequested(self, tool, autoRange):
    self.curTool = tool

    XY = [(sw.x(), sw.y()) for sw in self.sourcesWidget.enabledSheetWidgets()]
    if XY != self._prevXY:
      self._prevXY = XY
      self.update()
      return

    self.updateGraph()
    if autoRange:
      self.graphWidget.autoRange()

  def updateGraph(self):
    self.graphWidget.clearItems()
    for l in self.curTool.getLines():
      self.graphWidget.add(l)

  def log_(self, html, activate=False):
    self.logTextEdit.moveCursor(QTextCursor.End)
    self.logTextEdit.insertHtml(html)
    s = self.logTextEdit.verticalScrollBar()
    s.setValue(s.maximum())
    if activate:
      self.logDockWidget.raise_()

  def closeEvent(self, ev):
    self.saveConfig()
    ev.accept()

  def showOpenSessionDialog(self):
    dlg = FileDialog('session_open')
    if dlg.exec_() != dlg.Accepted:
      return

    filename = os.path.realpath(dlg.selectedFiles()[0])
    try:
      self.loadSession(json.load(open(filename)))
    except:
      log.excepthook(*sys.exc_info())
      return

    self.sessionFilename = filename
    self.update()

  def saveSession(self, forceShowDialog=False):
    if self.sessionFilename is None or forceShowDialog:
      dlg = FileDialog('session_save')
      if dlg.exec_() != dlg.Accepted:
        return
      self.sessionFilename = os.path.realpath(dlg.selectedFiles()[0])

    obj = self.createSessionData()
    json.dump(obj, open(self.sessionFilename, 'w'))

  def loadSession(self, sess):
    logging.debug('Loading session')

    self.sourcesWidget.removeAllFiles()

    if 'files' in sess:
      for f in sess['files']:
        checked = f['enabled']
        filename = f['filename']
        sheets = []

        try:
          book = fileloader.load(filename)
        except:
          log.excepthook(*sys.exc_info())
          continue

        for s in f['sheets']:
          c = s['enabled']
          i = s['index']
          x = s['x']
          y = s['y']
          sheets.append((book.getSheet(i), c, x, y))

        self.addFile(filename, checked, sheets)

    if 'graph' in sess:
      graph = sess['graph']
      if 'range' in graph:
        self.graphWidget.setRange(QRectF(*graph['range']))

    if 'tools' in sess:
      tools = sess['tools']
      for t in self.toolWidgets:
        if t.name() in tools:
          try:
            t.restoreState(tools[t.name()])
          except:
            log.excepthook(*sys.exc_info())
            pass

  def createSessionData(self):
    files = []
    for fn, fc, sw_ in self.sourcesWidget.files():
      sheets = []
      for sw, sc in sw_:
        sheets.append({
          'enabled': sc,
          'index': sw.sheet.idx,
          'x': sw.x(),
          'y': sw.y()
        })
      files.append({
        'enabled': fc,
        'filename': fn,
        'sheets': sheets
      })

    r = self.graphWidget.viewRect()
    obj = {
      'files': files,
      'graph': {
        'range': [r.x(), r.y(), r.width(), r.height()]
      },
      'tools': dict([(t.name(), t.saveState()) for t in self.toolWidgets]),
    }

    return obj

  def loadConfig(self):
    if not os.path.exists(self.configFilename):
      return

    logging.debug('Loading config file')
    obj = json.load(open(self.configFilename))

    if 'mainwindow' in obj:
      mw = obj['mainwindow']
      if 'state' in mw:
        state = b64decode(mw['state'])
        try:
          self.restoreState(state, self.saveStateVersion)
        except:
          log.excepthook(*sys.exc_info())

    if 'filedialogs' in obj:
      fd = obj['filedialogs']
      if 'state' in fd and fd['state'] is not None:
        FileDialog.state = b64decode(fd['state'])
      if 'states' in fd: FileDialog.states.update(fd['states'])

    if 'session' in obj:
      self.loadSession(obj['session'])

    self.update()
    self.sourcesDockWidget.raise_()

  def saveConfig(self):
    fdstate = None
    if FileDialog.state is not None: fdstate = str(b64encode(FileDialog.state), 'ascii')
    obj = {
      'session': self.createSessionData(),
      'mainwindow': {
        'state': str(b64encode(self.saveState(self.saveStateVersion)), 'ascii')
      },
      'filedialogs': {
        'state': fdstate,
        'states': FileDialog.states
      }
    }
    json.dump(obj, open(self.configFilename, 'w'))
<|endoftext|>


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

import argparse
import os

__author__ = "Gene Blanchard"
__email__ = "me@geneblanchard.com"

'''
DOC:

'''

# Template for the html file
def make_html(folder_name, body):
	html_string = """
<!DOCTYPE html>

		<meta charset="UTF-8">
		<title>{}</title>
		<link rel="stylesheet" type="text/css" href="https://cdn.rawgit.com/gblanchard4/sublimetext-markdown-preview/master/markdown.css">

		{}

	""".format(os.path.basename(folder_name), body)
	return html_string

# Make the main landing page
def make_overview_body(analysis_folder):
	# String buffer to hold the body
	body_string = ""

	# Make the heading for the page the analysis folder name
	folder_header = "
{}
\n".format(os.path.basename(analysis_folder))
	# Add it to the string buffer
	body_string += folder_header
	# Add the secondary folders to the string buffer
	for secondary_folder in list_subdirectories(analysis_folder):
		filepath =  analysis_folder+'/'+secondary_folder
		folder_link ='\t\t
{}
\n'.format(filepath+'/overview.html', secondary_folder)
		body_string+=folder_link
	return body_string

# Helper function to return only the secondary folders
def list_subdirectories(parent_dir):
	return [name for name in os.listdir(parent_dir)
		if os.path.isdir(os.path.join(parent_dir, name))]

# Make the html body for the secondary folders
def make_secondary_body(secondary_folder_path):
	# String buffer to hold the body
	body_string = ""

	# The name of the secondary folder
	folder_name = os.path.basename(secondary_folder_path)
	# Make the heading for the page the analysis folder name
	body_string += '
{}
\n'.format(os.path.basename(folder_name))
	# Click here to open analysis folder in browser
	body_string += '
(Click To Manually Browse Analysis Folder)

'.format(secondary_folder_path)
	'''
	# Key files listing (mapping, biom, tree)
	mapping_file_path = secondary_folder_path+'/'
	biom_file_path =
	tree_file_path =
	key_files ="""

Base Files:

Mapping File

Biom File

Tree File

	""".format()
	'''
	# Biom Stats
	body_string += biom_summary_to_html_table(secondary_folder_path)

	# Alpha diversity
	body_string += '
Alpha Diversity

Click on the graph below for an interactive view
\n'
	# Get an observed species plot to display
	alpha_graphic = [png for png in os.listdir("{}/alpha_diversity/alpha_rarefaction_plots/average_plots/".format(secondary_folder_path)) if png.startswith('observed_species')][0]
	alpha_graphic_path = "{}/alpha_diversity/alpha_rarefaction_plots/average_plots/{}".format(secondary_folder_path, alpha_graphic)
	body_string += '
\n'.format(secondary_folder_path,alpha_graphic_path)

	# Beta Diversity
	body_string += '
Beta Diversity
\n'
	beta_2d = '
2D Plots

More axes can be seen by clicking on the plots
\n'
	beta_3d = '
3D Plots

Follow the link for the Emperor 3D viewer
\n'
	anosim = '
ANOSIM Analysis

ANOSIM is a method that tests whether two or more groups of samples are significantly different.
\n'
	anosim_unweighted = '
ANOSIM Unweighted
\n'
	anosim_weighted = '
ANOSIM Weighted
\n'
	for beta_folder in os.listdir(secondary_folder_path+"/beta_diversity/"):
		# For the 2d plots
		if beta_folder.startswith('2d_unweighted'):
			beta_png = get_beta_image_2d("{0}/beta_diversity/2d_unweighted_unifrac_plots/unweighted_unifrac_pc_2D_PCoA_plots.html".format(secondary_folder_path))
			beta_2d += '
Unweighted
\n'.format(secondary_folder_path, beta_png)
		if beta_folder.startswith('2d_weighted'):
			beta_png = get_beta_image_2d("{0}/beta_diversity/2d_weighted_unifrac_plots/weighted_unifrac_pc_2D_PCoA_plots.html".format(secondary_folder_path))
			beta_2d += '
Weighted
\n'.format(secondary_folder_path, beta_png)
		# For the 3d Emperor plots
		if '_emperor_' in beta_folder and beta_folder.startswith('unweighted_'):
			beta_3d += '
Unweighted
\n'.format(secondary_folder_path)
		if '_emperor_' in beta_folder and beta_folder.startswith('weighted_'):
			beta_3d += '
Weighted
\n'.format(secondary_folder_path)
		if beta_folder.startswith('ANOSIM_') and '_unweighted' in beta_folder:
			anosim_unweighted += '
{1}
\n'.format(secondary_folder_path, beta_folder)
		if beta_folder.startswith('ANOSIM_') and '_weighted' in beta_folder:
			anosim_weighted += '
{1}
\n'.format(secondary_folder_path, beta_folder)

	# Add the 2d and 3d html
	body_string += beta_2d
	body_string += beta_3d
	# Add anosim
	body_string += anosim + anosim_unweighted + anosim_weighted +"
\n"

	# Taxa summary
	body_string += '
Taxa Summary

Follow the links for a taxa breakdown of your samples
\n'
	# Create the links and graphics for the bar and area charts
	for taxa_folder in os.listdir(secondary_folder_path+'/taxa_summary/'):
		#bar_png, area_png = get_taxa_image("{1}/taxa_summary/{0}/taxa_summary_plots/".format(taxa_folder, secondary_folder_path))
		#print bar_png
		#print area_png
		#body_string += '
{0}
\n\n\n'.format(taxa_folder, secondary_folder_path, bar_png, area_png)
		body_string += '
{0}
\n
Bar Charts
\n
Area Charts
\n'.format(taxa_folder, secondary_folder_path)
	# Section break
	body_string += '
\n'

	return body_string

# Get an image for the bar and area charts to use, return a tuple (barchart.png, areachart.png)

def get_taxa_image(taxa_folder_path):
	# Get barchart png
	with open('{}bar_charts.html'.format(taxa_folder_path), 'r') as bar:
		file_buffer = bar.read()
		bar_png = taxa_folder_path+file_buffer.split(' border=1 ismap usemap="#pointsrect1" />
	')[0].split('

	')[0].split('
<img src=')[1].strip("'")
	return bar_png, area_png

# Warning: May not be reliable in the future!
def get_beta_image_2d(beta_html):
	with open(beta_html, 'r') as html:
		file_buffer = html.read()
		beta_png = file_buffer.split('.png')[0].split('<img src="./')[-1]+'.png'
	return beta_png

# Turn a biom file into an html table of useful numbers
def biom_summary_to_html_table(secondary_folder_path):
	with open(secondary_folder_path+'/table_summary.txt', 'r') as table_file:
		# Table header string
		table_string = '
Biom Table Stats
\n
'.format(secondary_folder_path)
		for line in table_file:
			# Get important information
			# Number of samples
			if line.startswith('Num samples:'):
				value_list = line.rstrip().split(': ')
				table_string += '
{}	{}
\n'.format(value_list[0], value_list[1])
			if line.startswith('Num observations: '):
				value_list = line.rstrip().split(': ')
				table_string += '
{}	{}
\n'.format(value_list[0], value_list[1])
			if line.startswith('Total count: '):
				value_list = line.rstrip().split(': ')
				table_string += '
{}	{}
\n'.format(value_list[0], value_list[1])
			if line.startswith(' Min: '):
				value_list = line.rstrip().split(': ')
				table_string += '
{}	{}
\n'.format(value_list[0], value_list[1])
			if line.startswith(' Max: '):
				value_list = line.rstrip().split(': ')
				table_string += '
{}	{}
\n'.format(value_list[0], value_list[1])
			if line.startswith(' Median: '):
				value_list = line.rstrip().split(': ')
				table_string += '
{}	{}
\n'.format(value_list[0], value_list[1])
			if line.startswith(' Mean: '):
				value_list = line.rstrip().split(': ')
				table_string += '
{}	{}
\n'.format(value_list[0], value_list[1])
		#Close table
		table_string += '

\n'
	return table_string

def main():
	# Get command line arguments
	parser = argparse.ArgumentParser(description='Calculate stats on the input fastqs')
	# Secondary analysis directory generated with automated script
	parser.add_argument('-i','--input',dest='input', help='The secondary analysis folder to generate documentation on', required=True)
	# Parse arguments
	args = parser.parse_args()
	# Set arguments
	analysis_folder = os.path.abspath(args.input)

	# Error checking
	ERROR = False
	#Make sure the input directory exists
	if not os.path.isdir(analysis_folder):
		print "Input directory not found"
		ERROR = True

	# Write main overview page
	with open(analysis_folder+'/overview.html', 'w') as html_file:
		html_file.write(make_html(analysis_folder, make_overview_body(analysis_folder)))

	# Write the secondary pages
	for secondary_folder in list_subdirectories(analysis_folder):
		path = analysis_folder+'/'+secondary_folder
		with open(path+'/overview.html', 'w') as html_file:
			html_file.write(make_html(path, make_secondary_body(analysis_folder+'/'+secondary_folder)))

if __name__ == '__main__':
	main()
<commit_msg>added anosim tables<commit_after>#! /usr/bin/env python

import argparse
import os

__author__ = "Gene Blanchard"
__email__ = "me@geneblanchard.com"

'''
DOC:

'''

# Template for the html file
def make_html(folder_name, body):
	html_string = """
<!DOCTYPE html>

		<meta charset="UTF-8">
		<title>{}</title>
		<link rel="stylesheet" type="text/css" href="https://cdn.rawgit.com/gblanchard4/sublimetext-markdown-preview/master/markdown.css">

		{}

	""".format(os.path.basename(folder_name), body)
	return html_string

# Make the main landing page
def make_overview_body(analysis_folder):
	# String buffer to hold the body
	body_string = ""

	# Make the heading for the page the analysis folder name
	folder_header = "
{}
\n".format(os.path.basename(analysis_folder))
	# Add it to the string buffer
	body_string += folder_header
	# Add the secondary folders to the string buffer
	for secondary_folder in list_subdirectories(analysis_folder):
		filepath =  analysis_folder+'/'+secondary_folder
		folder_link ='\t\t
{}
\n'.format(filepath+'/overview.html', secondary_folder)
		body_string+=folder_link
	return body_string

# Helper function to return only the secondary folders
def list_subdirectories(parent_dir):
	return [name for name in os.listdir(parent_dir)
		if os.path.isdir(os.path.join(parent_dir, name))]

# Make the html body for the secondary folders
def make_secondary_body(secondary_folder_path):
	# String buffer to hold the body
	body_string = ""

	# The name of the secondary folder
	folder_name = os.path.basename(secondary_folder_path)
	# Make the heading for the page the analysis folder name
	body_string += '
{}
\n'.format(os.path.basename(folder_name))
	# Click here to open analysis folder in browser
	body_string += '
(Click To Manually Browse Analysis Folder)

'.format(secondary_folder_path)
	'''
	# Key files listing (mapping, biom, tree)
	mapping_file_path = secondary_folder_path+'/'
	biom_file_path =
	tree_file_path =
	key_files ="""

Base Files:

Mapping File

Biom File

Tree File

	""".format()
	'''
	# Biom Stats
	body_string += biom_summary_to_html_table(secondary_folder_path)

	# Alpha diversity
	body_string += '
Alpha Diversity

Click on the graph below for an interactive view
\n'
	# Get an observed species plot to display
	alpha_graphic = [png for png in os.listdir("{}/alpha_diversity/alpha_rarefaction_plots/average_plots/".format(secondary_folder_path)) if png.startswith('observed_species')][0]
	alpha_graphic_path = "{}/alpha_diversity/alpha_rarefaction_plots/average_plots/{}".format(secondary_folder_path, alpha_graphic)
	body_string += '
\n'.format(secondary_folder_path,alpha_graphic_path)

	# Beta Diversity
	body_string += '
Beta Diversity
\n'
	beta_2d = '
2D Plots

More axes can be seen by clicking on the plots
\n'
	beta_3d = '
3D Plots

Follow the link for the Emperor 3D viewer
\n'
	anosim = '
ANOSIM Analysis

ANOSIM is a method that tests whether two or more groups of samples are significantly different.
\n'
	anosim_unweighted = '
ANOSIM Unweighted
\n'
	anosim_weighted = '
ANOSIM Weighted
\n'
	for beta_folder in os.listdir(secondary_folder_path+"/beta_diversity/"):
		# For the 2d plots
		if beta_folder.startswith('2d_unweighted'):
			beta_png = get_beta_image_2d("{0}/beta_diversity/2d_unweighted_unifrac_plots/unweighted_unifrac_pc_2D_PCoA_plots.html".format(secondary_folder_path))
			beta_2d += '
Unweighted
\n'.format(secondary_folder_path, beta_png)
		if beta_folder.startswith('2d_weighted'):
			beta_png = get_beta_image_2d("{0}/beta_diversity/2d_weighted_unifrac_plots/weighted_unifrac_pc_2D_PCoA_plots.html".format(secondary_folder_path))
			beta_2d += '
Weighted
\n'.format(secondary_folder_path, beta_png)
		# For the 3d Emperor plots
		if '_emperor_' in beta_folder and beta_folder.startswith('unweighted_'):
			beta_3d += '
Unweighted
\n'.format(secondary_folder_path)
		if '_emperor_' in beta_folder and beta_folder.startswith('weighted_'):
			beta_3d += '
Weighted
\n'.format(secondary_folder_path)
		if beta_folder.startswith('ANOSIM_') and '_unweighted' in beta_folder:
			anosim_unweighted += '
{1}
\n'.format(secondary_folder_path, beta_folder)
			anosim_unweighted += anosim_to_html_table('/{0}/beta_diversity/{1}/anosim_results.txt'.format(secondary_folder_path, beta_folder))
		if beta_folder.startswith('ANOSIM_') and '_weighted' in beta_folder:
			anosim_weighted += '
{1}
\n'.format(secondary_folder_path, beta_folder)
			anosim_weighted += anosim_to_html_table('/{0}/beta_diversity/{1}/anosim_results.txt'.format(secondary_folder_path, beta_folder))

	# Add the 2d and 3d html
	body_string += beta_2d
	body_string += beta_3d
	# Add anosim
	body_string += anosim + anosim_unweighted + anosim_weighted +"
\n"

	# Taxa summary
	body_string += '
Taxa Summary

Follow the links for a taxa breakdown of your samples
\n'
	# Create the links and graphics for the bar and area charts
	for taxa_folder in os.listdir(secondary_folder_path+'/taxa_summary/'):
		#bar_png, area_png = get_taxa_image("{1}/taxa_summary/{0}/taxa_summary_plots/".format(taxa_folder, secondary_folder_path))
		#body_string += '
{0}
\n\n\n'.format(taxa_folder, secondary_folder_path, bar_png, area_png)
		body_string += '
{0}
\n
Bar Charts
\n
Area Charts
\n'.format(taxa_folder, secondary_folder_path)
	# Section break
	body_string += '
\n'

	# Core microbiome
	body_string += '
Core microbiome

Something something core microbiome
\n'
	# Create links to all the core microbiome stuffs
	for core_folder in os.listdir(secondary_folder_path+'/core_microbiome/'):

	return body_string

# Get an image for the bar and area charts to use, return a tuple (barchart.png, areachart.png)
def get_taxa_image(taxa_folder_path):
	# Get barchart png
	with open('{}bar_charts.html'.format(taxa_folder_path), 'r') as bar:
		file_buffer = bar.read()
		bar_png = taxa_folder_path+file_buffer.split(' border=1 ismap usemap="#pointsrect1" />
	')[0].split('

	')[0].split('
<img src=')[1].strip("'")
	return bar_png, area_png

# Warning: May not be reliable in the future!
def get_beta_image_2d(beta_html):
	with open(beta_html, 'r') as html:
		file_buffer = html.read()
		beta_png = file_buffer.split('.png')[0].split('<img src="./')[-1]+'.png'
	return beta_png

# Turn a biom file into an html table of useful numbers
def biom_summary_to_html_table(secondary_folder_path):
	with open(secondary_folder_path+'/table_summary.txt', 'r') as table_file:
		# Table header string
		table_string = '
Biom Table Stats
\n
'.format(secondary_folder_path+'/table_summary.txt')
		for line in table_file:
			# Get important information
			# Number of samples
			if line.startswith('Num samples:'):
				value_list = line.rstrip().split(': ')
				table_string += '
{}	{}
\n'.format(value_list[0], value_list[1])
			if line.startswith('Num observations: '):
				value_list = line.rstrip().split(': ')
				table_string += '
{}	{}
\n'.format(value_list[0], value_list[1])
			if line.startswith('Total count: '):
				value_list = line.rstrip().split(': ')
				table_string += '
{}	{}
\n'.format(value_list[0], value_list[1])
			if line.startswith(' Min: '):
				value_list = line.rstrip().split(': ')
				table_string += '
{}	{}
\n'.format(value_list[0], value_list[1])
			if line.startswith(' Max: '):
				value_list = line.rstrip().split(': ')
				table_string += '
{}	{}
\n'.format(value_list[0], value_list[1])
			if line.startswith(' Median: '):
				value_list = line.rstrip().split(': ')
				table_string += '
{}	{}
\n'.format(value_list[0], value_list[1])
			if line.startswith(' Mean: '):
				value_list = line.rstrip().split(': ')
				table_string += '
{}	{}
\n'.format(value_list[0], value_list[1])
		#Close table
		table_string += '

\n'
	return table_string

def anosim_to_html_table(anosim_txt):
	# Table header string
	table_string = '
\n'
	with open(anosim_txt) as anosim_file:
		for line in anosim_file:
			split_line = line.rstrip().split('\t')
			table_string += '
{}	{}	{}	{}
\n'.format(split_line[0],split_line[1],split_line[2],split_line[3])
		table_string += '
'
	return table_string

def main():
	# Get command line arguments
	parser = argparse.ArgumentParser(description='Calculate stats on the input fastqs')
	# Secondary analysis directory generated with automated script
	parser.add_argument('-i','--input',dest='input', help='The secondary analysis folder to generate documentation on', required=True)
	# Parse arguments
	args = parser.parse_args()
	# Set arguments
	analysis_folder = os.path.abspath(args.input)

	# Error checking
	ERROR = False
	#Make sure the input directory exists
	if not os.path.isdir(analysis_folder):
		print "Input directory not found"
		ERROR = True

	# Write main overview page
	with open(analysis_folder+'/overview.html', 'w') as html_file:
		html_file.write(make_html(analysis_folder, make_overview_body(analysis_folder)))

	# Write the secondary pages
	for secondary_folder in list_subdirectories(analysis_folder):
		path = analysis_folder+'/'+secondary_folder
		with open(path+'/overview.html', 'w') as html_file:
			html_file.write(make_html(path, make_secondary_body(analysis_folder+'/'+secondary_folder)))

if __name__ == '__main__':
	main()
<|endoftext|>


<commit_before>from django.db import models

from osf import OSFLine

from .DocumentState import DocumentState

class OSFDocumentState(DocumentState):

    def to_dict(self):
        return {
            "shownotes": self.get_shownotes_list(),
            "header": {}
        }

    def get_shownotes_list(self, notes=None, level=0):
        root = []

        if not notes:
            notes = self.shownotes

        for note in notes.all().order_by('order'):
            me = note.to_dict(level)
            me['shownotes'] = self.get_shownotes_list(notes=note.shownotes, level=level + 1)
            root.append(me)

        return root

    def __str__(self):
        return "OSFDocumentState, " + str(self.shownotes.count()) + " notes"

class OSFTag(models.Model):
    name = models.CharField(max_length=50)
    short = models.CharField(max_length=20)
    description = models.CharField(max_length=200)

    def __str__(self):
        return "#" + self.name + " (#" + self.short + ")"

class OSFNote(models.Model):
    state = models.ForeignKey(OSFDocumentState, related_name="shownotes")
    parent = models.ForeignKey('self', related_name="shownotes", null=True, blank=True)
    order = models.IntegerField()
    timestamp = models.BigIntegerField(null=True)
    title = models.CharField(max_length=1000)
    url = models.URLField(null=True)
    tags = models.ManyToManyField(OSFTag, related_name="notes")

    def to_dict(self, level):
        return {
            'timestamp': self.timestamp,
            'title': self.title,
            'url': self.url,
            'revision': 'revision' in self.tags.all(),
            'valid': True,
            'errorMessages': [],
            'tags': [tag.name for tag in self.tags.all()],
            'level': level,
        }

    class Meta:
        unique_together = ('order', 'state')
<commit_msg>osfnote: switch title to TextField<commit_after>from django.db import models

from osf import OSFLine

from .DocumentState import DocumentState

class OSFDocumentState(DocumentState):

    def to_dict(self):
        return {
            "shownotes": self.get_shownotes_list(),
            "header": {}
        }

    def get_shownotes_list(self, notes=None, level=0):
        root = []

        if not notes:
            notes = self.shownotes

        for note in notes.all().order_by('order'):
            me = note.to_dict(level)
            me['shownotes'] = self.get_shownotes_list(notes=note.shownotes, level=level + 1)
            root.append(me)

        return root

    def __str__(self):
        return "OSFDocumentState, " + str(self.shownotes.count()) + " notes"

class OSFTag(models.Model):
    name = models.CharField(max_length=50)
    short = models.CharField(max_length=20)
    description = models.CharField(max_length=200)

    def __str__(self):
        return "#" + self.name + " (#" + self.short + ")"

class OSFNote(models.Model):
    state = models.ForeignKey(OSFDocumentState, related_name="shownotes")
    parent = models.ForeignKey('self', related_name="shownotes", null=True, blank=True)
    order = models.IntegerField()
    timestamp = models.BigIntegerField(null=True)
    title = models.TextField()
    url = models.URLField(null=True)
    tags = models.ManyToManyField(OSFTag, related_name="notes")

    def to_dict(self, level):
        return {
            'timestamp': self.timestamp,
            'title': self.title,
            'url': self.url,
            'revision': 'revision' in self.tags.all(),
            'valid': True,
            'errorMessages': [],
            'tags': [tag.name for tag in self.tags.all()],
            'level': level,
        }

    class Meta:
        unique_together = ('order', 'state')
<|endoftext|>


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

from pyicloud import PyiCloudService
from math import radians, cos, sin, asin, sqrt
from datetime import datetime
from datetime import timedelta
import ConfigParser
import sys
import os
import time
import requests
import base64
import logging
import argparse

#DEFAULT_CONFIG = '/usr/sbin/iLocator_configuration.ini'
#DEFAULT_LOGFILE = '/usr/sbin/iLocatorLog.log'
DEFAULT_CONFIG = '/Users/peteraquino/Code/iLocator/iLocator_configuration.ini'
DEFAULT_LOGFILE = '/Users/peteraquino/Code/iLocator/iLocatorLog.log'

logger = logging.getLogger('iLocator')

def configurationManager(configfile):
    gConfig = ConfigParser.ConfigParser()
    try:
        gConfig.read(configfile)
        logging.info('Configuration %s loaded' % (configfile, ))
        if logger.isEnabledFor(logging.DEBUG):
            for section in gConfig.sections():
                logger.debug('Configuration[%s] %s' % (section, configSectionMap(gConfig, section)))

    except Exception, e:
        print('Exception! Please check the log: %s' % (e, ))
        logger.error('\r\nNo configuration avaialble. Please see https://github.com/trusk89/iLocatorBridge for configuration')
        sys.exit(0)

    #get the LocationItems...
    locationItems = parseMultipleSections(gConfig, 'LocationItems')

    # to stay backward compatible
    geoFences = parseMultipleSections(gConfig, 'Geofence')
    for fenceId, fence in geoFences.items():
        if 'homelatitude' in fence or 'homelongitude' in fence:
            for k in ['Latitude', 'Longitude']:
                oldkey = 'home%s' % k.lower()
                fence[k.lower()] = fence.get(oldkey, fence.get(k.lower()))

            print "Warning: found HomeLatitude/HomeLongitude in Section [Geofence%s] " % (fenceId, )
            print " - this is deprecated please use Lattitude/Longitude instead. exp: \n"
            print "[Geofence%s]" % fenceId
            for k, v in fence.items():
                if not k.startswith('home'):
                    print '%s: %s' % (k, v)
            print ""

    return (
        configSectionMap(gConfig, 'iCloud'),
        geoFences,
        configSectionMap(gConfig, 'OpenHab'),
        locationItems,
        )

def parseMultipleSections(gconfig, marker):
    return dict([
                 (section[len(marker):], configSectionMap(gconfig, section))
                 for section in gconfig.sections()
                 if section.startswith(marker)
                 ])

def configSectionMap(gConfig, section):
    dict = {}
    options = gConfig.options(section)
    for option in options:
        try:
            dict[option] = gConfig.get(section, option)
        except:
            print('Exception! Please check the log')
            logger.error('exception on %s!' % option)
            sys.exit(0)
    logger.info('Configuration %s parsed' % (section))
    return dict

def getDeviceCoordinates(gRequester, deviceId, deviceName):
    locationDictionary = None
    errorCount = 0

    StatusItem = gConfigurationOH.get('ohitem_status')

    #Initialize empty location items...
    CoordinatesItem = None
    AccuracyItem = None

    #See if there are any configured LocationItems for this device...
    for locationItemID, locationItem in gConfigurationLocationItems.items():
        #If found, assign them to the initialized vars...
        if 'deviceid'+ str(deviceName).strip() == locationItem.get('device').strip().lower():
            CoordinatesItem = locationItem.get('ohitem_coordinates')
            AccuracyItem = locationItem.get('ohitem_accuracy')
            #then exit the for loop...
            break

    while locationDictionary is None:
        try:
            locationDictionary = (gRequester.devices[deviceId].location())

        except Exception, e:
            errorCount = errorCount + 1
            print('Exception! Please check the log')
            logger.error('Could not get device coordinates. Retrying!: %s' % (e, ))
            #If configured, post error to OpenHAB status item...
            if StatusItem: postUpdate(StatusItem, str(e))

            #If we've passed the RetriesBeforeRestart count, restart the script...
            logger.info(str('Error count: %s' % (errorCount)))
            if int(errorCount) >= int(gConfigurationOH['retriesbeforerestart']):
                restartProgram()

            #If configured, send the next poll time to OpenHAB based on the config RetryInterval value...
            RetryPollTimeItem = gConfigurationOH.get('ohitem_nextpolltime')
            if RetryPollTimeItem:
                RetryPollTime = datetime.now() + timedelta(seconds=int(gConfigurationOH['retryinterval']))
                postUpdate(RetryPollTimeItem, str(RetryPollTime))

            time.sleep(int(gConfigurationOH['retryinterval']))

        pass

    #If configured, post 'Active' to OpenHAB status item...
    if StatusItem: postUpdate(StatusItem, 'Active')
    #If configured, post lat/ling coordinates to OpenHAB...
    if CoordinatesItem: postUpdate(CoordinatesItem, str(locationDictionary['latitude']) + ',' + str(locationDictionary['longitude']))
    #If configured, post accuracy to OpenHAB...
    if AccuracyItem: postUpdate(AccuracyItem, str(convertDistance(locationDictionary['horizontalAccuracy'],gConfigurationOH['distanceunit'])))

    return float(locationDictionary['latitude']), float(locationDictionary['longitude'])

def getDistance (lat, longitude, geoFence):
    return convertDistance(haversine(geoFence['latitude'], lat, geoFence['longitude'], long),gConfigurationOH['distanceunit'])

def convertDistance(meters,desiredUnit):
    if desiredUnit == 'km':
        return meters * .001
    elif desiredUnit == 'ft':
        return meters * 3.28084
    elif desiredUnit == 'mi':
        return meters * 0.000621371
    elif desiredUnit == 'nm':
        return meters * 0.000539957
    else:
        return meters

def isInGeofence(distance, geoFence):
    if float(distance) <= float(geoFence['geofenceradius']):
        return True
    else:
        return False

def haversine(lat1, lat2, lon1, lon2):
    # Thanks for this Aaron D
    # http://stackoverflow.com/questions/15736995/how-can-i-quickly-estimate-the-distance-between-two-latitude-longitude-points
    lon1 = (float(lon1))
    lat1 = (float(lat1))
    lon2 = (float(lon2))
    lat2 = (float(lat2))
    # convert decimal degrees to radians
    lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])
    # haversine formula
    dlon = lon2 - lon1
    dlat = lat2 - lat1
    a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
    c = 2 * asin(sqrt(a))
    m = 6378.137 * c * 1000
    return m

def postUpdate(variable, state):
    global gConfigurationOH

    url = '%s/rest/items/%s/state' % (gConfigurationOH['ohserver'], variable)
    try:
        req = requests.put(url, data=state, headers=basic_header())
        if req.status_code != requests.codes.ok:
            req.raise_for_status()
        logger.info('Update posted to OpenHab: %s = %s' %(variable,state))
    except Exception, e:
        print('Exception! Please check the log. Will continue execution.')
        logger.error('Could not post update to OpenHab: %s' % (e, ))

def restartProgram():
    try:
        logger.info('Restarting script...')
        os.execv(__file__, sys.argv)

    except Exception, e:
        logger.error('Restart program error: %s' % (e, ))

    return True

def basic_header():
    global gConfigurationOH

    auth = base64.encodestring('%s:%s' % (gConfigurationOH['ohusername'], gConfigurationOH['ohpassword'])).replace('\n', '')
    return {"Authorization": "Basic %s" % auth,"Content-type": "text/plain"}

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='iLocatorBridge - Bridge between iCloud location and OpenHAB')
    parser.add_argument('-c', '--config', dest='config', default=DEFAULT_CONFIG,help='Config location (default: %s)' % (DEFAULT_CONFIG, ))
    parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',help='Be more verbose in the output')
    parser.add_argument('--list-devices', dest='listDevices', action='store_true',help='Do not update anything, just prints all existing device ids')

    args = parser.parse_args()
    logging.basicConfig(
        filename=DEFAULT_LOGFILE, level=args.verbose and logging.DEBUG or logging.INFO,
        format='%(asctime)s %(message)s')

    gConfigurationiCloud, gConfigurationGeofence, gConfigurationOH, gConfigurationLocationItems = configurationManager(args.config)
    gRequester = PyiCloudService(gConfigurationiCloud['username'], gConfigurationiCloud['password'])

    if args.listDevices:
        # make it easy to get the device ids for config
        devices = gRequester.devices
        for idx, device in enumerate(devices.keys()):
            print 'device%d: %s  # %s' % (idx, device, devices[device])
    else:
        # get the first configured device for default case
        devices = [d for d in sorted(gConfigurationiCloud.keys()) if d.startswith('deviceid')]
        defaultDevice = devices[0]
        logging.debug('Default Device: %s of %s' % (defaultDevice, devices))

        while 1:
            try:
                minDesiredPollRate = -1
                coordCache = {}
                #get the OH items for current poll rate and next poll time
                variablepollingrate = gConfigurationOH.get('ohitem_pollingrate')
                variablenextpolltime = gConfigurationOH.get('ohitem_nextpolltime')
                #Loop through the geofences...
                for geoId, geoFence in gConfigurationGeofence.items():
                    # look for device overwrite on fence
                    device = geoFence.get('device') or defaultDevice
                    deviceName = device.lower() == 'deviceid' and '' or '%s ' % device[len('deviceid'):]
                    deviceId = gConfigurationiCloud[device.lower()]

                    # look for variable overwrite on fence
                    variable = geoFence.get('ohitem_presence') or gConfigurationOH.get('ohitem')
                    variablepollingmap = geoFence.get('pollingmap')
                    variabledistance = geoFence.get('ohitem_distance')

                    if not variable:
                        msg = "No OHItem is definded not in [Geofence%s] or [OpenHab]" % (geoId)
                        print msg
                        logging.error(msg)
                        continue

                    logging.debug('Device: %s(%s) -> %s' % (deviceName, variable, geoFence))

                    lat, long = coordCache[device] = getDeviceCoordinates(gRequester, deviceId, deviceName)

                    logging.info('Device %slocated @ %s,%s' % (deviceName, lat,long))

                    #Get the actual distance from base coordinates and post it if OHItem_Distance provided...
                    CurrentDistance = getDistance(lat, long, geoFence)
                    if variabledistance: postUpdate(variabledistance, str(CurrentDistance))

                    if isInGeofence(CurrentDistance, geoFence) is True:
                        logging.info('Device %sis in Geofence %s' % (deviceName, geoId))
                        postUpdate(variable, 'ON')
                    else:
                        logging.info('Device %sis outside of Geofence %s' % (deviceName, geoId))
                        postUpdate(variable, 'OFF')

                    #Set a default polling rate to the max in the PollingMap (in case the current distance is > the max defined distance in the polling map)
                    PollingMap = geoFence['pollingmap']
                    keyvaluepair = PollingMap.split(",")
                    last = (len(keyvaluepair) - 1)
                    PollingRate = keyvaluepair[last].split("=")[1]

                    #Loop through the polling map and find the interval that corresponds to the current distance...
                    for interval in PollingMap.split(","):
                        dist, rate = interval.split("=")
                        if float(CurrentDistance) <= float(dist):
                            PollingRate = rate
                            break

                    logging.info('Desired polling rate for Geofence %s is %s' % (geoId, PollingRate))
                    #Is the polling rate for this geofence less than any other geofence?...
                    if minDesiredPollRate==-1:
                        minDesiredPollRate=PollingRate
                    else:
                        if PollingRate < minDesiredPollRate: minDesiredPollRate = PollingRate

                    #Now we have the calculated poll time, based on current distance and polling map, calculate the next time we will poll
                    NextPollTime = datetime.now() + timedelta(seconds=int(minDesiredPollRate))

                    #log the current poll rate and next poll time and update the OpenHAB items if they were provided in the configuration file...
                    logging.info('Current Polling Rate: ' + str(minDesiredPollRate))
                    logging.info('Next Poll Time: %s' % (NextPollTime))
                    if variablepollingrate: postUpdate(variablepollingrate, str(minDesiredPollRate))
                    if variablenextpolltime: postUpdate(variablenextpolltime, str(NextPollTime))

                #wait until the next poll time...
                time.sleep(int(minDesiredPollRate))

            except Exception, e:
                restartProgram()

<commit_msg>typos in iLocator.py<commit_after>#!/usr/bin/env python

from pyicloud import PyiCloudService
from math import radians, cos, sin, asin, sqrt
from datetime import datetime
from datetime import timedelta
import ConfigParser
import sys
import os
import time
import requests
import base64
import logging
import argparse

DEFAULT_CONFIG = 'configuration.ini'
DEFAULT_LOGFILE = 'iLocatorLog.log'

logger = logging.getLogger('iLocator')

def configurationManager(configfile):
    gConfig = ConfigParser.ConfigParser()
    try:
        gConfig.read(configfile)
        logging.info('Configuration %s loaded' % (configfile, ))
        if logger.isEnabledFor(logging.DEBUG):
            for section in gConfig.sections():
                logger.debug('Configuration[%s] %s' % (section, configSectionMap(gConfig, section)))

    except Exception, e:
        print('Exception! Please check the log: %s' % (e, ))
        logger.error('\r\nNo configuration avaialble. Please see https://github.com/trusk89/iLocatorBridge for configuration')
        sys.exit(0)

    #get the LocationItems...
    locationItems = parseMultipleSections(gConfig, 'LocationItems')

    # to stay backward compatible
    geoFences = parseMultipleSections(gConfig, 'Geofence')
    for fenceId, fence in geoFences.items():
        if 'homelatitude' in fence or 'homelongitude' in fence:
            for k in ['Latitude', 'Longitude']:
                oldkey = 'home%s' % k.lower()
                fence[k.lower()] = fence.get(oldkey, fence.get(k.lower()))

            print "Warning: found HomeLatitude/HomeLongitude in Section [Geofence%s] " % (fenceId, )
            print " - this is deprecated please use Lattitude/Longitude instead. exp: \n"
            print "[Geofence%s]" % fenceId
            for k, v in fence.items():
                if not k.startswith('home'):
                    print '%s: %s' % (k, v)
            print ""

    return (
        configSectionMap(gConfig, 'iCloud'),
        geoFences,
        configSectionMap(gConfig, 'OpenHab'),
        locationItems,
        )

def parseMultipleSections(gconfig, marker):
    return dict([
                 (section[len(marker):], configSectionMap(gconfig, section))
                 for section in gconfig.sections()
                 if section.startswith(marker)
                 ])

def configSectionMap(gConfig, section):
    dict = {}
    options = gConfig.options(section)
    for option in options:
        try:
            dict[option] = gConfig.get(section, option)
        except:
            print('Exception! Please check the log')
            logger.error('exception on %s!' % option)
            sys.exit(0)
    logger.info('Configuration %s parsed' % (section))
    return dict

def getDeviceCoordinates(gRequester, deviceId, deviceName):
    locationDictionary = None
    errorCount = 0

    StatusItem = gConfigurationOH.get('ohitem_status')

    #Initialize empty location items...
    CoordinatesItem = None
    AccuracyItem = None

    #See if there are any configured LocationItems for this device...
    for locationItemID, locationItem in gConfigurationLocationItems.items():
        #If found, assign them to the initialized vars...
        if 'deviceid'+ str(deviceName).strip() == locationItem.get('device').strip().lower():
            CoordinatesItem = locationItem.get('ohitem_coordinates')
            AccuracyItem = locationItem.get('ohitem_accuracy')
            #then exit the for loop...
            break

    while locationDictionary is None:
        try:
            locationDictionary = (gRequester.devices[deviceId].location())

        except Exception, e:
            errorCount = errorCount + 1
            print('Exception! Please check the log')
            logger.error('Could not get device coordinates. Retrying!: %s' % (e, ))
            #If configured, post error to OpenHAB status item...
            if StatusItem: postUpdate(StatusItem, str(e))

            #If we've passed the RetriesBeforeRestart count, restart the script...
            logger.info(str('Error count: %s' % (errorCount)))
            if int(errorCount) >= int(gConfigurationOH['retriesbeforerestart']):
                restartProgram()

            #If configured, send the next poll time to OpenHAB based on the config RetryInterval value...
            RetryPollTimeItem = gConfigurationOH.get('ohitem_nextpolltime')
            if RetryPollTimeItem:
                RetryPollTime = datetime.now() + timedelta(seconds=int(gConfigurationOH['retryinterval']))
                postUpdate(RetryPollTimeItem, str(RetryPollTime))

            time.sleep(int(gConfigurationOH['retryinterval']))

        pass

    #If configured, post 'Active' to OpenHAB status item...
    if StatusItem: postUpdate(StatusItem, 'Active')
    #If configured, post lat/ling coordinates to OpenHAB...
    if CoordinatesItem: postUpdate(CoordinatesItem, str(locationDictionary['latitude']) + ',' + str(locationDictionary['longitude']))
    #If configured, post accuracy to OpenHAB...
    if AccuracyItem: postUpdate(AccuracyItem, str(convertDistance(locationDictionary['horizontalAccuracy'],gConfigurationOH['distanceunit'])))

    return float(locationDictionary['latitude']), float(locationDictionary['longitude'])

def getDistance (lat, longitude, geoFence):
    return convertDistance(haversine(geoFence['latitude'], lat, geoFence['longitude'], long),gConfigurationOH['distanceunit'])

def convertDistance(meters,desiredUnit):
    if desiredUnit == 'km':
        return meters * .001
    elif desiredUnit == 'ft':
        return meters * 3.28084
    elif desiredUnit == 'mi':
        return meters * 0.000621371
    elif desiredUnit == 'nm':
        return meters * 0.000539957
    else:
        return meters

def isInGeofence(distance, geoFence):
    if float(distance) <= float(geoFence['geofenceradius']):
        return True
    else:
        return False

def haversine(lat1, lat2, lon1, lon2):
    # Thanks for this Aaron D
    # http://stackoverflow.com/questions/15736995/how-can-i-quickly-estimate-the-distance-between-two-latitude-longitude-points
    lon1 = (float(lon1))
    lat1 = (float(lat1))
    lon2 = (float(lon2))
    lat2 = (float(lat2))
    # convert decimal degrees to radians
    lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])
    # haversine formula
    dlon = lon2 - lon1
    dlat = lat2 - lat1
    a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
    c = 2 * asin(sqrt(a))
    m = 6378.137 * c * 1000
    return m

def postUpdate(variable, state):
    global gConfigurationOH

    url = '%s/rest/items/%s/state' % (gConfigurationOH['ohserver'], variable)
    try:
        req = requests.put(url, data=state, headers=basic_header())
        if req.status_code != requests.codes.ok:
            req.raise_for_status()
        logger.info('Update posted to OpenHab: %s = %s' %(variable,state))
    except Exception, e:
        print('Exception! Please check the log. Will continue execution.')
        logger.error('Could not post update to OpenHab: %s' % (e, ))

def restartProgram():
    try:
        logger.info('Restarting script...')
        os.execv(__file__, sys.argv)

    except Exception, e:
        logger.error('Restart program error: %s' % (e, ))

    return True

def basic_header():
    global gConfigurationOH

    auth = base64.encodestring('%s:%s' % (gConfigurationOH['ohusername'], gConfigurationOH['ohpassword'])).replace('\n', '')
    return {"Authorization": "Basic %s" % auth,"Content-type": "text/plain"}

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='iLocatorBridge - Bridge between iCloud location and OpenHAB')
    parser.add_argument('-c', '--config', dest='config', default=DEFAULT_CONFIG,help='Config location (default: %s)' % (DEFAULT_CONFIG, ))
    parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',help='Be more verbose in the output')
    parser.add_argument('--list-devices', dest='listDevices', action='store_true',help='Do not update anything, just prints all existing device ids')

    args = parser.parse_args()
    logging.basicConfig(
        filename=DEFAULT_LOGFILE, level=args.verbose and logging.DEBUG or logging.INFO,
        format='%(asctime)s %(message)s')

    gConfigurationiCloud, gConfigurationGeofence, gConfigurationOH, gConfigurationLocationItems = configurationManager(args.config)
    gRequester = PyiCloudService(gConfigurationiCloud['username'], gConfigurationiCloud['password'])

    if args.listDevices:
        # make it easy to get the device ids for config
        devices = gRequester.devices
        for idx, device in enumerate(devices.keys()):
            print 'device%d: %s  # %s' % (idx, device, devices[device])
    else:
        # get the first configured device for default case
        devices = [d for d in sorted(gConfigurationiCloud.keys()) if d.startswith('deviceid')]
        defaultDevice = devices[0]
        logging.debug('Default Device: %s of %s' % (defaultDevice, devices))

        while 1:
            try:
                minDesiredPollRate = -1
                coordCache = {}
                #get the OH items for current poll rate and next poll time
                variablepollingrate = gConfigurationOH.get('ohitem_pollingrate')
                variablenextpolltime = gConfigurationOH.get('ohitem_nextpolltime')
                #Loop through the geofences...
                for geoId, geoFence in gConfigurationGeofence.items():
                    # look for device overwrite on fence
                    device = geoFence.get('device') or defaultDevice
                    deviceName = device.lower() == 'deviceid' and '' or '%s ' % device[len('deviceid'):]
                    deviceId = gConfigurationiCloud[device.lower()]

                    # look for variable overwrite on fence
                    variable = geoFence.get('ohitem_presence') or gConfigurationOH.get('ohitem')
                    variablepollingmap = geoFence.get('pollingmap')
                    variabledistance = geoFence.get('ohitem_distance')

                    if not variable:
                        msg = "No OHItem is definded not in [Geofence%s] or [OpenHab]" % (geoId)
                        print msg
                        logging.error(msg)
                        continue

                    logging.debug('Device: %s(%s) -> %s' % (deviceName, variable, geoFence))

                    lat, long = coordCache[device] = getDeviceCoordinates(gRequester, deviceId, deviceName)

                    logging.info('Device %slocated @ %s,%s' % (deviceName, lat,long))

                    #Get the actual distance from base coordinates and post it if OHItem_Distance provided...
                    CurrentDistance = getDistance(lat, long, geoFence)
                    if variabledistance: postUpdate(variabledistance, str(CurrentDistance))

                    if isInGeofence(CurrentDistance, geoFence) is True:
                        logging.info('Device %sis in Geofence %s' % (deviceName, geoId))
                        postUpdate(variable, 'ON')
                    else:
                        logging.info('Device %sis outside of Geofence %s' % (deviceName, geoId))
                        postUpdate(variable, 'OFF')

                    #Set a default polling rate to the max in the PollingMap (in case the current distance is > the max defined distance in the polling map)
                    PollingMap = geoFence['pollingmap']
                    keyvaluepair = PollingMap.split(",")
                    last = (len(keyvaluepair) - 1)
                    PollingRate = keyvaluepair[last].split("=")[1]

                    #Loop through the polling map and find the interval that corresponds to the current distance...
                    for interval in PollingMap.split(","):
                        dist, rate = interval.split("=")
                        if float(CurrentDistance) <= float(dist):
                            PollingRate = rate
                            break

                    logging.info('Desired polling rate for Geofence %s is %s' % (geoId, PollingRate))
                    #Is the polling rate for this geofence less than any other geofence?...
                    if minDesiredPollRate==-1:
                        minDesiredPollRate=PollingRate
                    else:
                        if PollingRate < minDesiredPollRate: minDesiredPollRate = PollingRate

                    #Now we have the calculated poll time, based on current distance and polling map, calculate the next time we will poll
                    NextPollTime = datetime.now() + timedelta(seconds=int(minDesiredPollRate))

                    #log the current poll rate and next poll time and update the OpenHAB items if they were provided in the configuration file...
                    logging.info('Current Polling Rate: ' + str(minDesiredPollRate))
                    logging.info('Next Poll Time: %s' % (NextPollTime))
                    if variablepollingrate: postUpdate(variablepollingrate, str(minDesiredPollRate))
                    if variablenextpolltime: postUpdate(variablenextpolltime, str(NextPollTime))

                #wait until the next poll time...
                time.sleep(int(minDesiredPollRate))

            except Exception, e:
                restartProgram()

<|endoftext|>