| 
 # Copyright (C) 2009  Internet Systems Consortium.  
#  
# Permission to use, copy, modify, and distribute this software for any  
# purpose with or without fee is hereby granted, provided that the above  
# copyright notice and this permission notice appear in all copies.  
#  
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM  
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL  
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,  
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING  
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,  
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION  
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.  
  
  
"""This module holds the command parser class for bindctl"""  
  
import re  
from bindctl.exception import *  
try:  
    from collections import OrderedDict  
except ImportError:  
    from bindctl.mycollections import OrderedDict  
  
param_name_str = "^\s*(?P<param_name>[\w]+)\s*=\s*"  
  
# The value string can be a sequence without space or comma   
# characters, or a string surroundedby quotation marks(such marks  
# can be part of string in an escaped form)  
#param_value_str  = "(?P<param_value>[\"\'].+?(?<!\\\)[\"\']|[^\'\"][^, ]+)"  
param_value_str  = "(?P<param_value>[^\'\" ][^, ]+)"  
param_value_with_quota_str  = "[\"\'](?P<param_value>.+?)(?<!\\\)[\"\']"  
next_params_str = "(?P<blank>\s*)(?P<comma>,?)(?P<next_params>.*)$"  
  
  
PARAM_WITH_QUOTA_PATTERN = re.compile(param_name_str +  
                                      param_value_with_quota_str +  
                                      next_params_str)  
PARAM_PATTERN = re.compile(param_name_str + param_value_str + next_params_str)  
# Used for module and command name  
NAME_PATTERN = re.compile("^\s*(?P<name>[\w]+)(?P<blank>\s*)(?P<others>.*)$")  
  
# this removes all whitespace in the given string, except when  
# between " quotes  
_remove_unquoted_whitespace = \  
    lambda text:'"'.join( it if i%2 else ''.join(it.split())  
        for i,it in enumerate(text.split('"'))  )  
  
  
def _remove_list_and_map_whitespace(text):  
    """Returns a string where the whitespace between matching [ and ]  
       is removed, unless quoted"""  
    # regular expression aren't really the right tool, since we may have  
    # nested structures  
    result = []  
    start_pos = 0  
    pos = 0  
    list_count = 0  
    map_count = 0  
    cur_start_list_pos = None  
    cur_start_map_pos = None  
    for i in text:  
        if i == '[' and map_count == 0:  
            if list_count == 0:  
                result.append(text[start_pos:pos + 1])  
                cur_start_list_pos = pos + 1  
            list_count = list_count + 1  
        elif i == ']' and map_count == 0:  
            if list_count > 0:  
                list_count = list_count - 1  
                if list_count == 0:  
                    result.append(_remove_unquoted_whitespace(text[cur_start_list_pos:pos + 1]))  
                    start_pos = pos + 1  
        if i == '{' and list_count == 0:  
            if map_count == 0:  
                result.append(text[start_pos:pos + 1])  
                cur_start_map_pos = pos + 1  
            map_count = map_count + 1  
        elif i == '}' and list_count == 0:  
            if map_count > 0:  
                map_count = map_count - 1  
                if map_count == 0:  
                    result.append(_remove_unquoted_whitespace(text[cur_start_map_pos:pos + 1]))  
                    start_pos = pos + 1  
  
  
        pos = pos + 1  
91    if start_pos <= len(text):  
        result.append(text[start_pos:len(text)])  
    return "".join(result)  
  
  
class BindCmdParse:  
    """ This class will parse the command line user input into three parts:  
    module name, command, parameters  
    the first two parts are strings and parameter is one hash,   
    parameters part is optional  
      
    Example: zone reload, zone_name=example.com   
    module == zone  
    command == reload  
    params == [zone_name = 'example.com']  
    """  
  
    def __init__(self, cmd):  
        self.params = OrderedDict()  
        self.module = ''  
        self.command = ''  
        self._parse_cmd(cmd)  
  
    def _parse_cmd(self, text_str):  
        '''Parse command line. '''  
        # Get module name  
        groups = NAME_PATTERN.match(text_str)  
        if not groups:  
            raise CmdModuleNameFormatError  
  
        self.module = groups.group('name')  
        cmd_str = groups.group('others')  
        if cmd_str:  
            if not groups.group('blank'):  
                raise CmdModuleNameFormatError  
        else:  
            raise CmdMissCommandNameFormatError(self.module)  
  
        # Get command name  
        groups = NAME_PATTERN.match(cmd_str)  
        if (not groups):  
            raise CmdCommandNameFormatError(self.module)  
  
        self.command = groups.group('name')  
        param_str = groups.group('others')  
        if param_str:  
            if not groups.group('blank'):  
                raise CmdCommandNameFormatError(self.module)  
  
            self._parse_params(param_str)  
  
    def _remove_list_whitespace(self, text):  
        return ""  
  
    def _parse_params(self, param_text):  
        """convert a=b,c=d into one hash """  
        param_text = _remove_list_and_map_whitespace(param_text)  
  
        # Check parameter name "help"  
        param = NAME_PATTERN.match(param_text)  
        if param and param.group('name') == "help":  
            self.params["help"] = "help"  
            return  
  
        while True:  
155            if not param_text.strip():  
                break  
  
            groups = PARAM_PATTERN.match(param_text) or \  
                     PARAM_WITH_QUOTA_PATTERN.match(param_text)  
            if not groups:  
                # ok, fill in the params in the order entered  
                params = re.findall("([^\" ]+|\".*\")", param_text)  
                i = 0  
                for p in params:  
                    self.params[i] = p  
                    i += 1  
                break  
            else:  
                self.params[groups.group('param_name')] = groups.group('param_value')  
                param_text = groups.group('next_params')  
                if not param_text or (not param_text.strip()):  
                    break  
  
175                if not groups.group('blank') and \  
                   not groups.group('comma'):  
                    raise CmdParamFormatError(self.module, self.command)  
                
             |