How to create a global hotkey on Windows with 3 arguments? - python

Just like Ctl, Alt + delete
I want to write a program, which uses global hotkeys with 3 or more arguments in python. The assigned function should only perform when I press all three keys on my keyboard. For example alt, windows and F3.
win32con.VK_F3, win32con.MOD_WIN, win32con.VK_F5
This is the current program I want to run, however its output is:
Traceback (most recent call last):
File "C:\Python32\Syntax\hot keys\hotkeys2.py", line 41, in <module>
for id, (vk, modifiers) in HOTKEYS.items ():
ValueError: too many values to unpack (expected 2)
The Program:
import os
import sys
import ctypes
from ctypes import wintypes
import win32con
byref = ctypes.byref
user32 = ctypes.windll.user32
HOTKEYS = {
1 : (win32con.VK_F3, win32con.MOD_WIN, win32con.VK_F5),
2 : (win32con.VK_F4, win32con.MOD_WIN),
3 : (win32con.VK_F2, win32con.MOD_WIN)
}
def handle_win_f3 ():
#os.startfile (os.environ['TEMP'])
print ("Hello WOrld! F3")
def handle_win_f4 ():
#user32.PostQuitMessage (0)
print ("Hello WOrld! F4")
def handle_win_f1_escape ():
print("exit")
sys.exit()
HOTKEY_ACTIONS = {
1 : handle_win_f3,
2 : handle_win_f4,
3 : handle_win_f1_escape
}
for id, (vk, modifiers) in HOTKEYS.items ():
print ("Registering id", id, "for key", vk)
if not user32.RegisterHotKey (None, id, modifiers, vk):
print ("Unable to register id", id)
try:
msg = wintypes.MSG ()
while user32.GetMessageA (byref (msg), None, 0, 0) != 0:
if msg.message == win32con.WM_HOTKEY:
action_to_take = HOTKEY_ACTIONS.get (msg.wParam)
#print(" msg.message == win32con.WM_HOTKEY:")
if action_to_take:
action_to_take ()
user32.TranslateMessage (byref (msg))
user32.DispatchMessageA (byref (msg))
finally:
for id in HOTKEYS.keys ():
user32.UnregisterHotKey (None, id)
print("user32.UnregisterHotKey (None, id)")
Registering 3 hotkeys? Possible?
Explains how one can use assign one key that needs to be pressed and then if two of which either needs to be pressed. However I won’t that the function only performs when all there are pressed simultaneously. I took

For anyone interested in details and a more elaborate example regarding this topic, I recently wrote a short program demonstrating the hotkey functions provided by win32con. The program allows you to specify and test any hotkeys you want via command line:
Usage: python.exe hotkey.py MOD_ALT VK_UP -> test hotkey ALT + Arrow UP
# Imports
import win32con
import ctypes, ctypes.wintypes
import sys
#
# Functions
#
def dispatch_hotkey(msg):
mod = msg.lParam & 0b1111111111111111
key = msg.lParam >> 16
bit = bin(msg.lParam)[2:]
print("\n*** Received hotkey message (wParam: %d, lParam: %d)" % (msg.wParam, msg.lParam))
print("lParam bitmap: %s" % bit)
print("lParam low-word (modifier): %d, high-word (key): %d" % (mod, key))
print("-> Hotkey %s with modifier %s detected\n" % (keys[key], mods[mod]))
#
# Main
#
# Build translation maps (virtual key codes / modifiers to string)
# Note: exec() is a hack and should not be used in real programs!!
print("\n*** Building translation maps")
mods = {}
keys = {}
for item in dir(win32con):
if item.startswith("MOD_"):
exec("mods[item] = win32con." + item)
exec("mods[win32con." + item + "] = '" + item + "'")
if item.startswith("VK_"):
exec("keys[item] = win32con." + item)
exec("keys[win32con." + item + "] = '" + item + "'")
# Process command line
print("\n*** Processing command line")
mod = "MOD_WIN"
key = "VK_ESCAPE"
for param in sys.argv:
if param.startswith("MOD_"):
if param in mods: mod = param
else: print("\nInvalid modifier specified (%s). Using default.\n-> Use '--list-mods' for a list of valid modifiers." % param)
if param.startswith("VK_"):
if param in keys: key = param
else: print("\nInvalid key specified (%s). Using default.\n-> Use '--list-keys' for a list of valid keys." % param)
if "--list-mods" in sys.argv:
print("\nAvailable modifiers:")
for item in dir(win32con):
if item.startswith("MOD_"): sys.stdout.write(item + ", ")
print("\b\b ")
if "--list-keys" in sys.argv:
print("\nAvailable keys:")
for item in dir(win32con):
if item.startswith("VK_"): sys.stdout.write(item + ", ")
print("\b\b ")
# Register hotkey
print("\n*** Registering global hotkey (modifier: %s, key: %s)" % (mod, key))
ctypes.windll.user32.RegisterHotKey(None, 1, mods[mod], keys[key])
# Wait for hotkey to be triggered
print("\n*** Waiting for hotkey message...")
try:
msg = ctypes.wintypes.MSG()
while ctypes.windll.user32.GetMessageA(ctypes.byref(msg), None, 0, 0) != 0:
if msg.message == win32con.WM_HOTKEY:
dispatch_hotkey(msg)
break
ctypes.windll.user32.TranslateMessage(ctypes.byref(msg))
ctypes.windll.user32.DispatchMessageA(ctypes.byref(msg))
# Unregister hotkey
finally:
ctypes.windll.user32.UnregisterHotKey(None, 1)
Please note that this program is intended for demonstration purposes only as parts of the program (e.g. exec function) should not be used in production environments. Also note that with this approach, you won't be able to override built-in hotkeys like WIN + E etc., they will simply be ignored and still perform the built-in functions (e.g. opening Explorer).
Another Way (courtesy of #martineau)
Here's how to build the translation maps without using exec():
print("\n*** Building translation maps")
mods = {}
keys = {}
for item, value in vars(win32con).items():
if item.startswith("MOD_"):
mods[item] = value
mods[value] = item
elif item.startswith("VK_"):
keys[item] = value
keys[value] = item

For starters, if you wanted alt, windows and F3, wouldn't you need to use win32con.VK_F3, win32con.MOD_ALT, win32con.MOD_WIN for the HOTKEYS entry?
However, it doesn't really make sense to say press F3 with modifiers of the Win and F5 key.
The error on the line:
for id, (vk, modifiers) in HOTKEYS.items ():
is because the value of each dictionary entry is a variable length tuple. Here's a way to handle that which also bitwise OR s all the modifier values together in preparation for passing them as a single argument to RegisterHotKey().
from functools import reduce
for id, values in HOTKEYS.items ():
vk, modifiers = values[0], reduce (lambda x, y: x | y, values[1:])
print ("Registering id", id, "for key", vk)
if not user32.RegisterHotKey (None, id, modifiers, vk):
print ("Unable to register id", id)
It would have been easier to work on your problem if your code was indented properly and followed the PEP 8 -- Style Guide for Python Code recommendations. Please consider doing so in the future.

Related

Identify Linux passwd file

I need help in writing a function (python preferably) to identify if a file is /etc/passwd or etc/shadow. So far I have tried using print(pw.getpwall()) but this reads the file from the os env. I need a library that takes input and can tell if a file is passwd/shadow file or not
passwd and shadow file format differs.
You can write a short function or class. First iteration would be:
Find root user, almost 100% true that root is the first entry
Check 2nd, 6th and 7th column (separator is : sign)
If 2nd is x and 6th is /root and 7th is /bin/*sh then it is a password file almost in 100%
If 2nd is a salt and hash (format: $salt$hash) and 6th is a number and 7th is empy then it is a shadow file almost in 100%
Naturally there could be problems:
Linux is configured not to use shadow file. In this case pasword file 2nd column contains the password
Linux is configured not to use salt (I guess is it possible or not)
Please check manuals: man 5 passwd and man 5 shadow
EDIT, 2020-04-24:
Here is the my corrected pwd.py:
#!/usr/bin/env python3
import os
import sys
passwd_file=('./passwd')
# path conversion handlers
def __nullpathconv(path):
return path
def __unixpathconv(path):
return path
# decide what field separator we can try to use - Unix standard, with
# the platform's path separator as an option. No special field conversion
# handler is required when using the platform's path separator as field
# separator, but are required for the home directory and shell fields when
# using the standard Unix (":") field separator.
__field_sep = {':': __unixpathconv}
if os.pathsep:
if os.pathsep != ':':
__field_sep[os.pathsep] = __nullpathconv
# helper routine to identify which separator character is in use
def __get_field_sep(record):
fs = None
for c in list(__field_sep.keys()):
# there should be 6 delimiter characters (for 7 fields)
if record.count(c) == 6:
fs = c
break
if fs:
return fs
else:
raise KeyError
# class to match the new record field name accessors.
# the resulting object is intended to behave like a read-only tuple,
# with each member also accessible by a field name.
class Passwd:
def __init__(self, name, passwd, uid, gid, gecos, dir, shell):
self.__dict__['pw_name'] = name
self.__dict__['pw_passwd'] = passwd
self.__dict__['pw_uid'] = uid
self.__dict__['pw_gid'] = gid
self.__dict__['pw_gecos'] = gecos
self.__dict__['pw_dir'] = dir
self.__dict__['pw_shell'] = shell
self.__dict__['_record'] = (self.pw_name, self.pw_passwd,
self.pw_uid, self.pw_gid,
self.pw_gecos, self.pw_dir,
self.pw_shell)
def __len__(self):
return 7
def __getitem__(self, key):
return self._record[key]
def __setattr__(self, name, value):
raise AttributeError('attribute read-only: %s' % name)
def __repr__(self):
return str(self._record)
def __cmp__(self, other):
this = str(self._record)
if this == other:
return 0
elif this < other:
return -1
else:
return 1
# read the whole file, parsing each entry into tuple form
# with dictionaries to speed recall by UID or passwd name
def __read_passwd_file():
if passwd_file:
passwd = open(passwd_file, 'r')
else:
raise KeyError
uidx = {}
namx = {}
sep = None
while 1:
entry = passwd.readline().strip()
if len(entry) > 6:
if sep is None:
sep = __get_field_sep(entry)
fields = entry.split(sep)
for i in (2, 3):
fields[i] = int(fields[i])
for i in (5, 6):
fields[i] = __field_sep[sep](fields[i])
record = Passwd(*fields)
if fields[2] not in uidx:
uidx[fields[2]] = record
if fields[0] not in namx:
namx[fields[0]] = record
elif len(entry) > 0:
pass # skip empty or malformed records
else:
break
passwd.close()
if len(uidx) == 0:
raise KeyError
return (uidx, namx)
# return the passwd database entry by UID
def getpwuid(uid):
u, n = __read_passwd_file()
return u[uid]
# return the passwd database entry by passwd name
def getpwnam(name):
u, n = __read_passwd_file()
return n[name]
# return all the passwd database entries
def getpwall():
u, n = __read_passwd_file()
return list(n.values())
# test harness
if __name__ == '__main__':
print(getpwall())

Query a key press using ctypes

I'd like to query each key of a keyboard without using win32api. I have it working using win32api.GetAsyncKeyState(key), but I'd also like to add support for if the module is not installed.
So far I've found one piece of fully working code, though it seems a bit heavyweight as it'd require its own thread, and would need over 1600 separate functions as I want to catch each key no matter of modifiers (there are 14 possible combinations per key).
Here is the code I found, would anyone be able to either suggest an alternative or how to get around the modifier problem?
import ctypes
import ctypes.wintypes
import win32con
class GlobalHotKeys(object):
"""
Register a key using the register() method, or using the #register decorator
Use listen() to start the message pump
Example:
from globalhotkeys import GlobalHotKeys
#GlobalHotKeys.register(GlobalHotKeys.VK_F1)
def hello_world():
print 'Hello World'
GlobalHotKeys.listen()
"""
key_mapping = []
user32 = ctypes.windll.user32
MOD_ALT = win32con.MOD_ALT
MOD_CTRL = win32con.MOD_CONTROL
MOD_CONTROL = win32con.MOD_CONTROL
MOD_SHIFT = win32con.MOD_SHIFT
MOD_WIN = win32con.MOD_WIN
#classmethod
def register(cls, vk, modifier=0, func=None):
"""
vk is a windows virtual key code
- can use ord('X') for A-Z, and 0-1 (note uppercase letter only)
- or win32con.VK_* constants
- for full list of VKs see: http://msdn.microsoft.com/en-us/library/dd375731.aspx
modifier is a win32con.MOD_* constant
func is the function to run. If False then break out of the message loop
"""
# Called as a decorator?
if func is None:
def register_decorator(f):
cls.register(vk, modifier, f)
return f
return register_decorator
else:
cls.key_mapping.append((vk, modifier, func))
#classmethod
def listen(cls):
"""
Start the message pump
"""
for index, (vk, modifiers, func) in enumerate(cls.key_mapping):
if not cls.user32.RegisterHotKey(None, index, modifiers, vk):
raise Exception('Unable to register hot key: ' + str(vk) + ' error code is: ' + str(ctypes.windll.kernel32.GetLastError()))
try:
msg = ctypes.wintypes.MSG()
i = 0
while cls.user32.GetMessageA(ctypes.byref(msg), None, 0, 0) != 0:
if msg.message == win32con.WM_HOTKEY:
(vk, modifiers, func) = cls.key_mapping[msg.wParam]
if not func:
break
func()
cls.user32.TranslateMessage(ctypes.byref(msg))
cls.user32.DispatchMessageA(ctypes.byref(msg))
finally:
for index, (vk, modifiers, func) in enumerate(cls.key_mapping):
cls.user32.UnregisterHotKey(None, index)
#classmethod
def _include_defined_vks(cls):
for item in win32con.__dict__:
item = str(item)
if item[:3] == 'VK_':
setattr(cls, item, win32con.__dict__[item])
#classmethod
def _include_alpha_numeric_vks(cls):
for key_code in (list (range(ord('A'), ord('Z') + 1)) + list(range(ord('0'), ord('9') + 1)) ):
setattr(cls, 'VK_' + chr(key_code), key_code)
GlobalHotKeys._include_defined_vks()
GlobalHotKeys._include_alpha_numeric_vks()
This is an example of how it'd be used to read a:
#GlobalHotKeys.register(ord('A'))
def a():
print 'a'
#GlobalHotKeys.register(ord('A'), GlobalHotKeys.MOD_SHIFT)
def a_shift():
print 'shift + a'
#GlobalHotKeys.register(ord('A'), GlobalHotKeys.MOD_CONTROL | GlobalHotKeys.MOD_SHIFT)
def a_ctrl_shift():
print 'ctrl + shift + a'
...
GlobalHotKeys.listen()
Turned out to be an extremely simple answer, I finally stumbled across it when trying to read the microsoft info for the GetKeyState function.
ctypes.windll.user32.GetKeyState(key)
The state will either be 0 or 1 when not pressed, and increase to something like 60000 when pressed, so to get a True/False result, checking for > 1 seems to do the trick.
GetAsyncKeyState also kinda works, but sometimes results in a negative number, and sometimes doesn't, so I thought it'd be best using the alternative.

Modifying Code of Function at Runtime

I am trying to write a decorator that adds verbose logging to a function via a decorator (a method would be nice too, but I haven't tried that yet). The motivation behind this is that patching in a one-line add_logs decorator call into a box in production is much easier (and safer) than adding 100 debug lines.
Ex:
def hey(name):
print("Hi " + name)
t = 1 + 1
if t > 6:
t = t + 1
print("I was bigger")
else:
print("I was not.")
print("t = ", t)
return t
I'd like to make a decorator that will transform this into code that does this:
def hey(name):
print("line 1")
print("Hi " + name)
print("line 2")
t = 1 + 1
print("line 3")
if t > 6:
print("line 4")
t = t + 1
print("line 5")
print("I was bigger")
else:
print("line 6")
print("I was not.")
print("line 7")
print("t = ", t)
print("line 8")
return t
What I've got so far:
import inspect, ast
import itertools
import imp
def log_maker():
line_num = 1
while True:
yield ast.parse('print("line {line_num}")'.format(line_num=line_num)).body[0]
line_num = line_num + 1
def add_logs(function):
def dummy_function(*args, **kwargs):
pass
lines = inspect.getsourcelines(function)
code = "".join(lines[0][1:])
ast_tree = ast.parse(code)
body = ast_tree.body[0].body
#I realize this doesn't do exactly what I want.
#(It doesn't add debug lines inside the if statement)
#Once I get it almost working, I will rewrite this
#to use something like node visitors
body = list(itertools.chain(*zip(log_maker(), body)))
ast_tree.body[0].body = body
fix_line_nums(ast_tree)
code = compile(ast_tree,"<string>", mode='exec')
dummy_function.__code__ = code
return dummy_function
def fix_line_nums(node):
if hasattr(node, "body"):
for index, child in enumerate(node.body):
if hasattr(child, "lineno"):
if index == 0:
if hasattr(node, "lineno"):
child.lineno = node.lineno + 1
else:
# Hopefully this only happens if the parent is a module...
child.lineno = 1
else:
child.lineno = node.body[index - 1].lineno + 1
fix_line_nums(child)
#add_logs
def hey(name):
print("Hi " + name)
t = 1 + 1
if t > 6:
t = t + 1
print("I was bigger")
else:
print("I was not.")
print("t = ", t)
return t
if __name__ == "__main__":
print(hey("mark"))
print(hey)
This produces this error:
Traceback (most recent call last):
File "so.py", line 76, in <module>
print(hey("mark"))
TypeError: <module>() takes no arguments (1 given)
which makes sense because compile creates a module and of course modules are not callables. I've tried a hundred different ways of making this work at this point but can't come up with a working solution. Any recommendations? Am I going about this the wrong way?
(I haven't been able to find a tutorial for the ast module that actually modifies code at runtime like this. A pointer to such a tutorial would be helpful as well)
Note: I am presently testing this on CPython 3.2, but a 2.6/3.3_and_up solution would be appreciated. Currently the behavior is the same on 2.7 and 3.3.
When you compile the source, you get a code object representing a module, not a function. Substituting this code object into an existing function won't work, because it's not a function code object, it's a module code object. It's still a code object, though, not a real module, you so can't just do hey.hey to get the function from it.
Instead, as described in this answer, you need to use exec to execute the module's code, store the resulting objects in a dictionary, and extract the one you want. What you could do, roughly, is:
code = compile(ast_tree,"<string>", mode='exec')
mod = {}
exec(code, mod)
Now mod['hey'] is the modified function. You could reassign the global hey to this, or replace its code object.
I am not sure if what you're doing with the AST is exactly right, but you will need to do the above at any rate, and if there are problems in the AST manipulation, doing this will get you to a point where you can start to debug them.
It looks like you're trying to hackily implement a trace function. Can I suggest using sys.settrace to accomplish that in a more reusable fashion?
import sys
def trace(f):
_counter = [0] #in py3, we can use `nonlocal`, but this is compatible with py2
def _tracer(frame, event, arg):
if event == 'line':
_counter[0] += 1
print('line {}'.format(_counter[0]))
elif event == 'return': #we're done here, reset the counter
_counter[0] = 0
return _tracer
def _inner(*args, **kwargs):
try:
sys.settrace(_tracer)
f(*args, **kwargs)
finally:
sys.settrace(None)
return _inner
#trace
def hey(name):
print("Hi " + name)
t = 1 + 1
if t > 6:
t = t + 1
print("I was bigger")
else:
print("I was not.")
print("t = ", t)
return t
hey('bob')
Output:
$ python3 test.py
line 1
Hi bob
line 2
line 3
line 4
I was not.
line 5
t = 2
line 6
Note that the semantics of this are slightly different than in your implementation; the if branches not exercised by your code, for example, are not counted.
This ends up being less fragile - you're not actually modifying the code of the functions you're decorating - and has extra utility. The trace function gives you access to the frame object before executing each line of code, so you're free to log locals/globals (or do some dodgy injection stuff, if you're so inclined).
When you call inspect.getsource() with a decorated function, you also get the decorator, which, in your case, gets called recursively (just twice, and the second time it produces an OSError).
You can use this workaround to remove #add_logs line from the source:
lines = inspect.getsourcelines(function)
code = "".join(lines[0][1:])
EDIT:
It looks like your problem is that your dummy_function doesn't take arguments:
>>> print(dummy_function.__code__.co_argcount)
0
>>> print(dummy_function.__code__.co_varnames)
()
Whereas your original function does:
>>> print(hey.__code__.co_argcount)
1
>>> print(hey.__code__.co_varnames)
('name')
EDIT:
You're right about the code object being returned as a module. As pointed in another answer, you have to execute the this object and then, assign the resulting function (identifiable by function.__name__) to dummy_function.
Like so:
code = compile(ast_tree,"<string>", mode='exec')
mod = {}
exec(code, mod)
dummy_function = mod[function.__name__]
return dummy_function
Then:
>>> print(hey('you'))
line 1
Hi you
line 2
line 3
I was not.
line 4
t = 2
line 5
2

Python Code List Defined

This is part of my python code. I keep having a problem with my lists and it says it is not defined. Can you help or explain to me how to fix that?
import maya.cmds as cmds
def changeXtransformVal(percentage=1.0, myList = myList):
"""
Changes the value of each transform in the scene by a percentange.
Parameters:
percentage - Percentange to change each transform's value. Default value is 1.
Returns:
Nothing.
"""
# The ls command is the list command. It is used to list various nodes
# in the current scene. You can also use it to list selected nodes.
transformInScene = cmds.ls(type='transform')
found = False
for thisTransform in transformInScene:
if thisTransform not in ['front','persp','side','top']:
found = True
break
else:
found = False
if found == False:
sphere1 = cmds.polySphere()[0]
cmds.xform(sphere1, t = (0.5, 0.5, 0.5))
transformInScene = cmds.ls(type='transform')
# If there are no transforms in the scene, there is no point running this script
if not transformInScene:
raise RuntimeError, 'There are no transforms in the scene!'
badAttrs = list('front','persp','side','top')
# Loop through each transform
for thisTransform in transformInScene:
if thisTransform not in ['front','persp','side','top']:
allAttrs = cmds.listAttr(thisTransform, keyable=True, scalar=True)
allAttrs = [ i for i in badAttrs if i != "visibility" ]
print allAttrs
for attr in myList:
if attr in allAttrs:
currentVal = cmds.getAttr( thisTransform + "." + attr )
newVal = currentVal * percentage
cmds.setAttr(thisTransform + "." + attr, newval)
print "Changed %s. %s from %s to %s" % (thisTransform,attr,currentVal,newVal)
else:
badAttrs.append(attr)
if badAttrs:
print "These attributes %s are not valid" % str()
myList = ("translateX", "translateY", "translateZ", "scaleX" )
changeXtransformVal(percentage=2.0, myList = myList)
You are giving your function default parameters:
def changeXtransformVal(percentage=1.0, myList = myList):
These are evaluated when the function object is created, but at that time you have no myList object yet. You don't have to give these parameters default values, and you don't appear to use the values you do give.
If you don't need default values, use:
def changeXtransformVal(percentage, myList):
If you do need a default value for percentage, move the parameter to the end:
def changeXtransformVal(myList, percentage=1.0):
and adjust your function call accordingly:
changeXtransformVal(myList, percentage=2.0)
Note that you don't have to name parameters either; positional parameters work by position, and in this case percentage= is optional too.

What is the pythonic way to implement a parse tree for custom format?

I have a project that has a non-standard file format something like:
var foo = 5
load 'filename.txt'
var bar = 6
list baz = [1, 2, 3, 4]
And I want to parse this into a data structure much like BeautifulSoup does. But this format isn't supported by BeautifulSoup. What is the pythonic way to build a parse tree so that I can modify the values and re-write it out? In the end I would like to do something like:
data = parse_file('file.txt')
data.foo = data.foo * 2
data.write_file('file_new.txt')
Here is a solution using pyparsing... it works in your case. Beware that i'm not an expert therefore depending on your standards the code could be ugly... cheers
class ConfigFile (dict):
"""
Configuration file data
"""
def __init__ (self, filename):
"""
Parses config file.
"""
from pyparsing import Suppress, Word, alphas, alphanums, nums, \
delimitedList, restOfLine, printables, ZeroOrMore, Group, \
Combine
equal = Suppress ("=")
lbrack = Suppress ("[")
rbrack = Suppress ("]")
delim = Suppress ("'")
string = Word (printables, excludeChars = "'")
identifier = Word (alphas, alphanums + '_')
integer = Word (nums).setParseAction (lambda t: int (t[0]))
real = Combine( Word(nums) + '.' + Word(nums) ).setParseAction (lambda t: float(t[0]))
value = real | integer
var_kwd = Suppress ("var")
load_kwd = Suppress ("load")
list_kwd = Suppress ("list")
var_stm = Group (var_kwd + identifier + equal + value +
restOfLine.suppress ()).setParseAction (
lambda tok: tok[0].insert(len(tok[0]), 0))
load_stm = Group (load_kwd + delim + string + delim +
restOfLine.suppress ()).setParseAction (
lambda tok: tok[0].insert(len(tok[0]), 1))
list_stm = Group (list_kwd + identifier + equal + lbrack +
Group ( delimitedList (value, ",") ) +
rbrack + restOfLine.suppress ()).setParseAction (
lambda tok: tok[0].insert(len(tok[0]), 2))
cnf_file = ZeroOrMore (var_stm | load_stm | list_stm)
lines = cnf_file.parseFile (filename)
self._lines = []
for line in lines:
self._lines.append ((line[-1], line[0]))
if line[-1] != 1: dict.__setitem__(self, line[0], line[1])
self.__initialized = True
# after initialisation, setting attributes is the same as setting an item
def __getattr__ (self, key):
try:
return dict.__getitem__ (self, key)
except KeyError:
return None
def __setattr__ (self, key, value):
"""Maps attributes to values. Only if we are initialised"""
# this test allows attributes to be set in the __init__ method
if not self.__dict__.has_key ('_ConfigFile__initialized'):
return dict.__setattr__(self, key, value)
# any normal attributes are handled normally
elif self.__dict__.has_key (key):
dict.__setattr__(self, key, value)
# takes care of including new 'load' statements
elif key == 'load':
if not isinstance (value, str):
raise ValueError, "Invalid data type"
self._lines.append ((1, value))
# this is called when setting new attributes after __init__
else:
if not isinstance (value, int) and \
not isinstance (value, float) and \
not isinstance (value, list):
raise ValueError, "Invalid data type"
if dict.has_key (self, key):
if type(dict.__getitem__(self, key)) != type (value):
raise ValueError, "Cannot modify data type."
elif not isinstance (value, list): self._lines.append ((0, key))
else: self._lines.append ((2, key))
dict.__setitem__(self, key, value)
def Write (self, filename):
"""
Write config file.
"""
fid = open (filename, 'w')
for d in self._lines:
if d[0] == 0: fid.write ("var %s = %s\n" % (d[1], str(dict.__getitem__(self, d[1]))))
elif d[0] == 1: fid.write ("file '%s'\n" % (d[1]))
else: fid.write ("list %s = %s\n" % (d[1], str(dict.__getitem__(self, d[1]))))
if __name__ == "__main__":
input="""var foo = 5
load 'filename.txt'
var bar = 6
list baz = [1, 2, 3, 4]"""
file ("test.txt", 'w').write (input)
config = ConfigFile ("test.txt")
# Modify existent items
config.foo = config.foo * 2
# Add new items
config.foo2 = [4,5,6,7]
config.foo3 = 12.3456
config.load = 'filenameX.txt'
config.load = 'filenameXX.txt'
config.Write ("test_new.txt")
EDIT
I have modified the class to use
__getitem__, __setitem__
methods to mimic the 'access to member' syntax to parsed items as required by our poster. Enjoy!
PS
Overloading of the
__setitem__
method should be done with care to avoid interferences between setting of 'normal' attributes (class members) and the parsed items (that are accesses like attributes). The code is now fixed to avoid these problems. See the following reference http://code.activestate.com/recipes/389916/ for more info. It was funny to discover this!
What you have is a custom language you need to parse.
Use one of the many existing parsing libraries for Python. I personally recommend PLY. Alternatively, Pyparsing is also good and widely used & supported.
If your language is relatively simple, you can also implement a hand-written parser. Here is an example

Categories