<commit_before>'''
Created on Oct 20, 2010

@author: Mark V Systems Limited
(c) Copyright 2010 Mark V Systems Limited, All rights reserved.
'''
from tkinter import Toplevel, StringVar, VERTICAL, HORIZONTAL, N, S, E, W, messagebox
try:
    from tkinter.ttk import Frame, Button, Treeview, Scrollbar
except ImportError:
    from ttk import Frame, Button, Treeview, Scrollbar
import os, sys
try:
    import regex as re
except ImportError:
    import re
from arelle.CntlrWinTooltip import ToolTip
from arelle.UrlUtil import isHttpUrl
from arelle.PackageManager import parsePackage

'''
caller checks accepted, if True, caller retrieves url
'''

ARCHIVE = 1
ENTRY_POINTS = 2
DISCLOSURE_SYSTEM = 3

def askArchiveFile(mainWin, filesource):
    filenames = filesource.dir
    if filenames is not None:   # an IO or other error can return None

        if filesource.isTaxonomyPackage:
            dialog = DialogOpenArchive(mainWin,
                                       ENTRY_POINTS,
                                       filesource,
                                       filenames,
                                       _("Select Entry Point"),
                                       _("File"),
                                       showAltViewButton=True)
        else:
            dialog = DialogOpenArchive(mainWin,
                                       ARCHIVE,
                                       filesource,
                                       filenames,
                                       _("Select Archive File"),
                                       _("File"))
        if dialog.accepted:
            return filesource.url
    return None

def selectDisclosureSystem(mainWin, disclosureSystem):
    dialog = DialogOpenArchive(mainWin,
                               DISCLOSURE_SYSTEM,
                               disclosureSystem,
                               disclosureSystem.dir,
                               _("Select Disclosure System"),
                               _("Disclosure System"))
    if dialog and dialog.accepted:
        return disclosureSystem.selection
    return None

class DialogOpenArchive(Toplevel):
    def __init__(self, mainWin, openType, filesource, filenames, title, colHeader, showAltViewButton=False):
        parent = mainWin.parent
        super(DialogOpenArchive, self).__init__(parent)
        self.parent = parent
        self.showAltViewButton = showAltViewButton
        parentGeometry = re.match("(\d+)x(\d+)[+]?([-]?\d+)[+]?([-]?\d+)", parent.geometry())
        dialogX = int(parentGeometry.group(3))
        dialogY = int(parentGeometry.group(4))
        self.accepted = False

        self.transient(self.parent)

        frame = Frame(self)

        treeFrame = Frame(frame, width=500)
        vScrollbar = Scrollbar(treeFrame, orient=VERTICAL)
        hScrollbar = Scrollbar(treeFrame, orient=HORIZONTAL)
        self.treeView = Treeview(treeFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set)
        self.treeView.grid(row=0, column=0, sticky=(N, S, E, W))
        hScrollbar["command"] = self.treeView.xview
        hScrollbar.grid(row=1, column=0, sticky=(E,W))
        vScrollbar["command"] = self.treeView.yview
        vScrollbar.grid(row=0, column=1, sticky=(N,S))
        treeFrame.columnconfigure(0, weight=1)
        treeFrame.rowconfigure(0, weight=1)
        treeFrame.grid(row=0, column=0, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3)
        self.treeView.focus_set()

        mainWin.showStatus(_("loading archive {0}").format(filesource.url))
        self.filesource = filesource
        self.filenames = filenames
        self.selection = filesource.selection
        self.hasToolTip = False
        selectedNode = None

        if openType == ENTRY_POINTS:
            try:
                metadataFiles = filesource.taxonomyPackageMetadataFiles
                ''' take first for now
                if len(metadataFiles) != 1:
                    raise IOError(_("Taxonomy package contained more than one metadata file: {0}.")
                                  .format(', '.join(metadataFiles)))
                '''
                metadataFile = metadataFiles[0]
                metadata = filesource.url + os.sep + metadataFile
                self.metadataFilePrefix = os.sep.join(os.path.split(metadataFile)[:-1])
                if self.metadataFilePrefix:
                    self.metadataFilePrefix += "/"  # zip contents have /, never \ file seps
                self.taxonomyPkgMetaInf = '{}/META-INF/'.format(
                            os.path.splitext(os.path.basename(filesource.url))[0])

                self.taxonomyPackage = parsePackage(mainWin, filesource, metadata,
                                                    os.sep.join(os.path.split(metadata)[:-1]) + os.sep)

                # may be a catalog file with no entry oint names
                if not self.taxonomyPackage["nameToUrls"]:
                    openType = ARCHIVE  # no entry points to show, just archive
                    self.showAltViewButton = False
            except Exception as e:
                self.close()
                err = _("Failed to parse metadata; the underlying error was: {0}").format(e)
                messagebox.showerror(_("Malformed taxonomy package"), err)
                mainWin.addToLog(err)
                return

        mainWin.showStatus(None)

        if openType == DISCLOSURE_SYSTEM:
            y = 3
        else:
            y = 1

        okButton = Button(frame, text=_("OK"), command=self.ok)
        cancelButton = Button(frame, text=_("Cancel"), command=self.close)
        okButton.grid(row=y, column=2, sticky=(S,E,W), pady=3)
        cancelButton.grid(row=y, column=3, sticky=(S,E,W), pady=3, padx=3)

        if self.showAltViewButton:
            self.altViewButton = Button(frame, command=self.showAltView)
            self.altViewButton.grid(row=y, column=0, sticky=(S,W), pady=3, padx=3)

        self.loadTreeView(openType, colHeader, title)

        self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100))
        frame.grid(row=0, column=0, sticky=(N,S,E,W))
        frame.columnconfigure(0, weight=1)
        frame.rowconfigure(0, weight=1)
        window = self.winfo_toplevel()
        window.columnconfigure(0, weight=1)
        window.rowconfigure(0, weight=1)

        self.bind("<Return>", self.ok)
        self.bind("<Escape>", self.close)

        self.toolTipText = StringVar()
        if self.hasToolTip:
            self.treeView.bind("<Motion>", self.motion, '+')
            self.treeView.bind("<Leave>", self.leave, '+')
            self.toolTipText = StringVar()
            self.toolTip = ToolTip(self.treeView,
                                   textvariable=self.toolTipText,
                                   wraplength=640,
                                   follow_mouse=True,
                                   state="disabled")
            self.toolTipRowId = None

        self.protocol("WM_DELETE_WINDOW", self.close)
        self.grab_set()
        self.wait_window(self)

    def loadTreeView(self, openType, title, colHeader):
        self.title(title)
        self.openType = openType
        selectedNode = None

        # clear previous treeview entries
        for previousNode in self.treeView.get_children(""):
            self.treeView.delete(previousNode)

        # set up treeView widget and tabbed pane
        if openType in (ARCHIVE, DISCLOSURE_SYSTEM):
            self.treeView.column("#0", width=500, anchor="w")
            self.treeView.heading("#0", text=colHeader)
            try:
                self.isRss = self.filesource.isRss
                if self.isRss:
                    self.treeView.column("#0", width=350, anchor="w")
                    self.treeView["columns"] = ("descr", "date", "instDoc")
                    self.treeView.column("descr", width=50, anchor="center", stretch=False)
                    self.treeView.heading("descr", text="Form")
                    self.treeView.column("date", width=170, anchor="w", stretch=False)
                    self.treeView.heading("date", text="Pub Date")
                    self.treeView.column("instDoc", width=200, anchor="w", stretch=False)
                    self.treeView.heading("instDoc", text="Instance Document")
            except AttributeError:
                self.isRss = False
                self.treeView["columns"] = tuple()

            loadedPaths = []
            for i, filename in enumerate(self.filenames):
                if isinstance(filename,tuple):
                    if self.isRss:
                        form, date, instDoc = filename[2:5]
                    filename = filename[0] # ignore tooltip
                    self.hasToolTip = True
                if filename.endswith("/"):
                    filename = filename[:-1]
                path = filename.split("/")
                if not self.isRss and len(path) > 1 and path[:-1] in loadedPaths:
                    parent = "file{0}".format(loadedPaths.index(path[:-1]))
                else:
                    parent = ""
                node = self.treeView.insert(parent, "end", "file{0}".format(i), text=path[-1])
                if self.isRss:
                    self.treeView.set(node, "descr", form)
                    self.treeView.set(node, "date", date)
                    self.treeView.set(node, "instDoc", os.path.basename(instDoc))
                if self.selection == filename:
                    selectedNode = node
                loadedPaths.append(path)

        elif openType == ENTRY_POINTS:
            self.treeView.column("#0", width=150, anchor="w")
            self.treeView.heading("#0", text="Name")

            self.treeView["columns"] = ("url",)
            self.treeView.column("url", width=350, anchor="w")
            self.treeView.heading("url", text="URL")

            for name, urls in self.taxonomyPackage["nameToUrls"].items():
                displayUrl = urls[1] # display the canonical URL
                self.treeView.insert("", "end", name, values=[displayUrl], text=name)

            self.hasToolTip = True
        else: # unknown openType
            return None
        if selectedNode:
            self.treeView.see(selectedNode)
            self.treeView.selection_set(selectedNode)

        if self.showAltViewButton:
            self.altViewButton.config(text=_("Show Files") if openType == ENTRY_POINTS else _("Show Entries"))

    def ok(self, event=None):
        selection = self.treeView.selection()
        if len(selection) > 0:
            if hasattr(self, "taxonomyPackage"):
                # load file source remappings
                self.filesource.mappedPaths = self.taxonomyPackage["remappings"]
            filename = None
            if self.openType in (ARCHIVE, DISCLOSURE_SYSTEM):
                filename = self.filenames[int(selection[0][4:])]
                if isinstance(filename,tuple):
                    if self.isRss:
                        filename = filename[4]
                    else:
                        filename = filename[0]
            elif self.openType == ENTRY_POINTS:
                epName = selection[0]
                #index 0 is the remapped Url, as opposed to the canonical one used for display
                filename = self.taxonomyPackage["nameToUrls"][epName][0]
                if not filename.endswith("/"):
                    # check if it's an absolute URL rather than a path into the archive
                    if not isHttpUrl(filename) and self.metadataFilePrefix != self.taxonomyPkgMetaInf:
                        # assume it's a path inside the archive:
                        filename = self.metadataFilePrefix + filename
            if filename is not None and not filename.endswith("/"):
                if hasattr(self, "taxonomyPackage"):
                    # attempt to unmap the filename to original file
                    # will be mapped again in loading, but this allows schemaLocation to be unmapped
                    for prefix, remapping in self.taxonomyPackage["remappings"].items():
                        if isHttpUrl(remapping):
                            remapStart = remapping
                        else:
                            remapStart = self.metadataFilePrefix + remapping
                        if filename.startswith(remapStart):
                            # set unmmapped file
                            filename = prefix + filename[len(remapStart):]
                            break
                self.filesource.select(filename)
                self.accepted = True
                self.close()

    def close(self, event=None):
        self.parent.focus_set()
        self.destroy()

    def showAltView(self, event=None):
        if self.openType == ENTRY_POINTS:
            self.loadTreeView(ARCHIVE, _("Select Entry Point"), _("File"))
        else:
            self.loadTreeView(ENTRY_POINTS, _("Select Archive File"), _("File"))

    def leave(self, *args):
        self.toolTipRowId = None

    def motion(self, *args):
        tvRowId = self.treeView.identify_row(args[0].y)
        if tvRowId != self.toolTipRowId:
            text = None
            if self.openType in (ARCHIVE, DISCLOSURE_SYSTEM):
                self.toolTipRowId = tvRowId
                if tvRowId and len(tvRowId) > 4:
                    try:
                        text = self.filenames[ int(tvRowId[4:]) ]
                        if isinstance(text, tuple):
                            text = text[1].replace("\\n","\n")
                    except (KeyError, ValueError):
                        pass
            elif self.openType == ENTRY_POINTS:
                try:
                    epUrl = self.taxonomyPackage["nameToUrls"][tvRowId][1]
                    text = "{0}\n{1}".format(tvRowId, epUrl)
                except KeyError:
                    pass
            self.setToolTip(text)

    def setToolTip(self, text):
        self.toolTip._hide()
        if text:
            self.toolTipText.set(text)
            self.toolTip.configure(state="normal")
            self.toolTip._schedule()
        else:
            self.toolTipText.set("")
            self.toolTip.configure(state="disabled")

<commit_msg>EBA/EIOPA table linkbase processing with older taxonomy packages, note unresolved Corep 1.6 loading issue work around.<commit_after>'''
Created on Oct 20, 2010

@author: Mark V Systems Limited
(c) Copyright 2010 Mark V Systems Limited, All rights reserved.
'''
from tkinter import Toplevel, StringVar, VERTICAL, HORIZONTAL, N, S, E, W, messagebox
try:
    from tkinter.ttk import Frame, Button, Treeview, Scrollbar
except ImportError:
    from ttk import Frame, Button, Treeview, Scrollbar
import os, sys
try:
    import regex as re
except ImportError:
    import re
from arelle.CntlrWinTooltip import ToolTip
from arelle.UrlUtil import isHttpUrl
from arelle.PackageManager import parsePackage

'''
caller checks accepted, if True, caller retrieves url
'''

ARCHIVE = 1
ENTRY_POINTS = 2
DISCLOSURE_SYSTEM = 3

def askArchiveFile(mainWin, filesource):
    filenames = filesource.dir
    if filenames is not None:   # an IO or other error can return None

        if filesource.isTaxonomyPackage:
            dialog = DialogOpenArchive(mainWin,
                                       ENTRY_POINTS,
                                       filesource,
                                       filenames,
                                       _("Select Entry Point"),
                                       _("File"),
                                       showAltViewButton=True)
        else:
            dialog = DialogOpenArchive(mainWin,
                                       ARCHIVE,
                                       filesource,
                                       filenames,
                                       _("Select Archive File"),
                                       _("File"))
        if dialog.accepted:
            return filesource.url
    return None

def selectDisclosureSystem(mainWin, disclosureSystem):
    dialog = DialogOpenArchive(mainWin,
                               DISCLOSURE_SYSTEM,
                               disclosureSystem,
                               disclosureSystem.dir,
                               _("Select Disclosure System"),
                               _("Disclosure System"))
    if dialog and dialog.accepted:
        return disclosureSystem.selection
    return None

class DialogOpenArchive(Toplevel):
    def __init__(self, mainWin, openType, filesource, filenames, title, colHeader, showAltViewButton=False):
        parent = mainWin.parent
        super(DialogOpenArchive, self).__init__(parent)
        self.parent = parent
        self.showAltViewButton = showAltViewButton
        parentGeometry = re.match("(\d+)x(\d+)[+]?([-]?\d+)[+]?([-]?\d+)", parent.geometry())
        dialogX = int(parentGeometry.group(3))
        dialogY = int(parentGeometry.group(4))
        self.accepted = False

        self.transient(self.parent)

        frame = Frame(self)

        treeFrame = Frame(frame, width=500)
        vScrollbar = Scrollbar(treeFrame, orient=VERTICAL)
        hScrollbar = Scrollbar(treeFrame, orient=HORIZONTAL)
        self.treeView = Treeview(treeFrame, xscrollcommand=hScrollbar.set, yscrollcommand=vScrollbar.set)
        self.treeView.grid(row=0, column=0, sticky=(N, S, E, W))
        hScrollbar["command"] = self.treeView.xview
        hScrollbar.grid(row=1, column=0, sticky=(E,W))
        vScrollbar["command"] = self.treeView.yview
        vScrollbar.grid(row=0, column=1, sticky=(N,S))
        treeFrame.columnconfigure(0, weight=1)
        treeFrame.rowconfigure(0, weight=1)
        treeFrame.grid(row=0, column=0, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3)
        self.treeView.focus_set()

        mainWin.showStatus(_("loading archive {0}").format(filesource.url))
        self.filesource = filesource
        self.filenames = filenames
        self.selection = filesource.selection
        self.hasToolTip = False
        selectedNode = None

        if openType == ENTRY_POINTS:
            try:
                metadataFiles = filesource.taxonomyPackageMetadataFiles
                ''' take first for now
                if len(metadataFiles) != 1:
                    raise IOError(_("Taxonomy package contained more than one metadata file: {0}.")
                                  .format(', '.join(metadataFiles)))
                '''
                metadataFile = metadataFiles[0]
                metadata = filesource.url + os.sep + metadataFile
                self.metadataFilePrefix = os.sep.join(os.path.split(metadataFile)[:-1])
                if self.metadataFilePrefix:
                    self.metadataFilePrefix += "/"  # zip contents have /, never \ file seps
                self.taxonomyPkgMetaInf = '{}/META-INF/'.format(
                            os.path.splitext(os.path.basename(filesource.url))[0])

                self.taxonomyPackage = parsePackage(mainWin, filesource, metadata,
                                                    os.sep.join(os.path.split(metadata)[:-1]) + os.sep)

                # may be a catalog file with no entry oint names
                if not self.taxonomyPackage["nameToUrls"]:
                    openType = ARCHIVE  # no entry points to show, just archive
                    self.showAltViewButton = False
            except Exception as e:
                self.close()
                err = _("Failed to parse metadata; the underlying error was: {0}").format(e)
                messagebox.showerror(_("Malformed taxonomy package"), err)
                mainWin.addToLog(err)
                return

        mainWin.showStatus(None)

        if openType == DISCLOSURE_SYSTEM:
            y = 3
        else:
            y = 1

        okButton = Button(frame, text=_("OK"), command=self.ok)
        cancelButton = Button(frame, text=_("Cancel"), command=self.close)
        okButton.grid(row=y, column=2, sticky=(S,E,W), pady=3)
        cancelButton.grid(row=y, column=3, sticky=(S,E,W), pady=3, padx=3)

        if self.showAltViewButton:
            self.altViewButton = Button(frame, command=self.showAltView)
            self.altViewButton.grid(row=y, column=0, sticky=(S,W), pady=3, padx=3)

        self.loadTreeView(openType, colHeader, title)

        self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100))
        frame.grid(row=0, column=0, sticky=(N,S,E,W))
        frame.columnconfigure(0, weight=1)
        frame.rowconfigure(0, weight=1)
        window = self.winfo_toplevel()
        window.columnconfigure(0, weight=1)
        window.rowconfigure(0, weight=1)

        self.bind("<Return>", self.ok)
        self.bind("<Escape>", self.close)

        self.toolTipText = StringVar()
        if self.hasToolTip:
            self.treeView.bind("<Motion>", self.motion, '+')
            self.treeView.bind("<Leave>", self.leave, '+')
            self.toolTipText = StringVar()
            self.toolTip = ToolTip(self.treeView,
                                   textvariable=self.toolTipText,
                                   wraplength=640,
                                   follow_mouse=True,
                                   state="disabled")
            self.toolTipRowId = None

        self.protocol("WM_DELETE_WINDOW", self.close)
        self.grab_set()
        self.wait_window(self)

    def loadTreeView(self, openType, title, colHeader):
        self.title(title)
        self.openType = openType
        selectedNode = None

        # clear previous treeview entries
        for previousNode in self.treeView.get_children(""):
            self.treeView.delete(previousNode)

        # set up treeView widget and tabbed pane
        if openType in (ARCHIVE, DISCLOSURE_SYSTEM):
            self.treeView.column("#0", width=500, anchor="w")
            self.treeView.heading("#0", text=colHeader)
            try:
                self.isRss = self.filesource.isRss
                if self.isRss:
                    self.treeView.column("#0", width=350, anchor="w")
                    self.treeView["columns"] = ("descr", "date", "instDoc")
                    self.treeView.column("descr", width=50, anchor="center", stretch=False)
                    self.treeView.heading("descr", text="Form")
                    self.treeView.column("date", width=170, anchor="w", stretch=False)
                    self.treeView.heading("date", text="Pub Date")
                    self.treeView.column("instDoc", width=200, anchor="w", stretch=False)
                    self.treeView.heading("instDoc", text="Instance Document")
            except AttributeError:
                self.isRss = False
                self.treeView["columns"] = tuple()

            loadedPaths = []
            for i, filename in enumerate(self.filenames):
                if isinstance(filename,tuple):
                    if self.isRss:
                        form, date, instDoc = filename[2:5]
                    filename = filename[0] # ignore tooltip
                    self.hasToolTip = True
                if filename.endswith("/"):
                    filename = filename[:-1]
                path = filename.split("/")
                if not self.isRss and len(path) > 1 and path[:-1] in loadedPaths:
                    parent = "file{0}".format(loadedPaths.index(path[:-1]))
                else:
                    parent = ""
                node = self.treeView.insert(parent, "end", "file{0}".format(i), text=path[-1])
                if self.isRss:
                    self.treeView.set(node, "descr", form)
                    self.treeView.set(node, "date", date)
                    self.treeView.set(node, "instDoc", os.path.basename(instDoc))
                if self.selection == filename:
                    selectedNode = node
                loadedPaths.append(path)

        elif openType == ENTRY_POINTS:
            self.treeView.column("#0", width=150, anchor="w")
            self.treeView.heading("#0", text="Name")

            self.treeView["columns"] = ("url",)
            self.treeView.column("url", width=350, anchor="w")
            self.treeView.heading("url", text="URL")

            for name, urls in self.taxonomyPackage["nameToUrls"].items():
                displayUrl = urls[1] # display the canonical URL
                self.treeView.insert("", "end", name, values=[displayUrl], text=name)

            self.hasToolTip = True
        else: # unknown openType
            return None
        if selectedNode:
            self.treeView.see(selectedNode)
            self.treeView.selection_set(selectedNode)

        if self.showAltViewButton:
            self.altViewButton.config(text=_("Show Files") if openType == ENTRY_POINTS else _("Show Entries"))

    def ok(self, event=None):
        selection = self.treeView.selection()
        if len(selection) > 0:
            if hasattr(self, "taxonomyPackage"):
                # load file source remappings
                self.filesource.mappedPaths = self.taxonomyPackage["remappings"]
            filename = None
            if self.openType in (ARCHIVE, DISCLOSURE_SYSTEM):
                filename = self.filenames[int(selection[0][4:])]
                if isinstance(filename,tuple):
                    if self.isRss:
                        filename = filename[4]
                    else:
                        filename = filename[0]
            elif self.openType == ENTRY_POINTS:
                epName = selection[0]
                #index 0 is the remapped Url, as opposed to the canonical one used for display
                # Greg Acsone reports [0] does not work for Corep 1.6 pkgs, need [1], old style packages
                filename = self.taxonomyPackage["nameToUrls"][epName][0]
                if not filename.endswith("/"):
                    # check if it's an absolute URL rather than a path into the archive
                    if not isHttpUrl(filename) and self.metadataFilePrefix != self.taxonomyPkgMetaInf:
                        # assume it's a path inside the archive:
                        filename = self.metadataFilePrefix + filename
            if filename is not None and not filename.endswith("/"):
                if hasattr(self, "taxonomyPackage"):
                    # attempt to unmap the filename to original file
                    # will be mapped again in loading, but this allows schemaLocation to be unmapped
                    for prefix, remapping in self.taxonomyPackage["remappings"].items():
                        if isHttpUrl(remapping):
                            remapStart = remapping
                        else:
                            remapStart = self.metadataFilePrefix + remapping
                        if filename.startswith(remapStart):
                            # set unmmapped file
                            filename = prefix + filename[len(remapStart):]
                            break
                self.filesource.select(filename)
                self.accepted = True
                self.close()

    def close(self, event=None):
        self.parent.focus_set()
        self.destroy()

    def showAltView(self, event=None):
        if self.openType == ENTRY_POINTS:
            self.loadTreeView(ARCHIVE, _("Select Entry Point"), _("File"))
        else:
            self.loadTreeView(ENTRY_POINTS, _("Select Archive File"), _("File"))

    def leave(self, *args):
        self.toolTipRowId = None

    def motion(self, *args):
        tvRowId = self.treeView.identify_row(args[0].y)
        if tvRowId != self.toolTipRowId:
            text = None
            if self.openType in (ARCHIVE, DISCLOSURE_SYSTEM):
                self.toolTipRowId = tvRowId
                if tvRowId and len(tvRowId) > 4:
                    try:
                        text = self.filenames[ int(tvRowId[4:]) ]
                        if isinstance(text, tuple):
                            text = text[1].replace("\\n","\n")
                    except (KeyError, ValueError):
                        pass
            elif self.openType == ENTRY_POINTS:
                try:
                    epUrl = self.taxonomyPackage["nameToUrls"][tvRowId][1]
                    text = "{0}\n{1}".format(tvRowId, epUrl)
                except KeyError:
                    pass
            self.setToolTip(text)

    def setToolTip(self, text):
        self.toolTip._hide()
        if text:
            self.toolTipText.set(text)
            self.toolTip.configure(state="normal")
            self.toolTip._schedule()
        else:
            self.toolTipText.set("")
            self.toolTip.configure(state="disabled")

<|endoftext|>


<commit_before>"""
 mbed CMSIS-DAP debugger
 Copyright (c) 2006-2015 ARM Limited

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
"""

import sys, os
import logging, array

from time import sleep
from board import Board
from pyOCD.pyDAPAccess import DAPAccess

class BoardInfo(object):
    def __init__(self, name, target, binary):
        self.name = name
        self.target = target
        self.binary = binary

BOARD_ID_TO_INFO = {
                        #           Board Name              Target              Test Binary
                "0200": BoardInfo(  "FRDM-KL25Z",           "kl25z",            "l1_kl25z.bin"          ),
                "0203": BoardInfo(  "TWR-KL28Z72M",         "kl28z",            "l1_kl28z.bin",         ),
                "0205": BoardInfo(  "FRDM-KL28Z",           "kl28z",            "l1_kl28z.bin",         ),
                "0206": BoardInfo(  "TWR-KE18F160M",        "ke18f16",          "l1_ke18f16.bin",       ),
                "0210": BoardInfo(  "FRDM-KL05Z",           "kl05z",            "l1_kl05z.bin",         ),
                "0213": BoardInfo(  "FRDM-KE15Z",           "ke15z7",           "l1_ke15z7.bin",        ),
                "0220": BoardInfo(  "FRDM-KL46z",           "kl46z",            "l1_kl46z.bin",         ),
                "0230": BoardInfo(  "FRDM-K20D50M",         "k20d50m",          "l1_k20d50m.bin",       ),
                "0231": BoardInfo(  "FRDM-K22F",            "k22f",             "l1_k22f.bin",          ),
                "0240": BoardInfo(  "FRDM-K64F",            "k64f",             "l1_k64f.bin",          ),
                "0250": BoardInfo(  "FRDM-KL02Z",           "kl02z",            "l1_kl02z.bin",         ),
                "0260": BoardInfo(  "FRDM-KL26Z",           "kl26z",            "l1_kl26z.bin",         ),
                "0261": BoardInfo(  "FRDM-KL27Z",           "kl27z",            "l1_kl26z.bin",         ),
                "0290": BoardInfo(  "FRDM-KW40Z",           "kw40z4",           "l1_kw40z.bin",         ),
                "0298": BoardInfo(  "FRDM-KV10Z",           "kv10z7",           "l1_kl25z.bin"          ),
                "0311": BoardInfo(  "FRDM-K66F",            "k66f18",           "l1_k66f.bin",          ),
#                 "0320": BoardInfo(  "FRDM-KW01Z9032",       "kw01z4",           "l1_kl25z.bin"          ),
                "0321": BoardInfo(  "USB-KW01Z",            "kw01z4",           "l1_kl25z.bin"          ),
                "0324": BoardInfo(  "USB-KW40Z",            "kw40z4",           "l1_kl25z.bin"          ),
                "0340": BoardInfo(  "TWR-K80F150M",         "k82f25615",        "l1_k64f.bin",          ),
#                 "0300": BoardInfo(  "TWR-KV11Z75M",         "kv11z7",           "l1_kl25z.bin"          ),
                "0341": BoardInfo(  "FRDM-KV31Z",           "kv31f12",          "l1_kl25z.bin"          ),
                "0400": BoardInfo(  "maxwsnenv",            "maxwsnenv",        "l1_maxwsnenv.bin",     ),
                "0405": BoardInfo(  "max32600mbed",         "max32600mbed",     "l1_max32600mbed.bin",  ),
                "0824": BoardInfo(  "LPCXpresso824-MAX",    "lpc824",           "l1_lpc824.bin",        ),
                "1010": BoardInfo(  "mbed NXP LPC1768",     "lpc1768",          "l1_lpc1768.bin",       ),
                "1017": BoardInfo(  "mbed HRM1017",         "nrf51",            "l1_nrf51.bin",         ),
                "1018": BoardInfo(  "Switch-Science-mbed-LPC824", "lpc824",     "l1_lpc824.bin",        ),
                "1019": BoardInfo(  "mbed TY51822r3",       "nrf51",            "l1_nrf51.bin",         ),
                "1040": BoardInfo(  "mbed NXP LPC11U24",    "lpc11u24",         "l1_lpc11u24.bin",      ),
                "1050": BoardInfo(  "NXP LPC800-MAX",       "lpc800",           "l1_lpc800.bin",        ),
                "1060": BoardInfo(  "EA-LPC4088",           "lpc4088qsb",       "l1_lpc4088qsb.bin",    ),
                "1062": BoardInfo(  "EA-LPC4088-Display-Module", "lpc4088dm",   "l1_lpc4088dm.bin",     ),
                "1070": BoardInfo(  "nRF51822-mKIT",        "nrf51",            "l1_nrf51.bin",         ),
                "1080": BoardInfo(  "DT01 + MB2001",        "stm32f103rc",      "l1_stm32f103rc.bin",   ),
                "1090": BoardInfo(  "DT01 + MB00xx",        "stm32f051",        "l1_stm32f051.bin",     ),
                "1090": BoardInfo(  "RedBearLab-nRF51822",  "nrf51",            "l1_nrf51.bin",         ),
                "1095": BoardInfo(  "RedBearLab-BLE-Nano",  "nrf51",            "l1_nrf51.bin",         ),
                "1100": BoardInfo(  "nRF51-DK",             "nrf51",            "l1_nrf51-dk.bin",      ),
                "1101": BoardInfo(  "nRF52-DK",             "nrf52",            "l1_nrf52-dk.bin",      ),
                "1114": BoardInfo(  "mbed LPC1114FN28",     "lpc11xx_32",       "l1_mbed_LPC1114FN28.bin",),
                "1120": BoardInfo(  "nRF51-Dongle",         "nrf51",            "l1_nrf51.bin",         ),
                "1234": BoardInfo(  "u-blox-C027",          "lpc1768",          "l1_lpc1768.bin",       ),
                "1600": BoardInfo(  "Bambino 210",          "lpc4330",          "l1_lpc4330.bin",       ),
                "1605": BoardInfo(  "Bambino 210E",         "lpc4330",          "l1_lpc4330.bin",       ),
                "2201": BoardInfo(  "WIZwik_W7500",         "w7500",            "l1_w7500mbed.bin",     ),
                "9004": BoardInfo(  "Arch Pro",             "lpc1768",          "l1_lpc1768.bin",       ),
                "9009": BoardInfo(  "Arch BLE",             "nrf51",            "l1_nrf51.bin",         ),
                "9012": BoardInfo(  "Seeed Tiny BLE",       "nrf51",            "l1_nrf51.bin",         ),
                "9900": BoardInfo(  "Microbit",             "nrf51",            "l1_microbit.bin",      ),
              }

mbed_vid = 0x0d28
mbed_pid = 0x0204

class MbedBoard(Board):
    """
    This class inherits from Board and is specific to mbed boards.
    Particularly, this class allows you to dynamically determine
    the type of all boards connected based on the id board
    """
    def __init__(self, link, target=None, frequency=1000000):
        """
        Init the board
        """
        self.native_target = None
        self.test_binary = None
        unique_id = link.get_unique_id()
        board_id = unique_id[0:4]
        self.name = "Unknown Board"
        if board_id in BOARD_ID_TO_INFO:
            board_info = BOARD_ID_TO_INFO[board_id]
            self.name = board_info.name
            self.native_target = board_info.target
            self.test_binary = board_info.binary

        # Unless overridden use the native target
        if target is None:
            target = self.native_target

        if target is None:
            logging.warning("Unsupported board found %s", board_id)
            target = "cortex_m"

        super(MbedBoard, self).__init__(target, target, link, frequency)
        self.unique_id = unique_id
        self.target_type = target

    def getUniqueID(self):
        """
        Return the unique id of the board
        """
        return self.unique_id

    def getTargetType(self):
        """
        Return the type of the board
        """
        return self.target_type

    def getTestBinary(self):
        """
        Return name of test binary file
        """
        return self.test_binary

    def getBoardName(self):
        """
        Return board name
        """
        return self.name

    def getInfo(self):
        """
        Return info on the board
        """
        return self.name + " [" + self.target_type + "]"

    @staticmethod
    def listConnectedBoards(dap_class=DAPAccess):
        """
        List the connected board info
        """
        all_mbeds = MbedBoard.getAllConnectedBoards(dap_class, close=True,
                                                    blocking=False)
        index = 0
        if len(all_mbeds) > 0:
            for mbed in all_mbeds:
                print("%d => %s boardId => %s" % (index, mbed.getInfo().encode('ascii', 'ignore'), mbed.unique_id))
                index += 1
        else:
            print("No available boards are connected")

    @staticmethod
    def getAllConnectedBoards(dap_class=DAPAccess, close=False, blocking=True,
                              target_override=None, frequency=1000000):
        """
        Return an array of all mbed boards connected
        """

        mbed_list = []
        while True:

            connected_daps = dap_class.get_connected_devices()
            for dap_access in connected_daps:
                new_mbed = MbedBoard(dap_access, target_override, frequency)
                mbed_list.append(new_mbed)

            #TODO - handle exception on open
            if not close:
                for dap_access in connected_daps:
                    dap_access.open()

            if not blocking:
                break
            elif len(mbed_list) > 0:
                break
            else:
                sleep(0.01)
            assert len(mbed_list) == 0

        return mbed_list

    @staticmethod
    def chooseBoard(dap_class=DAPAccess, blocking=True, return_first=False,
                    board_id=None, target_override=None, frequency=1000000,
                    init_board=True):
        """
        Allow you to select a board among all boards connected
        """
        all_mbeds = MbedBoard.getAllConnectedBoards(dap_class, False, blocking,
                                                    target_override, frequency)

        # If a board ID is specified close all other boards
        if board_id != None:
            new_mbed_list = []
            for mbed in all_mbeds:
                if mbed.unique_id == (board_id):
                    new_mbed_list.append(mbed)
                else:
                    mbed.link.close()
            assert len(new_mbed_list) <= 1
            all_mbeds = new_mbed_list

        # Return if no boards are connected
        if all_mbeds == None or len(all_mbeds) <= 0:
            if board_id is None:
                print("No connected boards")
            else:
                print("Board %s is not connected" % board_id)
            return None # No boards to close so it is safe to return

        # Select first board and close others if True
        if return_first:
            for i in range(1, len(all_mbeds)):
                all_mbeds[i].link.close()
            all_mbeds = all_mbeds[0:1]

        # Ask use to select boards if there is more than 1 left
        if len(all_mbeds) > 1:
            index = 0
            print "id => usbinfo | boardname"
            for mbed in all_mbeds:
                print "%d => %s" % (index, mbed.getInfo().encode('ascii', 'ignore'))
                index += 1
            while True:
                print "input id num to choice your board want to connect"
                line = sys.stdin.readline()
                valid = False
                try:
                    ch = int(line)
                    valid = 0 <= ch < len(all_mbeds)
                except ValueError:
                    pass
                if not valid:
                    logging.info("BAD CHOICE: %s", line)
                    index = 0
                    for mbed in all_mbeds:
                        print "%d => %s" % (index, mbed.getInfo())
                        index += 1
                else:
                    break
            # close all others mbed connected
            for mbed in all_mbeds:
                if mbed != all_mbeds[ch]:
                    mbed.link.close()
            all_mbeds = all_mbeds[ch:ch + 1]

        assert len(all_mbeds) == 1
        mbed = all_mbeds[0]
        if init_board:
            try:
                mbed.init()
            except:
                mbed.link.close()
                raise
        return mbed
<commit_msg>Added Hexiwear, corrected TWR-KE18F name.<commit_after>"""
 mbed CMSIS-DAP debugger
 Copyright (c) 2006-2015 ARM Limited

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
"""

import sys, os
import logging, array

from time import sleep
from board import Board
from pyOCD.pyDAPAccess import DAPAccess

class BoardInfo(object):
    def __init__(self, name, target, binary):
        self.name = name
        self.target = target
        self.binary = binary

BOARD_ID_TO_INFO = {
                        #           Board Name              Target              Test Binary
                "0200": BoardInfo(  "FRDM-KL25Z",           "kl25z",            "l1_kl25z.bin"          ),
                "0203": BoardInfo(  "TWR-KL28Z72M",         "kl28z",            "l1_kl28z.bin",         ),
                "0205": BoardInfo(  "FRDM-KL28Z",           "kl28z",            "l1_kl28z.bin",         ),
                "0206": BoardInfo(  "TWR-KE18F",            "ke18f16",          "l1_ke18f16.bin",       ),
                "0210": BoardInfo(  "FRDM-KL05Z",           "kl05z",            "l1_kl05z.bin",         ),
                "0213": BoardInfo(  "FRDM-KE15Z",           "ke15z7",           "l1_ke15z7.bin",        ),
                "0214": BoardInfo(  "Hexiwear",             "k64f",             "l1_k64f.bin",          ),
                "0220": BoardInfo(  "FRDM-KL46z",           "kl46z",            "l1_kl46z.bin",         ),
                "0230": BoardInfo(  "FRDM-K20D50M",         "k20d50m",          "l1_k20d50m.bin",       ),
                "0231": BoardInfo(  "FRDM-K22F",            "k22f",             "l1_k22f.bin",          ),
                "0240": BoardInfo(  "FRDM-K64F",            "k64f",             "l1_k64f.bin",          ),
                "0250": BoardInfo(  "FRDM-KL02Z",           "kl02z",            "l1_kl02z.bin",         ),
                "0260": BoardInfo(  "FRDM-KL26Z",           "kl26z",            "l1_kl26z.bin",         ),
                "0261": BoardInfo(  "FRDM-KL27Z",           "kl27z",            "l1_kl26z.bin",         ),
                "0290": BoardInfo(  "FRDM-KW40Z",           "kw40z4",           "l1_kw40z.bin",         ),
                "0298": BoardInfo(  "FRDM-KV10Z",           "kv10z7",           "l1_kl25z.bin"          ),
                "0311": BoardInfo(  "FRDM-K66F",            "k66f18",           "l1_k66f.bin",          ),
#                 "0320": BoardInfo(  "FRDM-KW01Z9032",       "kw01z4",           "l1_kl25z.bin"          ),
                "0321": BoardInfo(  "USB-KW01Z",            "kw01z4",           "l1_kl25z.bin"          ),
                "0324": BoardInfo(  "USB-KW40Z",            "kw40z4",           "l1_kl25z.bin"          ),
                "0340": BoardInfo(  "TWR-K80F150M",         "k82f25615",        "l1_k64f.bin",          ),
#                 "0300": BoardInfo(  "TWR-KV11Z75M",         "kv11z7",           "l1_kl25z.bin"          ),
                "0341": BoardInfo(  "FRDM-KV31Z",           "kv31f12",          "l1_kl25z.bin"          ),
                "0400": BoardInfo(  "maxwsnenv",            "maxwsnenv",        "l1_maxwsnenv.bin",     ),
                "0405": BoardInfo(  "max32600mbed",         "max32600mbed",     "l1_max32600mbed.bin",  ),
                "0824": BoardInfo(  "LPCXpresso824-MAX",    "lpc824",           "l1_lpc824.bin",        ),
                "1010": BoardInfo(  "mbed NXP LPC1768",     "lpc1768",          "l1_lpc1768.bin",       ),
                "1017": BoardInfo(  "mbed HRM1017",         "nrf51",            "l1_nrf51.bin",         ),
                "1018": BoardInfo(  "Switch-Science-mbed-LPC824", "lpc824",     "l1_lpc824.bin",        ),
                "1019": BoardInfo(  "mbed TY51822r3",       "nrf51",            "l1_nrf51.bin",         ),
                "1040": BoardInfo(  "mbed NXP LPC11U24",    "lpc11u24",         "l1_lpc11u24.bin",      ),
                "1050": BoardInfo(  "NXP LPC800-MAX",       "lpc800",           "l1_lpc800.bin",        ),
                "1060": BoardInfo(  "EA-LPC4088",           "lpc4088qsb",       "l1_lpc4088qsb.bin",    ),
                "1062": BoardInfo(  "EA-LPC4088-Display-Module", "lpc4088dm",   "l1_lpc4088dm.bin",     ),
                "1070": BoardInfo(  "nRF51822-mKIT",        "nrf51",            "l1_nrf51.bin",         ),
                "1080": BoardInfo(  "DT01 + MB2001",        "stm32f103rc",      "l1_stm32f103rc.bin",   ),
                "1090": BoardInfo(  "DT01 + MB00xx",        "stm32f051",        "l1_stm32f051.bin",     ),
                "1090": BoardInfo(  "RedBearLab-nRF51822",  "nrf51",            "l1_nrf51.bin",         ),
                "1095": BoardInfo(  "RedBearLab-BLE-Nano",  "nrf51",            "l1_nrf51.bin",         ),
                "1100": BoardInfo(  "nRF51-DK",             "nrf51",            "l1_nrf51-dk.bin",      ),
                "1101": BoardInfo(  "nRF52-DK",             "nrf52",            "l1_nrf52-dk.bin",      ),
                "1114": BoardInfo(  "mbed LPC1114FN28",     "lpc11xx_32",       "l1_mbed_LPC1114FN28.bin",),
                "1120": BoardInfo(  "nRF51-Dongle",         "nrf51",            "l1_nrf51.bin",         ),
                "1234": BoardInfo(  "u-blox-C027",          "lpc1768",          "l1_lpc1768.bin",       ),
                "1600": BoardInfo(  "Bambino 210",          "lpc4330",          "l1_lpc4330.bin",       ),
                "1605": BoardInfo(  "Bambino 210E",         "lpc4330",          "l1_lpc4330.bin",       ),
                "2201": BoardInfo(  "WIZwik_W7500",         "w7500",            "l1_w7500mbed.bin",     ),
                "9004": BoardInfo(  "Arch Pro",             "lpc1768",          "l1_lpc1768.bin",       ),
                "9009": BoardInfo(  "Arch BLE",             "nrf51",            "l1_nrf51.bin",         ),
                "9012": BoardInfo(  "Seeed Tiny BLE",       "nrf51",            "l1_nrf51.bin",         ),
                "9900": BoardInfo(  "Microbit",             "nrf51",            "l1_microbit.bin",      ),
              }

mbed_vid = 0x0d28
mbed_pid = 0x0204

class MbedBoard(Board):
    """
    This class inherits from Board and is specific to mbed boards.
    Particularly, this class allows you to dynamically determine
    the type of all boards connected based on the id board
    """
    def __init__(self, link, target=None, frequency=1000000):
        """
        Init the board
        """
        self.native_target = None
        self.test_binary = None
        unique_id = link.get_unique_id()
        board_id = unique_id[0:4]
        self.name = "Unknown Board"
        if board_id in BOARD_ID_TO_INFO:
            board_info = BOARD_ID_TO_INFO[board_id]
            self.name = board_info.name
            self.native_target = board_info.target
            self.test_binary = board_info.binary

        # Unless overridden use the native target
        if target is None:
            target = self.native_target

        if target is None:
            logging.warning("Unsupported board found %s", board_id)
            target = "cortex_m"

        super(MbedBoard, self).__init__(target, target, link, frequency)
        self.unique_id = unique_id
        self.target_type = target

    def getUniqueID(self):
        """
        Return the unique id of the board
        """
        return self.unique_id

    def getTargetType(self):
        """
        Return the type of the board
        """
        return self.target_type

    def getTestBinary(self):
        """
        Return name of test binary file
        """
        return self.test_binary

    def getBoardName(self):
        """
        Return board name
        """
        return self.name

    def getInfo(self):
        """
        Return info on the board
        """
        return self.name + " [" + self.target_type + "]"

    @staticmethod
    def listConnectedBoards(dap_class=DAPAccess):
        """
        List the connected board info
        """
        all_mbeds = MbedBoard.getAllConnectedBoards(dap_class, close=True,
                                                    blocking=False)
        index = 0
        if len(all_mbeds) > 0:
            for mbed in all_mbeds:
                print("%d => %s boardId => %s" % (index, mbed.getInfo().encode('ascii', 'ignore'), mbed.unique_id))
                index += 1
        else:
            print("No available boards are connected")

    @staticmethod
    def getAllConnectedBoards(dap_class=DAPAccess, close=False, blocking=True,
                              target_override=None, frequency=1000000):
        """
        Return an array of all mbed boards connected
        """

        mbed_list = []
        while True:

            connected_daps = dap_class.get_connected_devices()
            for dap_access in connected_daps:
                new_mbed = MbedBoard(dap_access, target_override, frequency)
                mbed_list.append(new_mbed)

            #TODO - handle exception on open
            if not close:
                for dap_access in connected_daps:
                    dap_access.open()

            if not blocking:
                break
            elif len(mbed_list) > 0:
                break
            else:
                sleep(0.01)
            assert len(mbed_list) == 0

        return mbed_list

    @staticmethod
    def chooseBoard(dap_class=DAPAccess, blocking=True, return_first=False,
                    board_id=None, target_override=None, frequency=1000000,
                    init_board=True):
        """
        Allow you to select a board among all boards connected
        """
        all_mbeds = MbedBoard.getAllConnectedBoards(dap_class, False, blocking,
                                                    target_override, frequency)

        # If a board ID is specified close all other boards
        if board_id != None:
            new_mbed_list = []
            for mbed in all_mbeds:
                if mbed.unique_id == (board_id):
                    new_mbed_list.append(mbed)
                else:
                    mbed.link.close()
            assert len(new_mbed_list) <= 1
            all_mbeds = new_mbed_list

        # Return if no boards are connected
        if all_mbeds == None or len(all_mbeds) <= 0:
            if board_id is None:
                print("No connected boards")
            else:
                print("Board %s is not connected" % board_id)
            return None # No boards to close so it is safe to return

        # Select first board and close others if True
        if return_first:
            for i in range(1, len(all_mbeds)):
                all_mbeds[i].link.close()
            all_mbeds = all_mbeds[0:1]

        # Ask use to select boards if there is more than 1 left
        if len(all_mbeds) > 1:
            index = 0
            print "id => usbinfo | boardname"
            for mbed in all_mbeds:
                print "%d => %s" % (index, mbed.getInfo().encode('ascii', 'ignore'))
                index += 1
            while True:
                print "input id num to choice your board want to connect"
                line = sys.stdin.readline()
                valid = False
                try:
                    ch = int(line)
                    valid = 0 <= ch < len(all_mbeds)
                except ValueError:
                    pass
                if not valid:
                    logging.info("BAD CHOICE: %s", line)
                    index = 0
                    for mbed in all_mbeds:
                        print "%d => %s" % (index, mbed.getInfo())
                        index += 1
                else:
                    break
            # close all others mbed connected
            for mbed in all_mbeds:
                if mbed != all_mbeds[ch]:
                    mbed.link.close()
            all_mbeds = all_mbeds[ch:ch + 1]

        assert len(all_mbeds) == 1
        mbed = all_mbeds[0]
        if init_board:
            try:
                mbed.init()
            except:
                mbed.link.close()
                raise
        return mbed
<|endoftext|>


<commit_before># Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import absolute_import, division, print_function

import datetime
import warnings

from cryptography import utils, x509
from cryptography.exceptions import UnsupportedAlgorithm
from cryptography.hazmat.primitives import hashes

def _obj2txt(backend, obj):
    # Set to 80 on the recommendation of
    # https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values
    buf_len = 80
    buf = backend._ffi.new("char[]", buf_len)
    res = backend._lib.OBJ_obj2txt(buf, buf_len, obj, 1)
    assert res > 0
    return backend._ffi.buffer(buf, res)[:].decode()

def _build_x509_name(backend, x509_name):
    count = backend._lib.X509_NAME_entry_count(x509_name)
    attributes = []
    for x in range(count):
        entry = backend._lib.X509_NAME_get_entry(x509_name, x)
        obj = backend._lib.X509_NAME_ENTRY_get_object(entry)
        assert obj != backend._ffi.NULL
        data = backend._lib.X509_NAME_ENTRY_get_data(entry)
        assert data != backend._ffi.NULL
        buf = backend._ffi.new("unsigned char **")
        res = backend._lib.ASN1_STRING_to_UTF8(buf, data)
        assert res >= 0
        assert buf[0] != backend._ffi.NULL
        buf = backend._ffi.gc(
            buf, lambda buf: backend._lib.OPENSSL_free(buf[0])
        )
        value = backend._ffi.buffer(buf[0], res)[:].decode('utf8')
        oid = _obj2txt(backend, obj)
        attributes.append(
            x509.NameAttribute(
                x509.ObjectIdentifier(oid), value
            )
        )

    return x509.Name(attributes)

@utils.register_interface(x509.Certificate)
class _Certificate(object):
    def __init__(self, backend, x509):
        self._backend = backend
        self._x509 = x509

    def fingerprint(self, algorithm):
        h = hashes.Hash(algorithm, self._backend)
        bio = self._backend._create_mem_bio()
        res = self._backend._lib.i2d_X509_bio(
            bio, self._x509
        )
        assert res == 1
        der = self._backend._read_mem_bio(bio)
        h.update(der)
        return h.finalize()

    @property
    def version(self):
        version = self._backend._lib.X509_get_version(self._x509)
        if version == 0:
            return x509.Version.v1
        elif version == 2:
            return x509.Version.v3
        else:
            raise x509.InvalidVersion(
                "{0} is not a valid X509 version".format(version), version
            )

    @property
    def serial(self):
        asn1_int = self._backend._lib.X509_get_serialNumber(self._x509)
        assert asn1_int != self._backend._ffi.NULL
        bn = self._backend._lib.ASN1_INTEGER_to_BN(
            asn1_int, self._backend._ffi.NULL
        )
        assert bn != self._backend._ffi.NULL
        bn = self._backend._ffi.gc(bn, self._backend._lib.BN_free)
        return self._backend._bn_to_int(bn)

    def public_key(self):
        pkey = self._backend._lib.X509_get_pubkey(self._x509)
        assert pkey != self._backend._ffi.NULL
        pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free)

        return self._backend._evp_pkey_to_public_key(pkey)

    @property
    def not_valid_before(self):
        asn1_time = self._backend._lib.X509_get_notBefore(self._x509)
        return self._parse_asn1_time(asn1_time)

    @property
    def not_valid_after(self):
        asn1_time = self._backend._lib.X509_get_notAfter(self._x509)
        return self._parse_asn1_time(asn1_time)

    def _parse_asn1_time(self, asn1_time):
        assert asn1_time != self._backend._ffi.NULL
        generalized_time = self._backend._lib.ASN1_TIME_to_generalizedtime(
            asn1_time, self._backend._ffi.NULL
        )
        assert generalized_time != self._backend._ffi.NULL
        generalized_time = self._backend._ffi.gc(
            generalized_time, self._backend._lib.ASN1_GENERALIZEDTIME_free
        )
        time = self._backend._ffi.string(
            self._backend._lib.ASN1_STRING_data(
                self._backend._ffi.cast("ASN1_STRING *", generalized_time)
            )
        ).decode("ascii")
        return datetime.datetime.strptime(time, "%Y%m%d%H%M%SZ")

    @property
    def issuer(self):
        issuer = self._backend._lib.X509_get_issuer_name(self._x509)
        assert issuer != self._backend._ffi.NULL
        return _build_x509_name(self._backend, issuer)

    @property
    def subject(self):
        subject = self._backend._lib.X509_get_subject_name(self._x509)
        assert subject != self._backend._ffi.NULL
        return _build_x509_name(self._backend, subject)

    @property
    def signature_hash_algorithm(self):
        oid = _obj2txt(self._backend, self._x509.sig_alg.algorithm)
        try:
            return x509._SIG_OIDS_TO_HASH[oid]
        except KeyError:
            raise UnsupportedAlgorithm(
                "Signature algorithm OID:{0} not recognized".format(oid)
            )

    @property
    def extensions(self):
        extensions = []
        seen_oids = set()
        extcount = self._backend._lib.X509_get_ext_count(self._x509)
        for i in range(0, extcount):
            ext = self._backend._lib.X509_get_ext(self._x509, i)
            assert ext != self._backend._ffi.NULL
            crit = self._backend._lib.X509_EXTENSION_get_critical(ext)
            critical = crit == 1
            oid = x509.ObjectIdentifier(_obj2txt(self._backend, ext.object))
            if oid in seen_oids:
                raise x509.DuplicateExtension(
                    "Duplicate {0} extension found".format(oid), oid
                )
            elif oid == x509.OID_BASIC_CONSTRAINTS:
                value = self._build_basic_constraints(ext)
            elif oid == x509.OID_KEY_USAGE and critical:
                # TODO: remove this obviously.
                warnings.warn(
                    "Extension support is not fully implemented. A key usage "
                    "extension with the critical flag was seen and IGNORED."
                )
                seen_oids.add(oid)
                continue
            elif critical:
                raise x509.UnsupportedExtension(
                    "{0} is not currently supported".format(oid), oid
                )
            else:
                # Unsupported non-critical extension, silently skipping for now
                seen_oids.add(oid)
                continue

            seen_oids.add(oid)
            extensions.append(x509.Extension(oid, critical, value))

        return x509.Extensions(extensions)

    def _build_basic_constraints(self, ext):
        bc_st = self._backend._lib.X509V3_EXT_d2i(ext)
        assert bc_st != self._backend._ffi.NULL
        basic_constraints = self._backend._ffi.cast(
            "BASIC_CONSTRAINTS *", bc_st
        )
        basic_constraints = self._backend._ffi.gc(
            basic_constraints, self._backend._lib.BASIC_CONSTRAINTS_free
        )
        # The byte representation of an ASN.1 boolean true is \xff. OpenSSL
        # chooses to just map this to its ordinal value, so true is 255 and
        # false is 0.
        ca = basic_constraints.ca == 255
        if basic_constraints.pathlen == self._backend._ffi.NULL:
            path_length = None
        else:
            bn = self._backend._lib.ASN1_INTEGER_to_BN(
                basic_constraints.pathlen, self._backend._ffi.NULL
            )
            assert bn != self._backend._ffi.NULL
            bn = self._backend._ffi.gc(bn, self._backend._lib.BN_free)
            path_length = self._backend._bn_to_int(bn)

        return x509.BasicConstraints(ca, path_length)
<commit_msg>Rename lambda argument<commit_after># Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import absolute_import, division, print_function

import datetime
import warnings

from cryptography import utils, x509
from cryptography.exceptions import UnsupportedAlgorithm
from cryptography.hazmat.primitives import hashes

def _obj2txt(backend, obj):
    # Set to 80 on the recommendation of
    # https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values
    buf_len = 80
    buf = backend._ffi.new("char[]", buf_len)
    res = backend._lib.OBJ_obj2txt(buf, buf_len, obj, 1)
    assert res > 0
    return backend._ffi.buffer(buf, res)[:].decode()

def _build_x509_name(backend, x509_name):
    count = backend._lib.X509_NAME_entry_count(x509_name)
    attributes = []
    for x in range(count):
        entry = backend._lib.X509_NAME_get_entry(x509_name, x)
        obj = backend._lib.X509_NAME_ENTRY_get_object(entry)
        assert obj != backend._ffi.NULL
        data = backend._lib.X509_NAME_ENTRY_get_data(entry)
        assert data != backend._ffi.NULL
        buf = backend._ffi.new("unsigned char **")
        res = backend._lib.ASN1_STRING_to_UTF8(buf, data)
        assert res >= 0
        assert buf[0] != backend._ffi.NULL
        buf = backend._ffi.gc(
            buf, lambda buffer: backend._lib.OPENSSL_free(buffer[0])
        )
        value = backend._ffi.buffer(buf[0], res)[:].decode('utf8')
        oid = _obj2txt(backend, obj)
        attributes.append(
            x509.NameAttribute(
                x509.ObjectIdentifier(oid), value
            )
        )

    return x509.Name(attributes)

@utils.register_interface(x509.Certificate)
class _Certificate(object):
    def __init__(self, backend, x509):
        self._backend = backend
        self._x509 = x509

    def fingerprint(self, algorithm):
        h = hashes.Hash(algorithm, self._backend)
        bio = self._backend._create_mem_bio()
        res = self._backend._lib.i2d_X509_bio(
            bio, self._x509
        )
        assert res == 1
        der = self._backend._read_mem_bio(bio)
        h.update(der)
        return h.finalize()

    @property
    def version(self):
        version = self._backend._lib.X509_get_version(self._x509)
        if version == 0:
            return x509.Version.v1
        elif version == 2:
            return x509.Version.v3
        else:
            raise x509.InvalidVersion(
                "{0} is not a valid X509 version".format(version), version
            )

    @property
    def serial(self):
        asn1_int = self._backend._lib.X509_get_serialNumber(self._x509)
        assert asn1_int != self._backend._ffi.NULL
        bn = self._backend._lib.ASN1_INTEGER_to_BN(
            asn1_int, self._backend._ffi.NULL
        )
        assert bn != self._backend._ffi.NULL
        bn = self._backend._ffi.gc(bn, self._backend._lib.BN_free)
        return self._backend._bn_to_int(bn)

    def public_key(self):
        pkey = self._backend._lib.X509_get_pubkey(self._x509)
        assert pkey != self._backend._ffi.NULL
        pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free)

        return self._backend._evp_pkey_to_public_key(pkey)

    @property
    def not_valid_before(self):
        asn1_time = self._backend._lib.X509_get_notBefore(self._x509)
        return self._parse_asn1_time(asn1_time)

    @property
    def not_valid_after(self):
        asn1_time = self._backend._lib.X509_get_notAfter(self._x509)
        return self._parse_asn1_time(asn1_time)

    def _parse_asn1_time(self, asn1_time):
        assert asn1_time != self._backend._ffi.NULL
        generalized_time = self._backend._lib.ASN1_TIME_to_generalizedtime(
            asn1_time, self._backend._ffi.NULL
        )
        assert generalized_time != self._backend._ffi.NULL
        generalized_time = self._backend._ffi.gc(
            generalized_time, self._backend._lib.ASN1_GENERALIZEDTIME_free
        )
        time = self._backend._ffi.string(
            self._backend._lib.ASN1_STRING_data(
                self._backend._ffi.cast("ASN1_STRING *", generalized_time)
            )
        ).decode("ascii")
        return datetime.datetime.strptime(time, "%Y%m%d%H%M%SZ")

    @property
    def issuer(self):
        issuer = self._backend._lib.X509_get_issuer_name(self._x509)
        assert issuer != self._backend._ffi.NULL
        return _build_x509_name(self._backend, issuer)

    @property
    def subject(self):
        subject = self._backend._lib.X509_get_subject_name(self._x509)
        assert subject != self._backend._ffi.NULL
        return _build_x509_name(self._backend, subject)

    @property
    def signature_hash_algorithm(self):
        oid = _obj2txt(self._backend, self._x509.sig_alg.algorithm)
        try:
            return x509._SIG_OIDS_TO_HASH[oid]
        except KeyError:
            raise UnsupportedAlgorithm(
                "Signature algorithm OID:{0} not recognized".format(oid)
            )

    @property
    def extensions(self):
        extensions = []
        seen_oids = set()
        extcount = self._backend._lib.X509_get_ext_count(self._x509)
        for i in range(0, extcount):
            ext = self._backend._lib.X509_get_ext(self._x509, i)
            assert ext != self._backend._ffi.NULL
            crit = self._backend._lib.X509_EXTENSION_get_critical(ext)
            critical = crit == 1
            oid = x509.ObjectIdentifier(_obj2txt(self._backend, ext.object))
            if oid in seen_oids:
                raise x509.DuplicateExtension(
                    "Duplicate {0} extension found".format(oid), oid
                )
            elif oid == x509.OID_BASIC_CONSTRAINTS:
                value = self._build_basic_constraints(ext)
            elif oid == x509.OID_KEY_USAGE and critical:
                # TODO: remove this obviously.
                warnings.warn(
                    "Extension support is not fully implemented. A key usage "
                    "extension with the critical flag was seen and IGNORED."
                )
                seen_oids.add(oid)
                continue
            elif critical:
                raise x509.UnsupportedExtension(
                    "{0} is not currently supported".format(oid), oid
                )
            else:
                # Unsupported non-critical extension, silently skipping for now
                seen_oids.add(oid)
                continue

            seen_oids.add(oid)
            extensions.append(x509.Extension(oid, critical, value))

        return x509.Extensions(extensions)

    def _build_basic_constraints(self, ext):
        bc_st = self._backend._lib.X509V3_EXT_d2i(ext)
        assert bc_st != self._backend._ffi.NULL
        basic_constraints = self._backend._ffi.cast(
            "BASIC_CONSTRAINTS *", bc_st
        )
        basic_constraints = self._backend._ffi.gc(
            basic_constraints, self._backend._lib.BASIC_CONSTRAINTS_free
        )
        # The byte representation of an ASN.1 boolean true is \xff. OpenSSL
        # chooses to just map this to its ordinal value, so true is 255 and
        # false is 0.
        ca = basic_constraints.ca == 255
        if basic_constraints.pathlen == self._backend._ffi.NULL:
            path_length = None
        else:
            bn = self._backend._lib.ASN1_INTEGER_to_BN(
                basic_constraints.pathlen, self._backend._ffi.NULL
            )
            assert bn != self._backend._ffi.NULL
            bn = self._backend._ffi.gc(bn, self._backend._lib.BN_free)
            path_length = self._backend._bn_to_int(bn)

        return x509.BasicConstraints(ca, path_length)
<|endoftext|>


<commit_before>import re
import os
import logging
import argparse

__author__ = """Lars Solberg"""
__email__ = 'lars.solberg@gmail.com'
__version__ = '0.6.1'

logger = logging.getLogger("taggo")
hashtag_re = re.compile(r'\B#([^ \.,]+)\b')

class Error(Exception):
    pass

class FolderException(Error):
    pass

class Taggo:
    # What should we name the file inside the folder belonging to a tag?
    # You should include enough data here so we wont get a name-conflict.
    # In case of conflicts, it will get overwritten, we do no check..
    symlink_name_template = "{rel_folders} - {basename}"

    def __init__(self, args):
        self.origin_cwd = os.getcwd()
        self.args = args
        logger.debug("Initializing using options: {}".format(args.__dict__))

    def __del__(self):
        # Try to get back to our original directory after we are done.
        # Not normally that important, but usefull in some cases, example for testing.
        os.chdir(self.origin_cwd)

    def _make_symlinks(self, basename, symlink_name_data, dirpath, dst_path, is_file=True):
        tags = set(hashtag_re.findall(basename))
        logger.debug("  {}({}) have tags({})".format(
            'file' if is_file else 'folder',
            basename,
            tags
        ))

        symlink_name_data['basename'] = basename

        # Filename is only set if we are making a symlink from a file
        if is_file:
            full_path = os.path.join(dirpath, basename)
        else:
            full_path = dirpath

        for tag in tags:
            # We replaces - with folder-separator so we can
            # use eg #tag-tag2 as a way of making tags in tags..
            # Inside nested folders..
            tag = tag.replace("-", os.path.sep)

            tag_folder = os.path.join(dst_path, tag)
            symlink_name = self.symlink_name_template.format(**symlink_name_data)
            logger.debug("Made symlink-name ({}) from symlink-name-data: {}".format(
                symlink_name, symlink_name_data
            ))

            # Find the related path to the file, from our symlink.
            # It is better to have a symlink pointing to our destination
            # via a relative path, than an absolute path.
            symlink_destination = os.path.relpath(full_path, tag_folder)

            full_symlink_path = os.path.join(tag_folder, symlink_name)

            if not os.path.isdir(tag_folder):
                logger.debug("    making tag-directory: {}".format(tag_folder))
                if not self.args.dry:
                    os.makedirs(tag_folder)

            logger.debug("    making symlink:")
            logger.debug("      in: {}".format(full_symlink_path))
            logger.debug("      to: {}".format(symlink_destination))

            if not self.args.dry:
                try:
                    os.symlink(symlink_destination, full_symlink_path)
                except FileExistsError:
                    pass

    def run(self):
        logger.debug("run()")

        src_path = os.path.abspath(self.args.src)
        dst_path = os.path.abspath(self.args.dst)
        logger.debug("Using src-path: {}".format(src_path))
        logger.debug("Using dst-path: {}".format(dst_path))

        if not os.path.isdir(src_path):
            raise FolderException("Didnt find src-path: {}".format(src_path))

        if os.path.exists(dst_path):
            if os.path.isdir(self.args.dst):
                logger.debug("dst path exists and is a folder")
            else:
                raise FolderException("dst exist but is not a folder. Cant continue")
        else:
            logger.debug("dst folder not found, creating")
            if not self.args.dry:
                os.makedirs(self.args.dst)

        # Information we can use with symlink_name_template to decide what to name
        # the file inside the tagged folder.
        symlink_name_data = {
            'rel_folders': '',  # Folders where we found the file, / replaced with _
            'basename': '',  # Original filename
        }

        # Change the folder to make it easier for ourself when getting
        # names of paths to use in link-names
        os.chdir(src_path)

        logger.debug('Changing folder to {} to start the search'.format(src_path))
        for dirpath, dirnames, filenames in os.walk('.'):
            logger.debug("Checking directory: {}".format(dirpath))

            relative_path = dirpath.split(os.path.sep)
            current_folder = relative_path[-1]
            relative_path.pop(0)  # Remove the "." entry

            if relative_path:
                symlink_name_data['rel_folders'] = '_'.join(relative_path)
            else:
                symlink_name_data['rel_folders'] = 'root'

            if "#" in current_folder:
                self._make_symlinks(
                    os.path.basename(dirpath),
                    symlink_name_data,
                    dirpath,
                    dst_path,
                    is_file=False
                )

            for filename in filenames:
                if "#" not in filename:
                    continue

                self._make_symlinks(
                    filename,
                    symlink_name_data,
                    dirpath,
                    dst_path,
                    is_file=True
                )

    def cleanup(self):
        src_path = os.path.abspath(self.args.src)
        if not os.path.isdir(src_path):
            raise FolderException("Didnt find src directory: {}".format(src_path))

        for root, dirs, files in os.walk(src_path):
            for f in files:
                full_path = os.path.join(root, f)
                if not os.path.islink(full_path):
                    continue

                link_path = os.path.join(root, os.readlink(full_path))
                exists = True if os.path.exists(link_path) else False
                logger.debug("Symlink: {}".format(full_path))
                logger.debug("  points to: {}".format(link_path))
                logger.debug("  destination exist: {}".format(exists))
                if not exists:
                    logger.debug("  deleting...")
                    if not self.args.dry:
                        os.unlink(full_path)

    def rename(self):
        pass

    def info(self):
        src_path = os.path.abspath(self.args.src)
        logger.debug("Using src-path: {}".format(src_path))

        if not os.path.isdir(src_path):
            raise FolderException("Didnt find src-path: {}".format(src_path))

        folder_tags = []
        file_tags = []
        for root, dirs, files in os.walk(src_path):
            for d in dirs:
                folder_tags += hashtag_re.findall(d)
            for f in files:
                file_tags += hashtag_re.findall(f)

        folder_tags = sorted(set(folder_tags))
        file_tags = sorted(set(file_tags))

        print("Folder tags:")
        for folder_tag in folder_tags:
            print("  {}".format(folder_tag))

        print()
        print("File tags:")
        for file_tag in file_tags:
            print("  {}".format(file_tag))

def main(known_args=None, reraise=False):
    parser = argparse.ArgumentParser(
        description="Create symlinks to files/folders based on their names"
    )
    parser.add_argument(
        "--debug",
        action="store_true",
        help="Print extra info about what we are doing",
    )

    subparsers = parser.add_subparsers(dest="cmd")
    subparsers.required = True

    # run
    parser_run = subparsers.add_parser("run", help="")
    parser_run.add_argument(
        "--dry",
        help="Dont actually do anything",
        action="store_true"
    )
    parser_run.add_argument(
        "src",
        help="Source folder"
    )
    parser_run.add_argument(
        "dst",
        help="Destination folder, folder to store the tags/symlinks"
    )

    # cleanup
    parser_cleanup = subparsers.add_parser("cleanup", help="Remove dead symlinks from src")
    parser_cleanup.add_argument(
        "--dry",
        help="Dont actually do anything",
        action="store_true"
    )
    parser_cleanup.add_argument(
        "src",
        help="Source folder, the folder containing your symlinks"
    )

    # rename
    parser_cleanup = subparsers.add_parser("rename", help="Rename an existing tag")
    parser_cleanup.add_argument(
        "--dry",
        help="Dont actually do anything",
        action="store_true"
    )
    parser_cleanup.add_argument(
        "src",
        help="Source folder, the folder containing your tagged files (not the symlinks)"
    )
    parser_cleanup.add_argument(
        "from",
        help="Old original tag"
    )
    parser_cleanup.add_argument(
        "to",
        help="New tag"
    )

    # info
    parser_cleanup = subparsers.add_parser("info", help="List existing tags and some info")
    parser_cleanup.add_argument(
        "src",
        help="Source folder, the folder containing your tagged files (not the symlinks)"
    )

    args = parser.parse_args(known_args)

    logging.basicConfig(format="%(asctime)s - %(levelname)s - %(message)s")
    if args.debug or os.environ.get("DEBUG"):
        logger.setLevel(logging.DEBUG)
    else:
        logger.setLevel(logging.INFO)

    t = Taggo(args)

    try:
        getattr(t, args.cmd)()
    except Error as e:
        logger.error(e)
        if reraise:
            raise

if __name__ == "__main__":  # pragma: no cover
    main()  # pragma: no cover

<commit_msg>Adding rename capability<commit_after>import re
import os
import logging
import argparse

__author__ = """Lars Solberg"""
__email__ = 'lars.solberg@gmail.com'
__version__ = '0.6.1'

logger = logging.getLogger("taggo")
hashtag_re = re.compile(r'\B#([^ \.,]+)\b')

class Error(Exception):
    pass

class FolderException(Error):
    pass

class Taggo:
    # What should we name the file inside the folder belonging to a tag?
    # You should include enough data here so we wont get a name-conflict.
    # In case of conflicts, it will get overwritten, we do no check..
    symlink_name_template = "{rel_folders} - {basename}"

    def __init__(self, args):
        self.origin_cwd = os.getcwd()
        self.args = args
        logger.debug("Initializing using options: {}".format(args.__dict__))

    def __del__(self):
        # Try to get back to our original directory after we are done.
        # Not normally that important, but usefull in some cases, example for testing.
        os.chdir(self.origin_cwd)

    def _make_symlinks(self, basename, symlink_name_data, dirpath, dst_path, is_file=True):
        tags = set(hashtag_re.findall(basename))
        logger.debug("  {}({}) have tags({})".format(
            'file' if is_file else 'folder',
            basename,
            tags
        ))

        symlink_name_data['basename'] = basename

        # Filename is only set if we are making a symlink from a file
        if is_file:
            full_path = os.path.join(dirpath, basename)
        else:
            full_path = dirpath

        for tag in tags:
            # We replaces - with folder-separator so we can
            # use eg #tag-tag2 as a way of making tags in tags..
            # Inside nested folders..
            tag = tag.replace("-", os.path.sep)

            tag_folder = os.path.join(dst_path, tag)
            symlink_name = self.symlink_name_template.format(**symlink_name_data)
            logger.debug("Made symlink-name ({}) from symlink-name-data: {}".format(
                symlink_name, symlink_name_data
            ))

            # Find the related path to the file, from our symlink.
            # It is better to have a symlink pointing to our destination
            # via a relative path, than an absolute path.
            symlink_destination = os.path.relpath(full_path, tag_folder)

            full_symlink_path = os.path.join(tag_folder, symlink_name)

            if not os.path.isdir(tag_folder):
                logger.debug("    making tag-directory: {}".format(tag_folder))
                if not self.args.dry:
                    os.makedirs(tag_folder)

            logger.debug("    making symlink:")
            logger.debug("      in: {}".format(full_symlink_path))
            logger.debug("      to: {}".format(symlink_destination))

            if not self.args.dry:
                try:
                    os.symlink(symlink_destination, full_symlink_path)
                except FileExistsError:
                    pass

    def run(self):
        logger.debug("run()")

        src_path = os.path.abspath(self.args.src)
        dst_path = os.path.abspath(self.args.dst)
        logger.debug("Using src-path: {}".format(src_path))
        logger.debug("Using dst-path: {}".format(dst_path))

        if not os.path.isdir(src_path):
            raise FolderException("Didnt find src-path: {}".format(src_path))

        if os.path.exists(dst_path):
            if os.path.isdir(self.args.dst):
                logger.debug("dst path exists and is a folder")
            else:
                raise FolderException("dst exist but is not a folder. Cant continue")
        else:
            logger.debug("dst folder not found, creating")
            if not self.args.dry:
                os.makedirs(self.args.dst)

        # Information we can use with symlink_name_template to decide what to name
        # the file inside the tagged folder.
        symlink_name_data = {
            'rel_folders': '',  # Folders where we found the file, / replaced with _
            'basename': '',  # Original filename
        }

        # Change the folder to make it easier for ourself when getting
        # names of paths to use in link-names
        os.chdir(src_path)

        logger.debug('Changing folder to {} to start the search'.format(src_path))
        for dirpath, dirnames, filenames in os.walk('.'):
            logger.debug("Checking directory: {}".format(dirpath))

            relative_path = dirpath.split(os.path.sep)
            current_folder = relative_path[-1]
            relative_path.pop(0)  # Remove the "." entry

            if relative_path:
                symlink_name_data['rel_folders'] = '_'.join(relative_path)
            else:
                symlink_name_data['rel_folders'] = 'root'

            if "#" in current_folder:
                self._make_symlinks(
                    os.path.basename(dirpath),
                    symlink_name_data,
                    dirpath,
                    dst_path,
                    is_file=False
                )

            for filename in filenames:
                if "#" not in filename:
                    continue

                self._make_symlinks(
                    filename,
                    symlink_name_data,
                    dirpath,
                    dst_path,
                    is_file=True
                )

    def cleanup(self):
        src_path = os.path.abspath(self.args.src)
        if not os.path.isdir(src_path):
            raise FolderException("Didnt find src directory: {}".format(src_path))

        for root, dirs, files in os.walk(src_path):
            for f in files:
                full_path = os.path.join(root, f)
                if not os.path.islink(full_path):
                    continue

                link_path = os.path.join(root, os.readlink(full_path))
                exists = True if os.path.exists(link_path) else False
                logger.debug("Symlink: {}".format(full_path))
                logger.debug("  points to: {}".format(link_path))
                logger.debug("  destination exist: {}".format(exists))
                if not exists:
                    logger.debug("  deleting...")
                    if not self.args.dry:
                        os.unlink(full_path)

    def rename(self):
        src_path = os.path.abspath(self.args.src)
        if not os.path.isdir(src_path):
            raise FolderException("Didnt find src directory: {}".format(src_path))
        logger.debug("Will look in folder '{}' for tags to rename from '{}' to '{}'".format(
            src_path, self.args.original, self.args.new
        ))

        original_tag = "#{}".format(self.args.original)
        if not hashtag_re.fullmatch(original_tag):
            raise Error("Invalid hashtag: '{}'".format(original_tag))

        new_tag = "#{}".format(self.args.new)
        if not hashtag_re.fullmatch(new_tag):
            raise Error("Invalid hashtag: '{}'".format(new_tag))

        queue = []
        logger.debug("Starting collecting list of files/folders to rename:")
        for root, dirs, files in os.walk(src_path):
            if dirs:
                for d in dirs:
                    if original_tag in d:
                        full_path = os.path.join(root, d)
                        logger.debug("  Found directory: {}".format(full_path))
                        queue.append(full_path)

            if files:
                for f in files:
                    if original_tag in f:
                        full_path = os.path.join(root, f)
                        logger.debug("  Found file: {}".format(full_path))
                        queue.append(full_path)

        # Start with the longest path, so we can be sure that we are not renaming a
        # folder that contains another file or folder we also should rename.
        # We must sort, or be dirty in the os.walk loop. This is much cleaner.
        for e in sorted(queue, key=len, reverse=True):
            dirname = os.path.dirname(e)
            old_basename = os.path.basename(e)
            new_basename = old_basename.replace(original_tag, new_tag)
            logger.debug("Renaming: {}{}{{{} -> {}}}".format(
                dirname,
                os.path.sep,
                old_basename,
                new_basename
            ))
            if not self.args.dry:
                os.rename(e, os.path.join(dirname, new_basename))

    def info(self):
        src_path = os.path.abspath(self.args.src)
        logger.debug("Using src-path: {}".format(src_path))

        if not os.path.isdir(src_path):
            raise FolderException("Didnt find src-path: {}".format(src_path))

        folder_tags = []
        file_tags = []
        for root, dirs, files in os.walk(src_path):
            for d in dirs:
                folder_tags += hashtag_re.findall(d)
            for f in files:
                file_tags += hashtag_re.findall(f)

        folder_tags = sorted(set(folder_tags))
        file_tags = sorted(set(file_tags))

        print("Folder tags:")
        for folder_tag in folder_tags:
            print("  {}".format(folder_tag))

        print()
        print("File tags:")
        for file_tag in file_tags:
            print("  {}".format(file_tag))

def main(known_args=None, reraise=False):
    parser = argparse.ArgumentParser(
        description="Create symlinks to files/folders based on their names"
    )
    parser.add_argument(
        "--debug",
        action="store_true",
        help="Print extra info about what we are doing",
    )

    subparsers = parser.add_subparsers(dest="cmd")
    subparsers.required = True

    # run
    parser_run = subparsers.add_parser("run", help="")
    parser_run.add_argument(
        "--dry",
        help="Dont actually do anything",
        action="store_true"
    )
    parser_run.add_argument(
        "src",
        help="Source folder"
    )
    parser_run.add_argument(
        "dst",
        help="Destination folder, folder to store the tags/symlinks"
    )

    # cleanup
    parser_cleanup = subparsers.add_parser("cleanup", help="Remove dead symlinks from src")
    parser_cleanup.add_argument(
        "--dry",
        help="Dont actually do anything",
        action="store_true"
    )
    parser_cleanup.add_argument(
        "src",
        help="Source folder, the folder containing your symlinks"
    )

    # rename
    parser_cleanup = subparsers.add_parser("rename", help="Rename an existing tag")
    parser_cleanup.add_argument(
        "--dry",
        help="Dont actually do anything",
        action="store_true"
    )
    parser_cleanup.add_argument(
        "src",
        help="Source folder, the folder containing your tagged files (not the symlinks)"
    )
    parser_cleanup.add_argument(
        "original",
        help="Original tag you want to replace (no #)"
    )
    parser_cleanup.add_argument(
        "new",
        help="New tag, without the #"
    )

    # info
    parser_cleanup = subparsers.add_parser("info", help="List existing tags and some info")
    parser_cleanup.add_argument(
        "src",
        help="Source folder, the folder containing your tagged files (not the symlinks)"
    )

    args = parser.parse_args(known_args)

    logging.basicConfig(format="%(asctime)s - %(levelname)s - %(message)s")
    if args.debug or os.environ.get("DEBUG"):
        logger.setLevel(logging.DEBUG)
    else:
        logger.setLevel(logging.INFO)

    t = Taggo(args)

    try:
        getattr(t, args.cmd)()
    except Error as e:
        logger.error(e)
        if reraise:
            raise

if __name__ == "__main__":  # pragma: no cover
    main()  # pragma: no cover

<|endoftext|>