Welcome, guest | Sign In | My Account | Store | Cart

Given a path to a primary file and a menu path, a section name and a command key, extract a command from a QEditor menu and execute it. Useful from context menu entries. Example use DoM.py -m {o}\menus.ini do.py * list, on my computer searches c:\source\python\menus.ini for the first command whose key begins with list. The command is executed without capturing its standard output.

Python, 340 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
mHelpText = '''
# ----------------------------------------------
# Name: DoM
# Description: Expand and execute command from menu
## D20H-53 Expand and execute command
#
# Author: Philip S. Rist
# Date: 10/26/2010
# Copyright 2010 by St. Thomas Software
# ----------------------------------------------
# This program is freeware.  It may be used
# for any moral purpose.  You use it at your
# own risk.  St. Thomas Software makes no
# guarantees to its fitness to do anything.
#
# If you feel you must pay for it. Please
# send one million dollars to
#
#     The International Rescue Committee
#     122 East 42nd Street
#     New York, N.Y.   10168-1289
#
# Ok, we are in a recession.  So, make it a half
# million.

# DoM.py -
# The specified command in the specified section in the specified menu file will
# be expanded with Do.py and executed.

# Usage examples:
#
#[HKEY_CLASSES_ROOT\*\Shell\Rundom]
#@="Run"
#"EditFlags"=hex:01,00,00,00
#[HKEY_CLASSES_ROOT\*\Shell\Rundom\Command]
#@="c:\\sys\\python25\\pythonw.exe c:\\bin\\dom.py -m c:\bin\menus.ini \"%1\" project open     "
#

# Syntax:
#        dom.py [ options ] <file path> <section> <command key>
#
# Options:
#        -d <path>         - set current working directory
#        -e <name>=<value> - set environment variable
#        -h                - display this text, everything else is ignored
#        -l                - list available commands, everything else is ignored
#        -m <menu path>    - set path to menu file
#                 default: '{o}\menus.ini;{i}\menus.ini'   <- can be changed
#        -v                - run in verbose mode

# Sample menu file

[DOS]
Open,c:\source\TextFiles\qeditor.exe                 "{a}"
; Open file
Print,c:\windows\nodepad.exe                        /P "{a}"
; Print file
Edit,c:\windows\system32\wscript.exe                 c:\bin\editit.vbs "{a}"
; Edit file with Notetab
Save,C:\Windows\system32\wscript.exe                 c:\bin\Util.vbs  /S  "{a}"
Has Been Ssved,C:\Windows\system32\wscript.exe                  c:\bin\Util.vbs  /K  "{a}"
UnTabify,c:\sys\python25\python.exe                  c:\sys\python25\tools\scripts\untabify.py     "{a}"
U2M,c:\bin\u2m.bat                                  "{a}"
Echo,c:\bin\messagebox.exe                          "{a}"
Dir,c:\windows\system32\cmd.exe   /C                dir "{p}\*.{e}"
'''

import sys
import os
import getopt
import Do              # Recipe: 577439
import Do2             # Recipe: 577440

import subprocess

def FindCommand(pKey, pFilePath, pSearchPath, pSection, pList=False, pVerbose=False):
    '''
    Find command keyword and extract containing line
    pKey         -- String identifying line to use as command template
    pFilePath    -- File to use in macro expansion
    pSearchPath  -- File to scan for command 
    pSection     -- Section containing command
    pList        -- 
    '''
    if pVerbose:
        print 'DoM.py FindCommand:', pKey, pFilePath, pSearchPath, pSection
    
    if not os.path.exists(pSearchPath):
        lCommand = ''
        print 'DoM.py Could not find menu file', pSearchPath
    else:
        lSection = pSection.lower()
        lKey = pKey.lower()

#       ---- Load menu file
        lFile = open(pSearchPath, 'r')
        lText = lFile.readlines()
        lFile.close()
        if len(lText) < 1:
            print 'DoM.py Menu', pSearchPath, 'read failed'

        lFound = False
        lCommand = ''
        lCount = 0
        if pList:
            print 'DoM.py Available commands in', pSearchPath

#       ---- Scan menu file
        for lLine in lText:
            lLine = lLine.lstrip()
            if len(lLine) < 1:
                continue

#           ---- Start of section
            if lLine[0] == '[':
                lCount = 0
                lPos = lLine.find(']')
                if lPos > 0:
                    lFoundSection = lLine[1:lPos].lower()
                else:
                    lFoundSection = ''
                    
#               ---- Check for conditions, conditions are ignored                    
                if lFoundSection[0] == '/':
                    lPos2 = lFoundSection.rfind('/')
                    if lPos2 >= 0:
                        lFoundSection = lFoundSection[lPos2+1:]
                elif lFoundSection[0] == '!':
                    lPos2 = lFoundSection.rfind('!')
                    if lPos2 >= 0:
                        lFoundSection = lFoundSection[lPos2+1:]
                #if lSection != lFoundSection and lSection != '*':
                if not lFoundSection.startswith(lSection) and lSection != '*':
                    if pVerbose:
                        print 'DoM.py not found', lSection, 'in', lFoundSection
                    if lFound == True:
                        break
                    continue
                elif lSection != '*' and lFound == True:
                    break
                else:
                    if pVerbose:
                        print 'DoM.py found', lSection, 'in', lFoundSection
                    if pList:
                        print 'DoM.py Section:', lFoundSection
                    lFound = True

#           ---- Comments
            elif lLine[0] == ';':
                if lFound and pList:
                    print 'DoM.py        ', lLine,

#           ---- Command lines and label lines
            else:
                if not lFound:
                    continue
                if lLine[0] == '-':
                    continue
                lPos = lLine.find(',')
                if lPos > 0:
                    lMatch = lLine[0:lPos].lower()
                else:
                    continue
                    
#               ---- Check for conditions, conditions are ignored                    
                if lMatch[0] == '/':
                    lPos2 = lMatch.rfind('/')
                    if lPos2 >= 0:
                        lMatch = lMatch[lPos2+1:]
                elif lMatch[0] == '!':
                    lPos2 = lMatch.rfind('!')
                    if lPos2 >= 0:
                        lMatch = lMatch[lPos2+1:]
                
                lCount += 1
                if pList:
                    if lPos > 0:
                        lLineText = lLine[lPos+1:]
                    print "DoM.py %5d: %-20s| %s" % (lCount, lMatch, lLineText),

#               ---- Check for matching command
                #if lKey == lMatch:                   # must match command key
                if lMatch.startswith(lKey):           # command key starts with key
                    lCommand = lLine[lPos+1:]
                    if pVerbose:
                        print 'DoM.py found command', lKey, 'in', lMatch, 'for', lCommand
                    break
        else:
            print 'DoM.py no command found in', pSearchPath, pSection
                    
    return lCommand[0:-1]


def Expand(pArgs, pFilePath, pSearchPath, pSection, pSep='!!', pList=False, pVerbose=False):
    '''
    Extract command from file and replace all macros
    pArgs        -- Args passed to program except file path and section name
    pFilePath    -- File to use in macro expansion
    pSearchPath  -- File to scan for command '
    pCount       -- Number of lines at the start of the file to scan
    pSep         -- String used to identify end of command
    pHelp        -- True to display available commands
    '''
#   ---- Find command    
    lCommand = FindCommand(pArgs[0], pFilePath,  pSearchPath, pSection, pList=pList, pVerbose=pVerbose)
    
#   ---- Expand and insert/append any passed arguments       
#        Arguments on original pb.py command line will replace {} from left to right
#        otherwise they will be appended to the end of the command 
    lStart = 1
    if len(lCommand) > 0:
        if len(pArgs) > lStart:
            for lArg in pArgs[lStart:]:
                if lArg.find('{') >= 0:
                    lArg = Do2.ExpandArg(lArg, pFilePath, '')
                if len(lArg) > 0:
                    try:
                        lTest = os.path.abspath(lArg)
                        if os.path.exists(lTest):
                            if lTest.find(" ") > 0:
                                lTest = '"' + lTest + '"'
                            lArg = lTest
                    except:
                        pass
                lPos = lCommand.find('{}')
                if lPos >= 0:
                    lCommand = lCommand[0:lPos] + lArg + lCommand[lPos+2:]
                else:
                    lCommand += ' ' + lArg
    
#   ---- Prevent unwanted arguments appended to command                    
    lPos = lCommand.rfind(pSep)
    if lPos > 0:
        lCommand = lCommand[0:lPos]
    
#   ---- Expand all remaining macros
    if lCommand.find('{') >= 0:
        lCommand = Do.Expand(lCommand, pFilePath)
        
    return lCommand
 
def submitnow(pArgs, pFilePath, pSearchPath, pSection, pVerbose, pList=False):
    'Expand and submit command'
    if pVerbose:
        print 'DoM.py File path:', pFilePath
        print 'DoM.py Menu path:', pSearchPath
        print 'DoM.py Section:  ', pSection
        print 'DoM.py Arguments:', pArgs

    lCommand = Expand(pArgs, pFilePath, pSearchPath, pSection, pList=pList, pVerbose=pVerbose)
#   ---- Any macro not expanded will be assumed to be an environment variable
#        If %...% had been used it would have been replaced when pb.py was run    
    lCommand = lCommand.replace('{}',' ')    #<-- may want to do something else
    lCommand = lCommand.replace('{', '%')    # try to replace with environment variable
    lCommand = lCommand.replace('}', '%')
    if len(lCommand) == 0:
        print 'DoM.py Expansion failed'
    else:
        lCommand = '"' + lCommand + '"'
        if pVerbose:
            print 'DoM.py Submitting: ', lCommand
        subprocess.Popen(lCommand, shell=True)

def setenviron(pValue, pFileName):
    'Set environment variable'
    lParts = pValue.split('=')
    if len(lParts) > 1:
        lKey = lParts[0]
        lValue = lParts[1]
        if lValue.find('{') >= 0:
            lValue = Do2.ExpandArg(lValue, pFileName, '')
        os.environ[lKey] = lValue
    else:
        os.environ[pValue] = ''

if __name__ == '__main__':
    (mOptions, mArgs) = getopt.getopt(sys.argv[1:], 'd:e:hlm:v')
    mVerbose = False
    mHelp = False
    mList = False
    mSearchPath = '{o}\menus.ini;{i}\menus.ini'
    
    for (mKey, mValue) in mOptions:
        if mKey == '-d':                 # Set current directory
            if mValue.find('{') >= 0:
                if len(mArgs) > 2:
                    mFilePath = os.path.abspath(mArgs[0])
                    mValue = Do.ExpandArg(mValue, mFilePath)
                else:
                    print 'DoM.py No primary file, could not set directory'
                    break
            else:
                os.chdir(mValue)
            
        elif mKey == '-e':               # Set environment variable
            setenviron(mValue, mFilePath)

        elif mKey == '-h':
            print mHelpText
            mHelp = True

        elif mKey == '-l':
            mList = True

        elif mKey == '-m':               #
            mSearchPath = mValue

        elif mKey == '-v':
            mVerbose = True
                
    if len(mArgs) > 2:
        mFilePath = os.path.abspath(mArgs[0])
        

        mSection = mArgs[1]
        if mSection.find('{'):
            mSection = Do.Expand(mSection, mFilePath)

        mKey = mArgs[2]
        if mKey.find('{'):
            mArgs[2] = Do.Expand(mKey, mFilePath)
        mArgs[2] = mArgs[2].replace('_', ' ')

                
        if mSearchPath.find('{') >= 0:
            mSearchPath = Do2.ExpandArg(mSearchPath, mFilePath, '')
            if mSearchPath[0] == '"':
                mSearchPath = mSearchPath[1:-1]
        mSearchPath = os.path.abspath(mSearchPath)

        if mHelp:
            print 'DoM.py Default menu:   ', mSearchPath
            print 'DoM.py Default section:', mSection
        elif mList:
            Expand('???', mFilePath, mSearchPath, mSection, mVerbose, mList)
        else:
            submitnow(mArgs[2:], mFilePath, mSearchPath, mSection, mVerbose)
            
    elif mHelp == False:
        print 'DoM.pyCommand and/or file path missing'
    

    

Complicated commands can be saved in a QEditor style menu file. This is especially convenient when QEditor or one of my button bar programs is also being used. These use the same menu files. Executing a command from a file type association can reduce redundancy and increase flexibiity. Normal associations can insert only the file name of the clicked file. DoM can generate names for multiple files and search folder trees for suilable files using Do2.py.

'DoM.py -m {p}\menus.ini;{o}\menus.ini StoryWriter.py Fantasy Emarald' will check for menus.ini first in the folder containing StoryWriter then in the base Python menu c:\source\python for a menus.ini file. It will use the first it finds. It will scan the file for a section whos name starts with 'Fantasy' and a command with a name starting with 'Emarald'. When found the command is expanded and executed.

Does not recognize {open} style macros which access the Win32 registry as do my button bar programs. It also ignores the special commands ;#set, ;#cd, ;#button and ;#label used by the button bar programs. Otherwise menu files should be useable by either program.