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, ustring(str(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                                                            ustring(str(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") % (
    285                  ustring(str(e))))
    286
    287          if f:
    288              f.close()
    289
    290      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    291
    292      @classmethod
    293      @instantiated
    294 -    def action(cls, rule, fn):
    295          """
    296          Set up an action to be taken upon pressing action key on file
    297          """
    298
    299          try:
    300              cls.xyz.am.register(rule, fn)
    301          except Exception, e:
    302              error(_(u"Unable to register action: %s") % ustring(str(e)))
    303
    304      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    305
    306      @classmethod
    307      @instantiated
    308 -    def macro(cls, macroname):
    309          """
    310          Expand macro name.
    311
    312          Availbale macros:
    313          * ACT_CWD        -- Working directory in active panel
    314          * INACT_CWD      -- Working directory in inactive panel
    315          * ACT_PATH       -- Full selected object path in active panel
    316          * INACT_PATH     -- Full selected object path in inactive panel
    317          * ACT_BASE       -- Parent directory in active panel
    318          * INACT_BASE     -- Parent directory in inactive panel
    319          * ACT_TAGGED     -- List of tagged files in active panel
    320          * INACT_TAGGED   -- List of tagged files in inactive panel
    321          * ACT_UNTAGGED   -- List of not tagged files in active panel
    322          * INACT_UNTAGGED -- List of not tagged files in inactive panel
    323          """
    324
    325          if macroname in cls.macros:
    326              try:
    327                  return cls.macros[macroname]()
    328              except Exception, e:
    329                  xyzlog.warning(_(u"Unable to expand macro %s: %s") %
    330                                 (ustring(macroname), ustring(str(e))))
    331          # Return unchanged
    332          return macroname
    333
    334      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    335
    336      @classmethod
    337      @instantiated
    338 -    def call(cls, method, *args, **kwargs):
    339          """
    340          Call plugin method
    341          """
    342
    343          try:
    344              p = Namespace(method)
    345              m = cls.xyz.pm.from_load(p.pfull, p.method)
    346              return m(*args, **kwargs)
    347          except Exception, e:
    348              error(_(u"Unable to execute method %s: %s" %
    349                      (method, ustring(str(e)))))
    350
    351      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    352
    353      @classmethod
    354      @instantiated
    355 -    def env(cls, var, default=None):
    356          """
    357          Return environment variable or default if is not set
    358          """
    359
    360          return os.getenv(var, default)
    361
    362      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    363
    364      @classmethod
    365      @instantiated
    366 -    def shell(cls, cmd, *args, **kwargs):
    367          """
    368          Execute command via :core:shell plugin
    369          Optional boolean argument 'current' can be provided to indicate
    370          that cmd is to be run from current directory.
    371          Optional boolean argument 'bg' can be provided to indicate that cmd
    372          must be executed in background
    373          Optional boolean argument 'reload' can be provided to indicate
    374          that panel content should/should not be reloaded after execution
    375          Optional boolean argument 'wait' can be provided to indicate
    376          that shell should/should not wait for user input after command executed
    377          The wait flag has higher priority than :core:shell's `wait`
    378          configuration flag.
    379          """
    380
    381          if kwargs.get("current", False):
    382              cmd = "./%s" % cmd
    383
    384          if kwargs.get("bg", False):
    385              bg = ["&"]
    386          else:
    387              bg = []
    388
    389          reloadp = kwargs.get("reload", True)
    390          wait = kwargs.get("wait", None)
    391
    392          try:
    393              exef = cls.xyz.pm.from_load(":core:shell", "execute")
    394              escapef = cls.xyz.pm.from_load(":sys:cmd", "escape")
    395              reloadf = cls.xyz.pm.from_load(":sys:panel", "reload_all")
    396              exef(" ".join([escapef(cmd, True)] +
    397                            [escapef(a, True) for a in args] + bg), wait=wait)
    398              if reloadp:
    399                  reloadf()
    400          except Exception, e:
    401              error(_(u"Error in DSL shell execution: %s") % ustring(str(e)))
    402
    403      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    404
    405      @classmethod
    406      @instantiated
    407 -    def alias(cls, alias, replace):
    408          """
    409          Set an alias which will be expanded in command line before execution
    410          @param replace: Either string or function
    411          """
    412
    413          return cls.let(alias, replace, sect="aliases")
    414
    415      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    416
    417      @classmethod
    418      @instantiated
    419 -    def icmd(cls, command, obj):
    420          """
    421          Set an internal command.
    422          """
    423
    424          if not is_func(obj):
    425              error(_(u"Invalid object type: %s. Function expected") %
    426                    type(obj), trace=False)
    427
    428          return cls.let(command, obj, sect="commands")
    429
    430      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    431
    432      @classmethod
    433      @instantiated
    434 -    def plugins_on(cls, *plugins):
    435          """
    436          Enable plugin[s]
    437          """
    438
    439          for plugin in plugins:
    440              cls.let("plugins", {plugin: "ENABLE"}, sect="xyz")
    441
    442      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    443
    444      @classmethod
    445      @instantiated
    446 -    def plugins_off(cls, *plugins):
    447          """
    448          Disable plugin[s]
    449          """
    450
    451          for plugin in plugins:
    452              cls.let("plugins", {plugin: "DISABLE"}, sect="xyz")
    453
    454      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    455
    456      @classmethod
    457      @instantiated
    458 -    def plugin_conf(cls, plugin, opts):
    459          """
    460          Configure plugin.
    461
    462          @param plugin: Plugin name
    463          @param opts: dict {var1: val1, var2: var2,..}
    464          """
    465
    466          if not isinstance(opts, dict):
    467              error(_(u"Invalid opts type: %s. Dict instance expected")
    468                    % type(opts))
    469
    470          return cls.let(plugin, opts, sect="plugins")
    471
    472      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    473
    474      @classmethod
    475      @instantiated
    476 -    def prefix(cls, shortcut):
    477          """
    478          Set new prefix key
    479          """
    480
    481          cls.xyz.km.set_prefix(shortcut)
    482
    483      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    484
    485      @classmethod
    486      @instantiated
    487 -    def help(cls, obj=None):
    488          """
    489          Help
    490          """
    491
    492          fmt = lambda o: "%s\t%s" % (o, getattr(cls, o).__doc__)
    493
    494          if obj is not None and obj not in cls.api:
    495              error(_(u"Invalid function %s") % obj)
    496
    497          if obj:
    498              objs = [obj]
    499          else:
    500              objs = cls.api
    501
    502          return "\n".join([fmt(x) for x in objs]).replace("\t", " ")
    503
    504      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    505
    506      @classmethod
    507      @instantiated
    508 -    def vfs(cls, prefix, vfsclass):
    509          """
    510          Set prefix and VFSObject class for VFS dispatching
    511          """
    512
    513          try:
    514              return cls.xyz.vfs.register(prefix, vfsclass)
    515          except Exception, e:
    516              error(_(u"Error setting VFS prefix: %s") % ustring(str(e)))
    517
    518      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    519
    520      @classmethod
    521      @instantiated
    522 -    def vfs_path(cls, path, driver):
    523          """
    524          Construct path using provided VFS driver
    525          """
    526
    527          return path + "#vfs-%s#" % driver
    528
    529      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    530
    531      @classmethod
    532      @instantiated
    533 -    def hook(cls, event, proc):
    534          """
    535          Register a new hook.
    536          Event is an event string and proc is a procedure to be called
    537          """
    538
    539          try:
    540              return cls.xyz.hm.register(event, proc)
    541          except Exception, e:
    542              error(_(u"Error registering new hook: %s") % ustring(str(e)))
    543
    544      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    545
    546      @classmethod
    547      @instantiated
    548 -    def unhook(cls, event):
    549          """
    550          Remove all hooks for the event
    551          """
    552
    553          return cls.xyz.hm.clear(event)
    554
    555      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    556
    557      @classmethod
    558      @instantiated
    559 -    def fsrule(cls, rule):
    560          """
    561          Return libxyz.core.FSRule instance
    562          """
    563
    564          try:
    565              return FSRule(rule)
    566          except Exception, e:
    567              error(_(u"Error parsing FSRule: %s") % ustring(str(e)))
    568
    569      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    570
    571      @classmethod
    572      @instantiated
    573 -    def palette(cls, config):
    574          """
    575          Create internal palette object
    576
    577          @param config: Dictionary of form:
    578          {
    579             'foreground': COLOR,
    580             'background': COLOR,
    581             'fg_attributes': [ATTR],
    582             'mono': [ATTR],
    583             'foreground_high': HG_COLOR,
    584             'background_high': HG_COLOR
    585          }
    586          """
    587
    588          try:
    589              return Palette(None, *Palette.convert(config))
    590          except Exception, e:
    591              error(_(u"Error creating Palette instance: %s") % ustring(str(e)))
    592
    593      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    594
    595      @classmethod
    596      @instantiated
    597 -    def skin(cls, **kwargs):
    598          """
    599          Make and register new skin
    600          """
    601
    602          try:
    603              cls.xyz.sm.add(Skin(**kwargs))
    604          except Exception, e:
    605              error(_(u"Error creating Skin instance: %s") % ustring(str(e)))
    606
    607      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    608
    609      @classmethod
    610      @instantiated
    611 -    def execute(cls, source):
    612          """
    613          Execute DSL statements
    614          @param source: Either string or open file-object or code object
    615          """
    616
    617          try:
    618              exec source in cls._env.copy()
    619          except Exception, e:
    620              error(_(u"Error in DSL execution: %s") % ustring(str(e)))
    621
    622      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    623
    624      @classmethod
    625      @instantiated
    626 -    def get_env(cls):
    627          """
    628          Return copy of global dsl environment
    629          """
    630
    631          return cls._env.copy()
    632
    633  #++++++++++++++++++++++++++++++++++++++++++++++++
    634
    635  ## Auto-generate corresponding module-level functions
    636  module = sys.modules[__name__]
    637
    638  __all__ = ["XYZ"]
    639
    640  for f in XYZ.api:
    641      setattr(module, f, getattr(XYZ, f))
    642      __all__.append(f)
    643