# 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", "withvalues_const": "WITHVALUES", "limit": "LIMIT", "expiration": "EX PX", "exat_const": "EXAT", "pxat_const": "PXAT", "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 SYNC", "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", "laddr": "LADDR", "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", "idle": "IDLE", "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", "lr_const": "LEFT RIGHT", "pause_type": "WRITE ALL", "db_const": "DB", "replace_const": "REPLACE", "to_const": "TO", "timeout_const": "TIMEOUT", "abort_const": "ABORT", } 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 = rf"(?P{VALID_TOKEN})" VALID_SLOT = r"\d+" # TODO add range? max value:16384 VALID_NODE = r"\w+" NUM = r"\d+" NNUM = r"-?\+?\(?\[?(\d+|inf)" # number cloud be negative _FLOAT = r"-?(\d|\.|e)+" DOUBLE = r"\d*(\.\d+)?" LEXNUM = r"(\[\w+)|(\(\w+)|(\+)|(-)" SLOT = rf"(?P{VALID_SLOT})" SLOTS = rf"(?P{VALID_SLOT}(\s+{VALID_SLOT})*)" NODE = rf"(?P{VALID_NODE})" KEY = rf"(?P{VALID_TOKEN})" KEYS = rf"(?P{VALID_TOKEN}(\s+{VALID_TOKEN})*)" PREFIX = rf"(?P{VALID_TOKEN})" PREFIXES = rf"(?P{VALID_TOKEN}(\s+{VALID_TOKEN})*?)" DESTINATION = rf"(?P{VALID_TOKEN})" NEWKEY = rf"(?P{VALID_TOKEN})" VALUE = rf"(?P{VALID_TOKEN})" VALUES = rf"(?P{VALID_TOKEN}(\s+{VALID_TOKEN})*)" ELEMENT = rf"(?P{VALID_TOKEN})" # element for list FIELDS = rf"(?P{VALID_TOKEN}(\s+{VALID_TOKEN})*)" FIELD = rf"(?P{VALID_TOKEN})" SFIELD = rf"(?P{VALID_TOKEN})" SVALUE = rf"(?P{VALID_TOKEN})" MEMBER = rf"(?P{VALID_TOKEN})" MEMBERS = rf"(?P{VALID_TOKEN}(\s+{VALID_TOKEN})*)" COUNT = rf"(?P{NNUM})" LEN = rf"(?P{NNUM})" RANK = rf"(?P{NNUM})" VERSION_NUM = rf"(?P{NUM})" MESSAGE = rf"(?P{VALID_TOKEN})" CHANNEL = rf"(?P{VALID_TOKEN})" GROUP = rf"(?P{VALID_TOKEN})" CONSUMER = rf"(?P{VALID_TOKEN})" CATEGORYNAME = rf"(?P{VALID_TOKEN})" USERNAME = rf"(?P{VALID_TOKEN})" RULE = rf"(?P{VALID_TOKEN})" BIT = r"(?P0|1)" FLOAT = rf"(?P{_FLOAT})" LONGITUDE = rf"(?P{_FLOAT})" LATITUDE = rf"(?P{_FLOAT})" CURSOR = rf"(?P{NUM})" PARAMETER = rf"(?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 = rf"(?P{NUM})" PASSWORD = rf"(?P{VALID_TOKEN})" REPLICATIONID = rf"(?P{VALID_TOKEN})" INDEX = r"(?P(1[0-5]|\d))" CLIENTID = rf"(?P{NUM})" CLIENTIDS = rf"(?P{NUM}(\s+{NUM})*)" SECOND = rf"(?P{NUM})" TIMESTAMP = r"(?P[T\d:>+*\-\$]+)" # TODO test lexer & completer for multi spaces in command # For now, redis command can have one space at most COMMAND = r"(\s* (?P[\w -]+))" MILLISECOND = rf"(?P{NUM})" TIMESTAMPMS = r"(?P[T\d:>+*\-\$]+)" ANY = r"(?P.*)" # TODO deleted START = rf"(?P{NNUM})" END = rf"(?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 = r"(?P[T\d:>+*\-\$]+)" DELTA = rf"(?P{NNUM})" OFFSET = rf"(?P{NUM})" # string offset, can't be negative SHARP_OFFSET = rf"(?P\#?{NUM})" # for bitfield command MIN = rf"(?P{NNUM})" MAX = rf"(?P{NNUM})" POSITION = rf"(?P{NNUM})" SCORE = rf"(?P{_FLOAT})" LEXMIN = rf"(?P{LEXNUM})" LEXMAX = rf"(?P{LEXNUM})" WEIGHTS = rf"(?P{_FLOAT}(\s+{_FLOAT})*)" IP_PORT = rf"(?P{IP}:{PORT})" HOST = rf"(?P{VALID_TOKEN})" MIN = rf"(?P{NNUM})" MAX = rf"(?P{NNUM})" POSITION = rf"(?P{NNUM})" TIMEOUT = rf"(?P{DOUBLE})" SCORE = rf"(?P{_FLOAT})" LEXMIN = rf"(?P{LEXNUM})" LEXMAX = rf"(?P{LEXNUM})" WEIGHTS = rf"(?P{_FLOAT}(\s+{_FLOAT})*)" IP_PORT = rf"(?P{IP}:{PORT})" HOST = rf"(?P{VALID_TOKEN})" # const choices FAILOVERCHOICE = rf"(?P{c('failoverchoice')})" WITHSCORES = rf"(?P{c('withscores')})" LIMIT = rf"(?P{c('limit')})" EXPIRATION = rf"(?P{c('expiration')})" CONDITION = rf"(?P{c('condition')})" OPERATION = rf"(?P{c('operation')})" CHANGED = rf"(?P{c('changed')})" INCR = rf"(?P{c('incr')})" RESETCHOICE = rf"(?P{c('resetchoice')})" MATCH = rf"(?P{c('match')})" COUNT_CONST = rf"(?P{c('count_const')})" TYPE_CONST = rf"(?P{c('type_const')})" TYPE = rf"(?P{c('type')})" POSITION_CHOICE = rf"(?P{c('position_choice')})" ERROR = rf"(?P{c('error')})" ASYNC = rf"(?P{c('async')})" CONNTYPE = rf"(?P{c('conntype')})" SAMPLES = rf"(?P{c('samples')})" SLOTSUBCMD = rf"(?P{c('slotsubcmd')})" WEIGHTS_CONST = rf"(?P{c('weights_const')})" AGGREGATE_CONST = rf"(?P{c('aggregate_const')})" AGGREGATE = rf"(?P{c('aggregate')})" SLOWLOGSUB = rf"(?P{c('slowlogsub')})" SHUTDOWN = rf"(?P{c('shutdown')})" SWITCH = rf"(?P{c('switch')})" ON_OFF = rf"(?P{c('on_off')})" CONST_ID = rf"(?P{c('const_id')})" CONST_USER = rf"(?P{c('const_user')})" ADDR = rf"(?P{c('addr')})" LADDR = rf"(?P{c('laddr')})" SKIPME = rf"(?P{c('skipme')})" YES = rf"(?P{c('yes')})" MIGRATECHOICE = rf"(?P{c('migratechoice')})" AUTH = rf"(?P{c('auth')})" CONST_KEYS = rf"(?P{c('const_keys')})" OBJECT = rf"(?P{c('object')})" SUBRESTORE = rf"(?P{c('subrestore')})" DISTUNIT = rf"(?P{c('distunit')})" GEOCHOICE = rf"(?P{c('geochoice')})" ORDER = rf"(?P{c('order')})" CONST_STORE = rf"(?P{c('const_store')})" CONST_STOREDIST = rf"(?P{c('const_storedist')})" PUBSUBCMD = rf"(?P{c('pubsubcmd')})" SCRIPTDEBUG = rf"(?P{c('scriptdebug')})" HELP = rf"(?P{c('help')})" STREAM = rf"(?P{c('stream')})" STREAM_GROUPS = rf"(?P{c('stream_groups')})" STREAM_GROUP = rf"(?P{c('stream_group')})" STREAM_CONSUMERS = rf"(?P{c('stream_consumers')})" STREAM_CREATE = rf"(?P{c('stream_create')})" STREAM_SETID = rf"(?P{c('stream_setid')})" STREAM_DESTROY = rf"(?P{c('stream_destroy')})" STREAM_DELCONSUMER = rf"(?P{c('stream_delconsumer')})" MAXLEN = rf"(?P{c('maxlen')})" APPROXIMATELY = r"(?P~)" IDLE = rf"(?P{c('idle')})" TIME = rf"(?P