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

This program extracts, expands and executes a command stored in an arbitrary file usually the primary file. This is similar to the #! facility in UNIX.

Python, 320 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
#!Test c:\bin\messagebox.exe Display for {a} %xyz% {} {xyz} {} and {p}
##              #!Test2 c:\bin\messagebox.exe Display for {a} and {} with {} and {p}
#!Edit {o}\qeditor.exe {a}   !!
#       Run the #!Inst command
#! c:\bin\qeditor.exe   "{a}"        !!
#!py  c:\bin\messagebox.exe    "{a}" is a python program
#!Inst c:\windows\system32\cmd.exe /C copy "{a}" "{i}\{f}"     !!
#!BadI c:\windows\system32\cmd.exe /C copy "{a}" "{i}\{f}"     
#!Run  c:\sys\python27\pythonw.exe "{a}" list of arguments
##  #!OKI c:\windows\system32\cmd.exe /C copy "{a}" "{t}\{f}"     !!
#!pynew c:\bin\messagebox.exe Display for {a} new
#!oldpy c:\bin\messagebox.exe Display for "{a}" old

mHelpText = '''
# ----------------------------------------------
# Name: pb
# Description: Expand and execute command from file
## D20H-57 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.

# pb.py -
# Scan file for specified #! keyword then expand and execute command following on line.
# See above for examples.  Scanned lines must be at the beginning of the file with no
# blank or shorx t lines preceeding them.
#
# Usage examples:
#
#[HKEY_CLASSES_ROOT\*\Shell\Runpb]
#@="Run"
#"EditFlags"=hex:01,00,00,00
#[HKEY_CLASSES_ROOT\*\Shell\Runpb\Command]
#@="c:\\sys\\python25\\pythonw.exe c:\\bin\\pb.py \"%1\" #!run     "
#
# Syntax:
#    pb.py [ Options ] <File path> #!<Key> [ <Optional parameter> ... ] 
#
# Options:
#
#    -c <Count>           - Set last line to search for #! default 10
#                           A too short line will also abort scan
#    -d <Directory path>  - Set current directory         
#    -e <Name>=<Value>    - Set environment variable
#    -h                   - Display this text
#    -s <File to search>  - Set name of file containing command, defaults to
#                           primary file named in <File path>
#    -v                   - Run in verbose mode
#    -w <Count>           - Set last column in line to search for #! default 15
#    -x                   - Do not run, list expanded command
'''

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

import subprocess

def FindCommand(pKey, pFilePath, pSearchPath, pCount=10, pDefault='#!', pList=False, pWidth=10, 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 '
    pCount       -- Number of lines at the start of the file to scan
    pDefault     -- Prefix for keys, from UNIX #!
    pList        -- True to display available commands
    pWidth       -- Number of columns at start of line in which key must appear
    '''
    lShortest = 6         # A line shorter than this will abort scan
    
    if not os.path.exists(pSearchPath):
        lCommand = ''
        print 'pb.py Could not find search file', pSearchPath
        
    else:
#       ---- Check allowed key values    
        if pKey == '' or pKey == pDefault:          #   #!
            lKey = pDefault 
        elif pKey.find('{') >= 0:                   #   #!{e}
            lKey = pDefault + Do.Expand(pKey, pFilePath)
            if not lKey.startswith(pDefault):
                lKey = pDefault + lKey
        elif pKey.startswith(pDefault):             #   #!text
            lKey = pKey
        else:
            lKey = pDefault + pKey                  #   text
            lKey = lKey.lower()
#        lKey += ' '                                #   Full key only

#       ---- read maximum scan lines
        lFile = open(pSearchPath, 'r')
        lText = lFile.readlines(pCount)
        lText = lText[0:pCount]                     #???
        lFile.close()
        
        lCommand = ''

#       ---- list available commands
        if pList:
            lText = [x for x in lText if x.find(pDefault) >= 0 ]
            lText = [x for x in lText if x.find(pDefault) < pWidth ]
            print 'Available Commands in', pSearchPath
            for (lCount, lLine) in enumerate(lText):
                lLine = lLine.strip()
                print "%5d: %s" % (lCount, lLine)

#       ---- Scan for selected command
#            Since comments in some languages do not start with first character of key,
#            the key need not appear at start of line but must appear within first pWidth
#            columns to allow for skipping lines with key that are not to be matched
        else:   
            lCount = 0     
            for lLine in lText:
                if len(lLine.strip()) < lShortest:              # blank or short line will stop scan
                    break
                lLine = lLine.lower()
                lPos = lLine.find(lKey.lower()) 
                if pVerbose:
                    lCount += 1
                    print "pb.py %5d: %s" % (lCount, lLine),
                    if lPos >= 0:
                        lAt = lPos + 13
                        print ' ' * lAt + '^'                
                if  lPos >= 0 and lPos < pWidth:  
#                   ---- Full key only                
#                    lCommand = lLine[lPos+len(lKey):]           
#                   ---- Partial keys allowed                     
                    lCommand = lLine[lPos+len(lKey):]          
                    if lCommand[0] == ' ':                 # matched full key
                        lCommand = lCommand.lstrip()
                    else:
                        lPos = lCommand.find(' ')          # matched partial key
                        lCommand = lCommand[lPos:].lstrip()
                        
                    break
    return lCommand[0:-1]


def Expand(pArgs, pFilePath, pSearchPath, pCount, pSep='!!', pDefault='#!', pList=False, pWidth=15, pVerbose=False):
    '''
    Extract command from file and replace all macros
    pArgs        -- Args passed to program except file path
                    #!key and any arguments
    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, extra arguments will not be appended
    pList        -- True to display available commands
    pWidth       -- Number of columns to scan for #!
    '''
#   ---- Find command  
    if len(pArgs[1]) == 0:
        lKey = pDefault 
        lStart = 1
    elif pArgs[1].startswith(pDefault):
        lKey = pArgs[1]
        lStart = 2
    else:
        lKey = pDefault
        lStart = 1
            
    lCommand = FindCommand(lKey, pFilePath, pSearchPath, pCount, pDefault=pDefault, pList=pList, pWidth=pWidth, pVerbose=pVerbose)
    
    if lCommand == '':
        print 'pb.py Command', lKey, 'not found'
   
#   ---- 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 
    if len(lCommand) > 0:
        if len(pArgs) > lStart:
            for lArg in pArgs[lStart:]:
                if lArg.find('{') >= 0:
                    lArg = Do2.ExpandArg(lArg, pFilePath, '')
                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, pCount, pList=False, pExtract=False, pVerbose=False, pWidth=15):
    'Expand and submit command'
    if pVerbose:
        print 'pb.py File path:  ', pFilePath
        print 'pb.py Search path:', pSearchPath
        print 'pb.py Arguments:  ', pArgs
   
    lCommand = Expand(pArgs, pFilePath, pSearchPath, pCount, pList=pList, pWidth=pWidth, 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('{', '%')
    lCommand = lCommand.replace('}', '%')

#   ---- Complete execution
    if pList:                      # List only
        pass
    elif len(lCommand) == 0:       # Expansion failed
        print 'pb.py Expansion failed'
    elif pExtract:                 # Expand command only
        print lCommand
    else:                          # Submit command
        lCommand = '"' + lCommand + '"'
        if pVerbose:
            print 'pb.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] = ''

#sys.argv.extend(   [ 'c:\\source\\python\\projects\\program execution\\pb.py', '#!Inst', 'A', '{p}'  ] )
#sys.argv.extend(   [ 'c:\\source\\python\\projects\\program execution\\pb.py', '#!BadI', 'A', '{p}'  ] )
#sys.argv.extend(   [ 'c:\\source\\python\\projects\\program execution\\pb.py', '#!OKI',  'A', '{p}'  ] )
#sys.argv.extend(   [ 'c:\\source\\python\\projects\\program execution\\pb.py', '#!Edit', 'A', '{p}'  ] )

if __name__ == '__main__':
    (mOptions, mArgs) = getopt.getopt(sys.argv[1:], 'c:d:e:hls:vx')
    if len(mArgs) > 1:

#       ---- Set defaults
        mFilePath = os.path.abspath(mArgs[0])
        mArgs[1] = mArgs[1].replace('_', ' ')
        mSearchPath = mFilePath
        mCount = 10
        mList = False
        mExtract = False
        mVerbose = False
        mWidth = 15
        mHelp = False

#       ---- Scan option list
        for (mKey, mValue) in mOptions:
            if mKey == '-d':                 # Set current directory
                if mValue.find('{') >= 0:
                    mValue = Do.Expand(mValue, mFilePath)
                os.chdir(mValue)
                
            elif mKey == '-e':               # Set environment variable
                setenviron(mValue, mFilePath)

            elif mKey == '-c':               # Set last line to search for #!
                try:
                    mCount = int(mValue)
                except Exception, e:
                    mCount = 10
                    
            elif mKey == '-s':               # Set search path
                mSearchPath = mValue
                if mValue.find('{') >= 0:
                    mSearchPath = Do2.ExpandArg(mSearchPath, mFilePath, '')
                    if mSearchPath[0] == '"':
                        mSearchPath = mSearchPath[1:-1]
                mSearchPath = os.path.abspath(mSearchPath)

            elif mKey == '-h':               # Display help only
                mHelp = True
                
            elif mKey == '-l':               # Display available commands
                mList = True

            elif mKey == '-x':               # List expanded command only
                mExtract = True

            elif mKey == '-v':               # Run in verbose mode
                mVerbose = True

            elif mKey == '-w':               # Set last column in file to search for #!
                try:
                    mWidth = int(mValue)
                except Exception, e:
                    mWidth = 10

        if mHelp:
            print mHelpText
            print 'pb.py Search file:', mSearchPath
            print 'pb.py Width:      ', mWidth
            print 'pb.py Lines:      ', mCount
            mList = True
        else:
            submitnow(mArgs, mFilePath, mSearchPath, mCount, mList, mExtract, mVerbose, mWidth)
    else:
        print 'pb.py Command and/or file path missing'
    

    

This program scans the head of an arbitrary file looking for a line containing a specified key, such as #!test. When found the command is expanded using the primary path and arguments from the command line and executed. The command 'pb.py mybest.py #!test1' will scan mybest.py for a line containing #!test1. When found it will be expanded and executed. An example set of commands can be found at the top of the program.

Two similar programs are DoCommand, recipe 577441 and DoM recipe 577453 which extract commands from the Windows registry and from a QEditor style menu file.