# noqa: F541 """ This module describes how to match a redis command to grammar token based on regex. command_nodex: x means node? command_keys: ends with s means there can be multiple """ import logging from functools import lru_cache from prompt_toolkit.contrib.regular_languages.compiler import compile from .commands import command2syntax logger = logging.getLogger(__name__) CONST = { "failoverchoice": "TAKEOVER FORCE", "withscores": "WITHSCORES", "limit": "LIMIT", "expiration": "EX PX", "condition": "NX XX", "keepttl": "KEEPTTL", "operation": "AND OR XOR NOT", "changed": "CH", "incr": "INCR", "resetchoice": "HARD SOFT", "match": "MATCH", "count_const": "COUNT", "const_store": "STORE", "const_storedist": "STOREDIST", "type_const": "TYPE", "type": "string list set zset hash stream", "position_choice": "BEFORE AFTER", "error": "TIMEOUT ERROR", "async": "ASYNC", "conntype": "NORMAL MASTER REPLICA PUBSUB", "samples": "SAMPLES", "slotsubcmd": "IMPORTING MIGRATING NODE STABLE", "weights_const": "WEIGHTS", "aggregate_const": "AGGREGATE", "aggregate": "SUM MIN MAX", "slowlogsub": "LEN RESET GET", "shutdown": "SAVE NOSAVE", "switch": "ON OFF SKIP", "on_off": "ON OFF", "const_id": "ID", "addr": "ADDR", "skipme": "SKIPME", "yes": "YES NO", "migratechoice": "COPY REPLACE", "auth": "AUTH", "const_keys": "KEYS", "object": "REFCOUNT ENCODING IDLETIME FREQ HELP", "subrestore": "REPLACE ABSTTL IDLETIME FREQ", "distunit": "m km ft mi", "geochoice": "WITHCOORD WITHDIST WITHHASH", "order": "ASC DESC", "pubsubcmd": "CHANNELS NUMSUB NUMPAT", "scriptdebug": "YES NO SYNC", "help": "HELP", "stream": "STREAM", "streams": "STREAMS", "stream_create": "CREATE", "stream_setid": "SETID", "stream_destroy": "DESTROY", "stream_delconsumer": "DELCONSUMER", "stream_consumers": "CONSUMERS", "stream_groups": "GROUPS", "stream_group": "GROUP", "maxlen": "MAXLEN", "idel": "IDEL", "time": "TIME", "retrycount": "RETRYCOUNT", "force": "FORCE", "justid": "JUSTID", "block": "BLOCK", "noack": "NOACK", "get": "GET", "set": "SET", "incrby": "INCRBY", "overflow": "OVERFLOW", "overflow_option": "WRAP SAT FAIL", "version": "VERSION", "schedule": "SCHEDULE", "graphevent": ( "ACTIVE-DEFRAG-CYCLE " "AOF-FSYNC-ALWAYS " "AOF-STAT " "AOF-REWRITE-DIFF-WRITE " "AOF-RENAME " "AOF-WRITE " "AOF-WRITE-ACTIVE-CHILD " "AOF-WRITE-ALONE " "AOF-WRITE-PENDING-FSYNC " "COMMAND " "EXPIRE-CYCLE " "EVICTION-CYCLE " "EVICTION-DEL " "FAST-COMMAND " "FORK " "RDB-UNLINK-TEMP-FILE" ), "section": ( "SERVER " "CLIENTS " "MEMORY " "PERSISTENCE " "STATS " "REPLICATION " "CPU " "COMMANDSTATS " "CLUSTER " "KEYSPACE " "ALL " "DEFAULT " ), "redirect_const": "REDIRECT", "prefix_const": "PREFIX", "bcast_const": "BCAST", "optin_const": "OPTIN", "optout_const": "OPTOUT", "noloop_const": "NOLOOP", "reset_const": "RESET", "const_user": "USER", "full_const": "FULL", "str_algo": "LCS", "len_const": "LEN", "idx_const": "IDX", "minmatchlen_const": "MINMATCHLEN", "withmatchlen_const": "WITHMATCHLEN", "strings_const": "STRINGS", "rank_const": "RANK", } def c(const_name): const_values = CONST[const_name].split() uppers = [x.lower() for x in const_values] const_values.extend(uppers) return "|".join(const_values) VALID_TOKEN = r"""( ("([^"]|\\")*?") |# with double quotes ('([^']|\\')*?') |# with single quotes ([^\s"]+) # without quotes )""" PATTERN = fr"(?P{VALID_TOKEN})" VALID_SLOT = r"\d+" # TODO add range? max value:16384 VALID_NODE = r"\d+" NUM = r"\d+" NNUM = r"-?\+?\(?\[?(\d+|inf)" # number cloud be negative _FLOAT = r"-?(\d|\.|e)+" LEXNUM = r"(\[\w+)|(\(\w+)|(\+)|(-)" SLOT = fr"(?P{VALID_SLOT})" SLOTS = fr"(?P{VALID_SLOT}(\s+{VALID_SLOT})*)" NODE = fr"(?P{VALID_NODE})" KEY = fr"(?P{VALID_TOKEN})" PREFIX = fr"(?P{VALID_TOKEN})" KEYS = fr"(?P{VALID_TOKEN}(\s+{VALID_TOKEN})*)" DESTINATION = fr"(?P{VALID_TOKEN})" NEWKEY = fr"(?P{VALID_TOKEN})" VALUE = fr"(?P{VALID_TOKEN})" VALUES = fr"(?P{VALID_TOKEN}(\s+{VALID_TOKEN})*)" ELEMENT = fr"(?P{VALID_TOKEN})" # element for list FIELDS = fr"(?P{VALID_TOKEN}(\s+{VALID_TOKEN})*)" FIELD = fr"(?P{VALID_TOKEN})" SFIELD = fr"(?P{VALID_TOKEN})" SVALUE = fr"(?P{VALID_TOKEN})" MEMBER = fr"(?P{VALID_TOKEN})" MEMBERS = fr"(?P{VALID_TOKEN}(\s+{VALID_TOKEN})*)" COUNT = fr"(?P{NNUM})" LEN = fr"(?P{NNUM})" RANK = fr"(?P{NNUM})" VERSION_NUM = fr"(?P{NUM})" MESSAGE = fr"(?P{VALID_TOKEN})" CHANNEL = fr"(?P{VALID_TOKEN})" GROUP = fr"(?P{VALID_TOKEN})" CONSUMER = fr"(?P{VALID_TOKEN})" CATEGORYNAME = fr"(?P{VALID_TOKEN})" USERNAME = fr"(?P{VALID_TOKEN})" RULE = fr"(?P{VALID_TOKEN})" BIT = r"(?P0|1)" FLOAT = fr"(?P{_FLOAT})" LONGITUDE = fr"(?P{_FLOAT})" LATITUDE = fr"(?P{_FLOAT})" CURSOR = fr"(?P{NUM})" PARAMETER = fr"(?P{VALID_TOKEN})" DOUBLE_LUA = r'(?P[^"]*)' SINGLE_LUA = r"(?P[^']*)" INTTYPE = r"(?P(i|u)\d+)" # IP re copied from: # https://www.regular-expressions.info/ip.html IP = r"""(?P(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\. (25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\. (25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\. (25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]))""" # Port re copied from: # https://stackoverflow.com/questions/12968093/regex-to-validate-port-number # pompt_toolkit limit: Exception: {4}-style repetition not yet supported PORT = r"(?P[1-9]|[1-5]?\d\d\d?\d?|6[1-4][0-9]\d\d\d|65[1-4]\d\d|655[1-2][0-9]|6553[1-5])" EPOCH = fr"(?P{NUM})" PASSWORD = fr"(?P{VALID_TOKEN})" REPLICATIONID = fr"(?P{VALID_TOKEN})" INDEX = r"(?P(1[0-5]|\d))" CLIENTID = fr"(?P{NUM})" SECOND = fr"(?P{NUM})" TIMESTAMP = fr"(?P{NUM})" # TODO test lexer & completer for multi spaces in command # For now, redis command can have one space at most COMMAND = "(\s* (?P[\w -]+))" MILLISECOND = fr"(?P{NUM})" TIMESTAMPMS = fr"(?P{NUM})" ANY = r"(?P.*)" # TODO deleted START = fr"(?P{NNUM})" END = fr"(?P{NNUM})" # for stream ids, special ids include: -, +, $, > and * # please see: # https://redis.io/topics/streams-intro#special-ids-in-the-streams-api # stream id, DO NOT use r"" here, or the \+ will be two string # NOTE: if miss the outer (), multi IDS won't work. STREAM_ID = "(?P[T\d:>+*\-\$]+)" DELTA = fr"(?P{NNUM})" OFFSET = fr"(?P{NUM})" # string offset, can't be negative SHARP_OFFSET = f"(?P\#?{NUM})" # for bitfield command MIN = fr"(?P{NNUM})" MAX = fr"(?P{NNUM})" POSITION = fr"(?P{NNUM})" TIMEOUT = fr"(?P{NUM})" SCORE = fr"(?P{_FLOAT})" LEXMIN = fr"(?P{LEXNUM})" LEXMAX = fr"(?P{LEXNUM})" WEIGHTS = fr"(?P{_FLOAT}(\s+{_FLOAT})*)" IP_PORT = fr"(?P{IP}:{PORT})" HOST = fr"(?P{VALID_TOKEN})" # const choices FAILOVERCHOICE = fr"(?P{c('failoverchoice')})" WITHSCORES = fr"(?P{c('withscores')})" LIMIT = fr"(?P{c('limit')})" EXPIRATION = fr"(?P{c('expiration')})" CONDITION = fr"(?P{c('condition')})" OPERATION = fr"(?P{c('operation')})" CHANGED = fr"(?P{c('changed')})" INCR = fr"(?P{c('incr')})" RESETCHOICE = fr"(?P{c('resetchoice')})" MATCH = fr"(?P{c('match')})" COUNT_CONST = fr"(?P{c('count_const')})" TYPE_CONST = fr"(?P{c('type_const')})" TYPE = fr"(?P{c('type')})" POSITION_CHOICE = fr"(?P{c('position_choice')})" ERROR = fr"(?P{c('error')})" ASYNC = fr"(?P{c('async')})" CONNTYPE = fr"(?P{c('conntype')})" SAMPLES = fr"(?P{c('samples')})" SLOTSUBCMD = fr"(?P{c('slotsubcmd')})" WEIGHTS_CONST = fr"(?P{c('weights_const')})" AGGREGATE_CONST = fr"(?P{c('aggregate_const')})" AGGREGATE = fr"(?P{c('aggregate')})" SLOWLOGSUB = fr"(?P{c('slowlogsub')})" SHUTDOWN = fr"(?P{c('shutdown')})" SWITCH = fr"(?P{c('switch')})" ON_OFF = fr"(?P{c('on_off')})" CONST_ID = fr"(?P{c('const_id')})" CONST_USER = fr"(?P{c('const_user')})" ADDR = fr"(?P{c('addr')})" SKIPME = fr"(?P{c('skipme')})" YES = fr"(?P{c('yes')})" MIGRATECHOICE = fr"(?P{c('migratechoice')})" AUTH = fr"(?P{c('auth')})" CONST_KEYS = fr"(?P{c('const_keys')})" OBJECT = fr"(?P{c('object')})" SUBRESTORE = fr"(?P{c('subrestore')})" DISTUNIT = fr"(?P{c('distunit')})" GEOCHOICE = fr"(?P{c('geochoice')})" ORDER = fr"(?P{c('order')})" CONST_STORE = fr"(?P{c('const_store')})" CONST_STOREDIST = fr"(?P{c('const_storedist')})" PUBSUBCMD = fr"(?P{c('pubsubcmd')})" SCRIPTDEBUG = fr"(?P{c('scriptdebug')})" HELP = fr"(?P{c('help')})" STREAM = fr"(?P{c('stream')})" STREAM_GROUPS = fr"(?P{c('stream_groups')})" STREAM_GROUP = fr"(?P{c('stream_group')})" STREAM_CONSUMERS = fr"(?P{c('stream_consumers')})" STREAM_CREATE = fr"(?P{c('stream_create')})" STREAM_SETID = fr"(?P{c('stream_setid')})" STREAM_DESTROY = fr"(?P{c('stream_destroy')})" STREAM_DELCONSUMER = fr"(?P{c('stream_delconsumer')})" MAXLEN = fr"(?P{c('maxlen')})" APPROXIMATELY = r"(?P~)" IDEL = fr"(?P{c('idel')})" TIME = fr"(?P