active_path.py :  » Development » Leo » Leo-4.7.1-final » leo » plugins » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Development » Leo 
Leo » Leo 4.7.1 final » leo » plugins » active_path.py
#@+leo-ver=4-thin
#@+node:tbrown.20080613095157.2:@thin active_path.py
#@<< docstring >>
#@+node:tbrown.20080613095157.3:<< docstring >>
'''Synchronize @path nodes with folders.

If a node is named '@path path_to_folder', the content (file and folder names)
of the folder and the children of that node will synchronized whenever
the node's status-iconbox is double clicked.

For files not previously seen in a folder a new node will appear on top of
the children list (with a mark).

Folders appear in the list as /foldername/.  If you double click on the
icon-box of the folder node, it will have children added to it based on
the contents of the folder on disk.  These folders have the '@path' directive
as the first line of their body text.

When files are deleted from thefolderthelistupdatedbydouble import 
clicking the files will appear in the list as *filename* (or */foldername/*).

You can describe files and directories in the body of the nodes.

You can organize files and directories with organizer nodes, an organizer
node name cannot contain with '/'.

Files and folders can be created by entering a node with the required name as its headline
(must start and/or end with "/" for a folder) and then double clicking on the node's
status-iconbox.

@auto nodes can be set up for existing files can be loaded by
double clicking on the node's status-iconbox.  If you prefer
@shadow or something else use the "active_path_attype" setting,
without the "@".

There are commands on the Plugins active_path submenu:

    - show path - show the current path
    - set absolute path - changes a node "/dirname/" to "@path /absolute/path/to/dirname".
    - purge vanished (recursive) - remove *entries*
    - update recursive - recursive load of directories, use with caution on large
      file systems

If you want to use an input other than double clicking a node's status-iconbox
set active_path_event to a value like 'iconrclick1' or 'iconclick1'.

active_path is a rewrite of the at_directory plugin to use @path directives (which influence
@auto and other @file type directives), and to handle sub-folders more automatically.
'''
#@nonl
#@-node:tbrown.20080613095157.3:<< docstring >>
#@nl

#@@language python
#@@tabwidth -4

import leo.core.leoGlobals as g
import leo.core.leoPlugins as leoPlugins
import os
import re

from leo.plugins.plugins_menu import PlugIn

testing = False

__version__ = "0.2"

#@<< version history >>
#@+node:ekr.20090120065737.1:<< version history >>
#@@nocolor-node
#@+at
# 
# 0.2 EKR: replaced begin/endUpdate with c.redraw(p)
#@-at
#@-node:ekr.20090120065737.1:<< version history >>
#@nl

#@+others
#@+node:tbrown.20091128094521.15048:init
if g.app.gui.guiName() == "qt":
    # for the right click context menu
    from PyQt4 import QtCore

def init():
    leoPlugins.registerHandler('after-create-leo-frame', attachToCommander)
    g.act_on_node.add(active_path_act_on_node, priority = 90)

    g.plugin_signon(__name__)

    if g.app.gui.guiName() == "qt":
        g.tree_popup_handlers.append(popup_entry)

    return True
#@-node:tbrown.20091128094521.15048:init
#@+node:tbrown.20091128094521.15047:attachToCommander
# defer binding event until c exists
def attachToCommander(t,k):
    c = k.get('c')
    event = c.config.getString('active_path_event') or "icondclick1"
    leoPlugins.registerHandler(event, lambda t,k: onSelect(t,k))

    # not using a proper class, so
    c.__active_path = {'ignore': [], 'autoload': []}

    if c.config.getData('active_path_ignore'):
        c.__active_path['ignore'] = [re.compile(i, re.IGNORECASE)
            for i in c.config.getData('active_path_ignore')]
    if c.config.getData('active_path_autoload'):
        c.__active_path['autoload'] = [re.compile(i, re.IGNORECASE)
            for i in c.config.getData('active_path_autoload')]
#@-node:tbrown.20091128094521.15047:attachToCommander
#@+node:tbrown.20091128094521.15042:popup_entry
def mkCmd(cmd, c):

    def f():
        return cmd(c)

    return f

def popup_entry(c,p,menu):

    pathmenu = menu.addMenu("Path")

    for i in globals():
        if i.startswith('cmd_'):

            a = pathmenu.addAction(PlugIn.niceMenuName(i))
            CMD = globals()[i]
            a.connect(a, QtCore.SIGNAL("triggered()"), mkCmd(CMD,c))
#@-node:tbrown.20091128094521.15042:popup_entry
#@+node:tbrown.20091128094521.15037:isDirNode
def isDirNode(p):

    return (
        p.h.startswith('@path ') or 
        #  '/foo/' form *assumes* @path in body
        (not p.h.strip().startswith('@') and p.h.strip().endswith('/'))
        or p.h.strip().startswith('/')
        )
#@-node:tbrown.20091128094521.15037:isDirNode
#@+node:tbrown.20091128094521.15039:isFileNode
def isFileNode(p):
    """really isEligibleToBecomeAFileNode"""
    return (not p.h.strip().startswith('@') and not p.hasChildren() and
      not isDirNode(p) and isDirNode(p.parent())
      and not p.b.strip())
#@-node:tbrown.20091128094521.15039:isFileNode
#@+node:tbrown.20091129085043.9329:inReList
def inReList(txt, lst):

    for pat in lst:
        if pat.search(txt):
            return True

    return False
#@-node:tbrown.20091129085043.9329:inReList
#@+node:tbrown.20091128094521.15040:subDir
def subDir(d, p):

    if p.h.strip().startswith('@path'):
        p = p.h.split(None,1)
        if len(p) != 2:
            return None
        p = p[1]

    elif p.b.strip().startswith('@path'):
        p = p.b.split('\n',1)[0].split(None,1)
        if len(p) != 2:
            return None
        p = p[1]

    else:
        p = p.h.strip(' /')

    return os.path.join(d,p)
#@-node:tbrown.20091128094521.15040:subDir
#@+node:tbrown.20080613095157.4:onSelect
def onSelect (tag,keywords):
    """Determine if a file or directory status-iconbox was clicked, and the path"""
    c = keywords.get('c') or keywords.get('new_c')
    if not c: return
    p = keywords.get("p")
    pos = p.copy()

    path = getPath(c, p)

    if path:
        if sync_node_to_folder(c,pos,path):
            c.requestRedrawFlag = True
            c.redraw()
            return True

    return None
#@-node:tbrown.20080613095157.4:onSelect
#@+node:tbrown.20080616153649.4:getPath
def getPath(c, p):

    for n in p.self_and_parents():
        if n.h.startswith('@path'):
            break
    else:
        return None  # must have a full fledged @path in parents

    aList = g.get_directives_dict_list(p)
    path = c.scanAtPathDirectives(aList)
    if (not isDirNode(p)):  # add file name
        h = p.h.split(None, 1)
        if h[0].startswith('@') and len(h) == 2:
            path = os.path.join(path, h[1])
        else:
            path = os.path.join(path, p.h.strip())
    return path
#@-node:tbrown.20080616153649.4:getPath
#@+node:tbrown.20090219133655.230:getPathOld
def getPathOld(p):
    # NOT USED, my version which does its own @path scanning
    p = p.copy()

    path = []

    while p:
        h = p.h

        if g.match_word(h,0,"@path"):  # top of the tree
            path.insert(0,os.path.expanduser(h[6:].strip()))
            d = os.path.join(*path)
            return d

        elif h.startswith('@'):  # some other directive, run away
            break

        elif isDirNode(p):  # a directory
            path.insert(0,h.strip('/*'))

        elif not p.hasChildren():  # a leaf node, assume a file
            path.insert(0,h.strip('*'))

        p = p.parent()

    return None
#@-node:tbrown.20090219133655.230:getPathOld
#@+node:tbrown.20080613095157.5:flattenOrganizers
def flattenOrganizers(p):
    """Children of p, some of which may be in organizer nodes

    In the following example nodeA's children are nodes B, F, and G::

      /nodeA/
         nodeB
         /nodeC/
            nodeD
            nodeE
         oldStuff
            nodeF
            nodeG    
    """    
    for n in p.children():
        yield n
        if (not isDirNode(n)
            and not n.h.startswith('@')):
            for i in flattenOrganizers(n):
                yield i
#@nonl
#@-node:tbrown.20080613095157.5:flattenOrganizers
#@+node:tbrown.20080613095157.6:sync_node_to_folder
def sync_node_to_folder(c,parent,d,updateOnly=False, recurse=False):
    """Decide whether we're opening or creating a file or a folder"""

    if (not updateOnly
      and not recurse
      and isDirNode(parent) and not parent.h.strip().startswith('@path')
      and not parent.b.strip().startswith('@path')):
        createDir(c,parent,d)
        return True  # even if it didn't happen, else get stuck in edit mode w/o focus

    if os.path.isdir(d):
        if (isDirNode(parent)
            and (not updateOnly or recurse or parent.hasChildren())):
            # no '/' or @path implies organizer
            openDir(c,parent,d)
            return True

    if updateOnly: return False

    if os.path.isfile(d) and isFileNode(parent):
        openFile(c,parent,d)
        return True

    if isFileNode(parent):
        createFile(c,parent,d)
        return True  # even if it didn't happen, else get stuck in edit mode w/o focus

    return False
#@-node:tbrown.20080613095157.6:sync_node_to_folder
#@+node:tbrown.20080613095157.7:createDir
def createDir(c,parent,d):
    """Ask if we should create a new folder"""
    newd = parent.h.strip(' /')
    ok = g.app.gui.runAskYesNoDialog(c, 'Create folder?',
        'Create folder '+newd+'?')
    if ok == 'no':
        return False
    parent.h = '/'+newd+'/'
    if parent.b.strip():
        parent.b = '@path '+newd+'\n'+parent.b
    else:
        parent.b = '@path '+newd

    os.mkdir(os.path.join(d, newd))
    return True
#@-node:tbrown.20080613095157.7:createDir
#@+node:tbrown.20080613095157.8:createFile
def createFile(c,parent,d):
    """Ask if we should create a new file"""
    directory = os.path.dirname(d)
    if not os.path.isdir(directory):
        g.es('Create parent directories first', color='red')
        return False

    d = os.path.basename(d)
    atType = c.config.getString('active_path_attype') or 'auto'
    ok = g.app.gui.runAskYesNoDialog(c, 'Create / load file?',
        'Create file @'+atType+' '+d+'?')
    if ok == 'no':
        return False
    c.setHeadString(parent, '@'+atType+' '+d)
    c.bodyWantsFocusNow()
    return True
#@nonl
#@-node:tbrown.20080613095157.8:createFile
#@+node:tbrown.20080613095157.9:openFile
def openFile(c,parent,d):
    """Open an existing file"""
    # hdr = os.path.basename(d)
    # parent.h = '@auto '+hdr
    # parent.b = file(d).read()
    c.importCommands.createOutline(d,parent=parent,atAuto=True)
    atType = c.config.getString('active_path_attype') or 'auto'
    parent.h = '@' + atType + ' ' + parent.h

    c.bodyWantsFocusNow()
#@-node:tbrown.20080613095157.9:openFile
#@+node:tbrown.20080613095157.10:openDir
def openDir(c,parent,d):
    """Expand / refresh an existing folder"""

    # compare folder content to children
    try:
        path, dirs, files = os.walk(d).next()
    except StopIteration:
        # directory deleted?
        c.setHeadString(parent,'*'+parent.h.strip('*')+'*')
        return

    parent.expand()

    oldlist = set()
    newlist = []

    # get children info
    for p in flattenOrganizers(parent):
        entry = p.h.strip('/*')
        if entry.startswith('@'):  # remove only the @part
            directive = entry.split(None,1)
            if len(directive) > 1:
                entry = entry[len(directive[0]):].strip()
        oldlist.add(entry)

    for d2 in dirs:
        if d2 in oldlist:
            oldlist.discard(d2)
        else:
            newlist.append('/'+d2+'/')
    for f in files:
        if f in oldlist:
            oldlist.discard(f)
        else:
            newlist.append(f)

    # insert newlist
    newlist.sort()
    ignored = 0
    newlist.reverse()  # un-reversed by the following loop
    for name in newlist:

        if inReList(name, c.__active_path['ignore']):
            ignored += 1
            continue

        p = parent.insertAsNthChild(0)
        c.setChanged(True)
        c.setHeadString(p,name)
        if name.startswith('/'): 
            # sufficient test of dirness as we created newlist
            c.setBodyString(p, '@path '+name.strip('/'))
        elif inReList(name, c.__active_path['autoload']):
            openFile(c, p, os.path.join(d, p.h))
        p.setMarked()

    if ignored:
        g.es('Ignored %d files in directory' % ignored)

    # warn / mark for orphan oldlist
    for p in flattenOrganizers(parent):
        h = p.h.strip('/*')  # strip / and *
        if (h not in oldlist 
            or (p.hasChildren() and not isDirNode(p))):  # clears bogus '*' marks
            nh = p.h.strip('*')  # strip only *
        else:
            nh = '*'+p.h.strip('*')+'*'
            if isDirNode(p):
                for orphan in p.subtree():
                    c.setHeadString(orphan, '*'+orphan.h.strip('*')+'*')
        if p.h != nh:  # don't dirty node unless we must
            c.setHeadString(p,nh)
#@-node:tbrown.20080613095157.10:openDir
#@+node:ville.20090223183051.1:act on node
def cmd_ActOnNode(c, p=None, event=None):
    """ act_on_node handler for active_path.py
    """

    # implementation mostly copied from onSelect
    if p is None:
        p = c.currentPosition()

    pos = p.copy()
    path = getPath(c, p)

    if path:
        sync_node_to_folder(c,pos,path)
        c.requestRedrawFlag = True
        c.redraw()
        return True

    else:

        raise leoPlugins.TryNext

active_path_act_on_node = cmd_ActOnNode
#@-node:ville.20090223183051.1:act on node
#@+node:tbrown.20080616153649.2:cmd_ShowCurrentPath
def cmd_ShowCurrentPath(c):
    """Just show the path to the current file/directory node in the log pane."""
    g.es(getPath(c, c.p))
#@-node:tbrown.20080616153649.2:cmd_ShowCurrentPath
#@+node:tbrown.20080619080950.16:cmd_UpdateRecursive
def cmd_UpdateRecursive(c):
    """Recursive update, no new expansions."""
    p = c.p

    for s in p.self_and_subtree():
        path = getPath(c, s)

        if path:
            sync_node_to_folder(c,s,path,updateOnly=True)

    c.redraw(p)

#@-node:tbrown.20080619080950.16:cmd_UpdateRecursive
#@+node:tbrown.20090225191501.1:cmd_LoadRecursive
def cmd_LoadRecursive(c):
    """Recursive update, with expansions."""
    p = c.p

    for s in p.self_and_subtree():
        path = getPath(c, s)

        if path:
            sync_node_to_folder(c,s,path,updateOnly=True,recurse=True)

    c.redraw(p)
#@-node:tbrown.20090225191501.1:cmd_LoadRecursive
#@+node:tbrown.20091214212801.13475:cmd_SetNodeToAbsolutePathRecursive
def cmd_SetNodeToAbsolutePathRecursive(c):
    """Change "/dirname/" to "@path /absolute/path/to/dirname", recursively"""

    p = c.p

    for s in p.self_and_subtree():

        cmd_SetNodeToAbsolutePath(c, p=s)

    c.redraw(p)
#@-node:tbrown.20091214212801.13475:cmd_SetNodeToAbsolutePathRecursive
#@+node:tbrown.20080616153649.5:cmd_SetNodeToAbsolutePath
def cmd_SetNodeToAbsolutePath(c, p=None):
    """Change "/dirname/" to "@path /absolute/path/to/dirname"."""

    if p is None:
        p = c.p

    path = getPath(c, p)
    d = p.h.split(None, 1)
    if len(d) > 1 and d[0].startswith('@'):
        type_  = d[0]+" "
    elif isDirNode(p):
        type_ = "@path "
        p.b = '# path Created from node "%s"\n\n' % p.h + p.b
    else:
        type_ = "@auto "
    p.h = type_+path
#@-node:tbrown.20080616153649.5:cmd_SetNodeToAbsolutePath
#@+node:tbrown.20080618141617.879:cmd_PurgeVanishedFiles
def cond(p):
    return p.h.startswith('*') and p.h.endswith('*')

def condunl(p):
    return isFileNode(p) and not p.b.strip()

def dtor(p):
    # g.es(p.h)
    p.doDelete()

def cmd_PurgeVanishedFilesHere(c):
    """Remove files no longer present, i.e. "*filename*" entries."""
    p = c.p.getParent()
    n = deleteChildren(p, cond, dtor=dtor)
    g.es('Deleted %d nodes' % n)
    c.redraw(p)

def cmd_PurgeVanishedFilesRecursive(c):
    """Remove files no longer present, i.e. "*filename*" entries."""
    p = c.p
    n = deleteDescendents(p, cond, dtor=dtor)
    g.es('Deleted at least %d nodes' % n)
    c.redraw(p)

def cmd_PurgeUnloadedFilesHere(c):
    """Remove files no longer present, i.e. "*filename*" entries."""
    p = c.p.getParent()
    n = deleteChildren(p, condunl, dtor=dtor)
    g.es('Deleted %d nodes' % n)
    c.redraw(p)

def cmd_PurgeUnloadedFilesRecursive(c):
    """Remove files no longer present, i.e. "*filename*" entries."""
    p = c.p
    n = deleteDescendents(p, condunl, dtor=dtor)
    g.es('Deleted at least %d nodes' % n)
    c.redraw(p)

def deleteChildren(p, cond, dtor=None):

    cull = [child.copy() for child in p.children() if cond(child)]

    if cull:
        cull.reverse()
        for child in cull:
            if dtor:
                dtor(child)
            else:
                child.doDelete()
        return len(cull)

    return 0

def deleteDescendents(p, cond, dtor=None, descendAnyway=False, _culls=0):

    childs = [child.copy() for child in p.children()]
    childs.reverse()
    for child in childs:
        if descendAnyway or not cond(child):
            _culls += deleteDescendents(child, cond, dtor=dtor,
                                        descendAnyway=descendAnyway)
        if cond(child):
            _culls += 1
            if dtor:
                dtor(child)
            else:
                child.doDelete()
    return _culls

#@-node:tbrown.20080618141617.879:cmd_PurgeVanishedFiles
#@+node:tbrown.20080619080950.14:testing
#@+node:tbrown.20080619080950.15:makeTestHierachy
files="""
a/
a/a/
a/a/1
a/a/2
a/a/3
a/b/
a/c/
a/c/1
a/c/2
a/c/3
b/
c/
1
2
3
"""
import os, shutil
def makeTestHierachy(c):

    shutil.rmtree('active_directory_test')
    for i in files.strip().split():
        f = 'active_directory_test/'+i
        if f.endswith('/'):
            os.makedirs(os.path.normpath(f))
        else:
            file(os.path.normpath(f),'w')

def deleteTestHierachy(c):

    for i in files.strip().split():
        f = 'active_directory_test/'+i
        if 'c/' in f and f.endswith('/'):
            shutil.rmtree(os.path.normpath(f))
        elif '2' in f:
            try: os.remove(os.path.normpath(f))
            except: pass  # already gone

if testing:
    cmd_MakeTestHierachy = makeTestHierachy
    cmd_DeleteFromTestHierachy = deleteTestHierachy
#@-node:tbrown.20080619080950.15:makeTestHierachy
#@-node:tbrown.20080619080950.14:testing
#@-others
#@-node:tbrown.20080613095157.2:@thin active_path.py
#@-leo
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.