Package libxyz :: Package core :: Module dsl
[hide private]
[frames] | no frames]

Source Code for Module libxyz.core.dsl

  1  #-*- coding: utf8 -* 
  2  # 
  3  # Max E. Kuznecov ~syhpoon <mek@mek.uz.ua> 2008-2009 
  4  # 
  5  # This file is part of XYZCommander. 
  6  # XYZCommander is free software: you can redistribute it and/or modify 
  7  # it under the terms of the GNU Lesser Public License as published by 
  8  # the Free Software Foundation, either version 3 of the License, or 
  9  # (at your option) any later version. 
 10  # XYZCommander is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 13  # GNU Lesser Public License for more details. 
 14  # You should have received a copy of the GNU Lesser Public License 
 15  # along with XYZCommander. If not, see <http://www.gnu.org/licenses/>. 
 16   
 17  import sys 
 18  import os 
 19  import traceback 
 20  import __builtin__ 
 21   
 22  from libxyz.core.utils import ustring 
 23  from libxyz.core.utils import is_func 
 24  from libxyz.core.plugins import Namespace 
 25  from libxyz.core import FSRule 
 26   
 27  from libxyz.ui.colors import Palette 
 28  from libxyz.ui import Shortcut 
 29   
 30  from skin import Skin 
 31   
 32  import libxyz.exceptions as ex 
33 34 -def instantiated(func):
35 """ 36 Ensure the class has been instantiated 37 """ 38 39 def wrap(cls, *args, **kwargs): 40 if cls._instance is None: 41 error(_(u"Class must be instantiated first!")) 42 else: 43 return func(cls, *args, **kwargs)
44 45 wrap.__doc__ = func.__doc__ 46 47 return wrap 48
49 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 50 51 -def error(msg, trace=True):
52 if trace and hasattr(__builtin__, "xyzlog"): 53 xyzlog.debug(ustring(traceback.format_exc())) 54 raise ex.DSLError(_(u"DSL Error: %s") % msg)
55
56 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 57 58 -class XYZ(object):
59 """ 60 XYZ DSL implementation object 61 """ 62 63 api = ["let", 64 "val", 65 "section", 66 "unlet", 67 "load", 68 "bind", 69 "exec_file", 70 "kbd", 71 "action", 72 "macro", 73 "call", 74 "env", 75 "shell", 76 "alias", 77 "plugins_on", 78 "plugins_off", 79 "plugin_conf", 80 "icmd", 81 "prefix", 82 "help", 83 "vfs", 84 "vfs_path", 85 "hook", 86 "unhook", 87 "fsrule", 88 "palette", 89 "skin" 90 ] 91 92 EVENT_CONF_UPDATE = u"event:conf_update" 93 94 macros = {} 95 96 _instance = None 97 _env = {} 98
99 - def __new__(cls, xyz):
100 if cls._instance is not None: 101 return cls._instance 102 103 # Else init singleton 104 cls.xyz = xyz 105 cls._instance = cls 106 107 cls._env = {"XYZ": cls} 108 cls._env.update(dict([(ff, getattr(cls, ff)) for ff in cls.api])) 109 110 # Init macros 111 cls.macros = {} 112 cls.init_macros() 113 return cls
114 115 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 116 117 @classmethod
118 - def init_macros(cls):
119 cls.macros["ACT_CWD"] = lambda: cls.xyz.pm.from_load(":sys:panel", 120 "cwd")() 121 cls.macros["INACT_CWD"] = lambda: cls.xyz.pm.from_load(":sys:panel", 122 "cwd")(False) 123 124 cls.macros["ACT_PATH"] = lambda: \ 125 cls.xyz.pm.from_load(":sys:panel", 126 "get_selected")().path 127 128 cls.macros["INACT_PATH"] = lambda: \ 129 cls.xyz.pm.from_load(":sys:panel", 130 "get_selected" 131 )(False).path 132 cls.macros["ACT_BASE"] = lambda: \ 133 os.path.dirname(cls.macros["ACT_CWD"]()) 134 135 cls.macros["INACT_BASE"] = lambda: \ 136 os.path.dirname(cls.macros["INACT_CWD"]()) 137 138 cls.macros["ACT_TAGGED"] = lambda: [x.full_path for x in 139 cls.xyz.pm.from_load( 140 ":sys:panel", 141 "get_tagged")()] 142 cls.macros["INACT_TAGGED"] = lambda: [x.full_path for x in 143 cls.xyz.pm.from_load( 144 ":sys:panel", 145 "get_tagged")(False)] 146 147 cls.macros["ACT_UNTAGGED"] = lambda: [x.full_path for x in 148 cls.xyz.pm.from_load( 149 ":sys:panel", 150 "get_untagged")()] 151 cls.macros["INACT_UNTAGGED"] = lambda: [x.full_path for x in 152 cls.xyz.pm.from_load( 153 ":sys:panel", 154 "get_untagged")(False)]
155 156 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 157 158 @classmethod
159 - def _clear(cls):
160 cls._instance = None
161 162 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 163 164 @classmethod 165 @instantiated
166 - def let(cls, var, val, sect=u"local"):
167 """ 168 Set variable. 169 Variable will be available in xyz.conf[section][varname] 170 If section is not provided - local will be used 171 """ 172 173 _conf = cls.xyz.conf 174 175 if sect not in _conf: 176 _conf[sect] = {} 177 178 if var in _conf[sect] and isinstance(_conf[sect][var], dict) and \ 179 isinstance(val, dict): 180 # Update rather than overwrite 181 _conf[sect][var].update(val) 182 else: 183 cls.xyz.conf[sect][var] = val 184 185 cls.xyz.hm.dispatch(cls.EVENT_CONF_UPDATE, var, val, sect)
186 187 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 188 189 @classmethod 190 @instantiated
191 - def val(cls, var, sect=u"local"):
192 """ 193 Return variable value or None if undefined 194 """ 195 196 try: 197 return cls.xyz.conf[sect][var] 198 except Exception: 199 return None
200 201 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 202 203 @classmethod 204 @instantiated
205 - def section(cls, sect=u"local"):
206 """ 207 Return whole configuration section contents as a dictionary or None 208 if undefined 209 """ 210 211 try: 212 return cls.xyz.conf[sect] 213 except Exception: 214 return None
215 216 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 217 218 @classmethod 219 @instantiated
220 - def unlet(cls, var, sect=u"local"):
221 """ 222 Unset variable 223 """ 224 225 if var in cls.xyz.conf[sect]: 226 del(cls.xyz.conf[sect])
227 228 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 229 230 @classmethod 231 @instantiated
232 - def load(cls, plugin):
233 """ 234 Load method[s] from plugin 235 """ 236 237 try: 238 cls.xyz.km.load(plugin) 239 except Exception, e: 240 error(_(u"Unable to load plugin %s: %s") % 241 (plugin, unicode(e)))
242 243 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 244 245 @classmethod 246 @instantiated
247 - def bind(cls, method, shortcut, context="DEFAULT"):
248 """ 249 Bind method to shortcut 250 """ 251 252 try: 253 cls.xyz.km.bind(method, shortcut, context=context) 254 except Exception, e: 255 error(_(u"Unable to bind shortcut %s: %s") % (str(shortcut), 256 unicode(e)))
257 258 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 259 260 @classmethod 261 @instantiated
262 - def kbd(cls, *args):
263 """ 264 Create keyboard shortcut 265 """ 266 267 return Shortcut(sc=list(args))
268 269 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 270 271 @classmethod 272 @instantiated
273 - def exec_file(cls, filename):
274 """ 275 Execute DSL in file 276 """ 277 278 f = None 279 280 try: 281 f = open(filename) 282 cls.execute(f.read()) 283 except Exception, e: 284 error(_(u"Unable to execute file: %s") % unicode(e)) 285 286 if f: 287 f.close()
288 289 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 290 291 @classmethod 292 @instantiated
293 - def action(cls, rule, fn):
294 """ 295 Set up an action to be taken upon pressing action key on file 296 """ 297 298 try: 299 cls.xyz.am.register(rule, fn) 300 except Exception, e: 301 error(_(u"Unable to register action: %s") % unicode(e))
302 303 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 304 305 @classmethod 306 @instantiated
307 - def macro(cls, macroname):
308 """ 309 Expand macro name. 310 311 Available macros: 312 * ACT_CWD -- Working directory in active panel 313 * INACT_CWD -- Working directory in inactive panel 314 * ACT_PATH -- Full selected object path in active panel 315 * INACT_PATH -- Full selected object path in inactive panel 316 * ACT_BASE -- Parent directory in active panel 317 * INACT_BASE -- Parent directory in inactive panel 318 * ACT_TAGGED -- List of tagged files in active panel 319 * INACT_TAGGED -- List of tagged files in inactive panel 320 * ACT_UNTAGGED -- List of not tagged files in active panel 321 * INACT_UNTAGGED -- List of not tagged files in inactive panel 322 """ 323 324 if macroname in cls.macros: 325 try: 326 return cls.macros[macroname]() 327 except Exception, e: 328 xyzlog.warning(_(u"Unable to expand macro %s: %s") % 329 (ustring(macroname), unicode(e))) 330 # Return unchanged 331 return macroname
332 333 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 334 335 @classmethod 336 @instantiated
337 - def call(cls, method, *args, **kwargs):
338 """ 339 Call plugin method 340 """ 341 342 try: 343 p = Namespace(method) 344 m = cls.xyz.pm.from_load(p.pfull, p.method) 345 return m(*args, **kwargs) 346 except Exception, e: 347 error(_(u"Unable to execute method %s: %s") % 348 (method, unicode(e)))
349 350 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 351 352 @classmethod 353 @instantiated
354 - def env(cls, var, default=None):
355 """ 356 Return environment variable or default if is not set 357 """ 358 359 return os.getenv(var, default)
360 361 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 362 363 @classmethod 364 @instantiated
365 - def shell(cls, cmd, *args, **kwargs):
366 """ 367 Execute command via :core:shell plugin 368 Optional boolean argument 'current' can be provided to indicate 369 that cmd is to be run from current directory. 370 Optional boolean argument 'bg' can be provided to indicate that cmd 371 must be executed in background 372 Optional boolean argument 'reload' can be provided to indicate 373 that panel content should/should not be reloaded after execution 374 Optional boolean argument 'wait' can be provided to indicate 375 that shell should/should not wait for user input after command executed 376 The wait flag has higher priority than :core:shell's `wait` 377 configuration flag. 378 """ 379 380 if kwargs.get("current", False): 381 cmd = "./%s" % cmd 382 383 if kwargs.get("bg", False): 384 bg = ["&"] 385 else: 386 bg = [] 387 388 reloadp = kwargs.get("reload", True) 389 wait = kwargs.get("wait", None) 390 391 try: 392 exef = cls.xyz.pm.from_load(":core:shell", "execute") 393 escapef = cls.xyz.pm.from_load(":sys:cmd", "escape") 394 reloadf = cls.xyz.pm.from_load(":sys:panel", "reload_all") 395 exef(" ".join([escapef(cmd, True)] + 396 [escapef(a, True) for a in args] + bg), wait=wait) 397 if reloadp: 398 reloadf() 399 except Exception, e: 400 error(_(u"Error in DSL shell execution: %s") % unicode(e))
401 402 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 403 404 @classmethod 405 @instantiated
406 - def alias(cls, alias, replace):
407 """ 408 Set an alias which will be expanded in command line before execution 409 @param replace: Either string or function 410 """ 411 412 return cls.let(alias, replace, sect="aliases")
413 414 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 415 416 @classmethod 417 @instantiated
418 - def icmd(cls, command, obj):
419 """ 420 Set an internal command. 421 """ 422 423 if not is_func(obj): 424 error(_(u"Invalid object type: %s. Function expected") % 425 type(obj), trace=False) 426 427 return cls.let(command, obj, sect="commands")
428 429 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 430 431 @classmethod 432 @instantiated
433 - def plugins_on(cls, *plugins):
434 """ 435 Enable plugin[s] 436 """ 437 438 for plugin in plugins: 439 cls.let("plugins", {plugin: "ENABLE"}, sect="xyz")
440 441 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 442 443 @classmethod 444 @instantiated
445 - def plugins_off(cls, *plugins):
446 """ 447 Disable plugin[s] 448 """ 449 450 for plugin in plugins: 451 cls.let("plugins", {plugin: "DISABLE"}, sect="xyz")
452 453 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 454 455 @classmethod 456 @instantiated
457 - def plugin_conf(cls, plugin, opts):
458 """ 459 Configure plugin. 460 461 @param plugin: Plugin name 462 @param opts: dict {var1: val1, var2: var2,..} 463 """ 464 465 if not isinstance(opts, dict): 466 error(_(u"Invalid opts type: %s. Dict instance expected") 467 % type(opts)) 468 469 return cls.let(plugin, opts, sect="plugins")
470 471 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 472 473 @classmethod 474 @instantiated
475 - def prefix(cls, shortcut):
476 """ 477 Set new prefix key 478 """ 479 480 cls.xyz.km.set_prefix(shortcut)
481 482 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 483 484 @classmethod 485 @instantiated
486 - def help(cls, obj=None):
487 """ 488 Help 489 """ 490 491 fmt = lambda o: "%s\t%s" % (o, getattr(cls, o).__doc__) 492 493 if obj is not None and obj not in cls.api: 494 error(_(u"Invalid function %s") % obj) 495 496 if obj: 497 objs = [obj] 498 else: 499 objs = cls.api 500 501 return "\n".join([fmt(x) for x in objs]).replace("\t", " ")
502 503 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 504 505 @classmethod 506 @instantiated
507 - def vfs(cls, prefix, vfsclass):
508 """ 509 Set prefix and VFSObject class for VFS dispatching 510 """ 511 512 try: 513 return cls.xyz.vfs.register(prefix, vfsclass) 514 except Exception, e: 515 error(_(u"Error setting VFS prefix: %s") % unicode(e))
516 517 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 518 519 @classmethod 520 @instantiated
521 - def vfs_path(cls, path, driver):
522 """ 523 Construct path using provided VFS driver 524 """ 525 526 return path + "#vfs-%s#" % driver
527 528 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 529 530 @classmethod 531 @instantiated
532 - def hook(cls, event, proc):
533 """ 534 Register a new hook. 535 Event is an event string and proc is a procedure to be called 536 """ 537 538 try: 539 return cls.xyz.hm.register(event, proc) 540 except Exception, e: 541 error(_(u"Error registering new hook: %s") % unicode(e))
542 543 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 544 545 @classmethod 546 @instantiated
547 - def unhook(cls, event):
548 """ 549 Remove all hooks for the event 550 """ 551 552 return cls.xyz.hm.clear(event)
553 554 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 555 556 @classmethod 557 @instantiated
558 - def fsrule(cls, rule):
559 """ 560 Return libxyz.core.FSRule instance 561 """ 562 563 try: 564 return FSRule(rule) 565 except Exception, e: 566 error(_(u"Error parsing FSRule: %s") % unicode(e))
567 568 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 569 570 @classmethod 571 @instantiated
572 - def palette(cls, config):
573 """ 574 Create internal palette object 575 576 @param config: Dictionary of form: 577 { 578 'foreground': COLOR, 579 'background': COLOR, 580 'fg_attributes': [ATTR], 581 'mono': [ATTR], 582 'foreground_high': HG_COLOR, 583 'background_high': HG_COLOR 584 } 585 """ 586 587 try: 588 return Palette(None, *Palette.convert(config)) 589 except Exception, e: 590 error(_(u"Error creating Palette instance: %s") % unicode(e))
591 592 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 593 594 @classmethod 595 @instantiated
596 - def skin(cls, **kwargs):
597 """ 598 Make and register new skin 599 """ 600 601 try: 602 cls.xyz.sm.add(Skin(**kwargs)) 603 except Exception, e: 604 error(_(u"Error creating Skin instance: %s") % unicode(e))
605 606 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 607 608 @classmethod 609 @instantiated
610 - def execute(cls, source):
611 """ 612 Execute DSL statements 613 @param source: Either string or open file-object or code object 614 """ 615 616 try: 617 exec source in cls._env.copy() 618 except Exception, e: 619 error(_(u"Error in DSL execution: %s") % unicode(e))
620 621 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 622 623 @classmethod 624 @instantiated
625 - def get_env(cls):
626 """ 627 Return copy of global dsl environment 628 """ 629 630 return cls._env.copy()
631 632 #++++++++++++++++++++++++++++++++++++++++++++++++ 633 634 ## Auto-generate corresponding module-level functions 635 module = sys.modules[__name__] 636 637 __all__ = ["XYZ"] 638 639 for f in XYZ.api: 640 setattr(module, f, getattr(XYZ, f)) 641 __all__.append(f) 642