diff --git a/src/module.c b/src/module.c index a6a1c1df7..56a3ef98a 100644 --- a/src/module.c +++ b/src/module.c @@ -478,8 +478,8 @@ void *RM_PoolAlloc(RedisModuleCtx *ctx, size_t bytes) { * Helpers for modules API implementation * -------------------------------------------------------------------------- */ -/* Create an empty key of the specified type. 'kp' must point to a key object - * opened for writing where the .value member is set to NULL because the +/* Create an empty key of the specified type. `key` must point to a key object + * opened for writing where the `.value` member is set to NULL because the * key was found to be non existing. * * On success REDISMODULE_OK is returned and the key is populated with @@ -1322,7 +1322,7 @@ int RM_StringAppendBuffer(RedisModuleCtx *ctx, RedisModuleString *str, const cha * -------------------------------------------------------------------------- */ /* Send an error about the number of arguments given to the command, - * citing the command name in the error message. + * citing the command name in the error message. Returns REDISMODULE_OK. * * Example: * @@ -1394,7 +1394,7 @@ int RM_ReplyWithError(RedisModuleCtx *ctx, const char *err) { return REDISMODULE_OK; } -/* Reply with a simple string (+... \r\n in RESP protocol). This replies +/* Reply with a simple string (`+... \r\n` in RESP protocol). This replies * are suitable only when sending a small non-binary string with small * overhead, like "OK" or similar replies. * @@ -3348,20 +3348,19 @@ fmterr: * * * **cmdname**: The Redis command to call. * * **fmt**: A format specifier string for the command's arguments. Each - * of the arguments should be specified by a valid type specification: + * of the arguments should be specified by a valid type specification. The + * format specifier can also contain the modifiers `!`, `A` and `R` which + * don't have a corresponding argument. * - * * b: The argument is a buffer and is immediately followed by another - * argument that is the buffer's length. - * * c: The argument is a pointer to a plain C string (null-terminated). - * * l: The argument is long long integer. - * * s: The argument is a RedisModuleString. - * * v: The argument(s) is a vector of RedisModuleString. - * - * The format specifier can also include modifiers: - * - * * !: Sends the Redis command and its arguments to replicas and AOF. - * * A: Suppress AOF propagation, send only to replicas (requires `!`). - * * R: Suppress replicas propagation, send only to AOF (requires `!`). + * * `b` -- The argument is a buffer and is immediately followed by another + * argument that is the buffer's length. + * * `c` -- The argument is a pointer to a plain C string (null-terminated). + * * `l` -- The argument is long long integer. + * * `s` -- The argument is a RedisModuleString. + * * `v` -- The argument(s) is a vector of RedisModuleString. + * * `!` -- Sends the Redis command and its arguments to replicas and AOF. + * * `A` -- Suppress AOF propagation, send only to replicas (requires `!`). + * * `R` -- Suppress replicas propagation, send only to AOF (requires `!`). * * **...**: The actual arguments to the Redis command. * * On success a RedisModuleCallReply object is returned, otherwise @@ -3685,27 +3684,28 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, robj *value) { * still load old data produced by an older version if the rdb_load * callback is able to check the encver value and act accordingly. * The encver must be a positive value between 0 and 1023. + * * * **typemethods_ptr** is a pointer to a RedisModuleTypeMethods structure * that should be populated with the methods callbacks and structure * version, like in the following example: * - * RedisModuleTypeMethods tm = { - * .version = REDISMODULE_TYPE_METHOD_VERSION, - * .rdb_load = myType_RDBLoadCallBack, - * .rdb_save = myType_RDBSaveCallBack, - * .aof_rewrite = myType_AOFRewriteCallBack, - * .free = myType_FreeCallBack, + * RedisModuleTypeMethods tm = { + * .version = REDISMODULE_TYPE_METHOD_VERSION, + * .rdb_load = myType_RDBLoadCallBack, + * .rdb_save = myType_RDBSaveCallBack, + * .aof_rewrite = myType_AOFRewriteCallBack, + * .free = myType_FreeCallBack, * - * // Optional fields - * .digest = myType_DigestCallBack, - * .mem_usage = myType_MemUsageCallBack, - * .aux_load = myType_AuxRDBLoadCallBack, - * .aux_save = myType_AuxRDBSaveCallBack, - * .free_effort = myType_FreeEffortCallBack, - * .unlink = myType_UnlinkCallBack, - * .copy = myType_CopyCallback, - * .defrag = myType_DefragCallback - * } + * // Optional fields + * .digest = myType_DigestCallBack, + * .mem_usage = myType_MemUsageCallBack, + * .aux_load = myType_AuxRDBLoadCallBack, + * .aux_save = myType_AuxRDBSaveCallBack, + * .free_effort = myType_FreeEffortCallBack, + * .unlink = myType_UnlinkCallBack, + * .copy = myType_CopyCallback, + * .defrag = myType_DefragCallback + * } * * * **rdb_load**: A callback function pointer that loads data from RDB files. * * **rdb_save**: A callback function pointer that saves data to RDB files. @@ -3743,7 +3743,7 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, robj *value) { * a time limit and provides cursor support is used only for keys that are determined * to have significant internal complexity. To determine this, the defrag mechanism * uses the free_effort callback and the 'active-defrag-max-scan-fields' config directive. - * NOTE: The value is passed as a void** and the function is expected to update the + * NOTE: The value is passed as a `void**` and the function is expected to update the * pointer if the top-level value pointer is defragmented and consequentially changes. * * Note: the module name "AAAAAAAAA" is reserved and produces an error, it @@ -3903,7 +3903,7 @@ int moduleAllDatatypesHandleErrors() { } /* Returns true if any previous IO API failed. - * for Load* APIs the REDISMODULE_OPTIONS_HANDLE_IO_ERRORS flag must be set with + * for `Load*` APIs the REDISMODULE_OPTIONS_HANDLE_IO_ERRORS flag must be set with * RedisModule_SetModuleOptions first. */ int RM_IsIOError(RedisModuleIO *io) { return io->error; @@ -3929,7 +3929,7 @@ saveerr: } /* Load an unsigned 64 bit value from the RDB file. This function should only - * be called in the context of the rdb_load method of modules implementing + * be called in the context of the `rdb_load` method of modules implementing * new data types. */ uint64_t RM_LoadUnsigned(RedisModuleIO *io) { if (io->error) return 0; @@ -4245,7 +4245,6 @@ void RM_DigestEndSequence(RedisModuleDigest *md) { * If this is NOT done, Redis will handle corrupted (or just truncated) serialized * data by producing an error message and terminating the process. */ - void *RM_LoadDataTypeFromString(const RedisModuleString *str, const moduleType *mt) { rio payload; RedisModuleIO io; @@ -4273,7 +4272,6 @@ void *RM_LoadDataTypeFromString(const RedisModuleString *str, const moduleType * * implement in order to allow a module to arbitrarily serialize/de-serialize * keys, similar to how the Redis 'DUMP' and 'RESTORE' commands are implemented. */ - RedisModuleString *RM_SaveDataTypeToString(RedisModuleCtx *ctx, void *data, const moduleType *mt) { rio payload; RedisModuleIO io; @@ -4371,7 +4369,7 @@ const RedisModuleString *RM_GetKeyNameFromIO(RedisModuleIO *io) { return io->key; } -/* Returns a RedisModuleString with the name of the key from RedisModuleKey */ +/* Returns a RedisModuleString with the name of the key from RedisModuleKey. */ const RedisModuleString *RM_GetKeyNameFromModuleKey(RedisModuleKey *key) { return key ? key->key : NULL; } @@ -4442,6 +4440,9 @@ void RM_LogIOError(RedisModuleIO *io, const char *levelstr, const char *fmt, ... } /* Redis-like assert function. + * + * The macro `RedisModule_Assert(expression)` is recommended, rather than + * calling this function directly. * * A failed assertion will shut down the server and produce logging information * that looks identical to information generated by Redis itself. @@ -4676,7 +4677,7 @@ RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc * key, or a client in queue before this one can be served, modifying the key * as well and making it empty again. So when a client is blocked with * RedisModule_BlockClientOnKeys() the reply callback is not called after - * RM_UnblockCLient() is called, but every time a key is signaled as ready: + * RM_UnblockClient() is called, but every time a key is signaled as ready: * if the reply callback can serve the client, it returns REDISMODULE_OK * and the client is unblocked, otherwise it will return REDISMODULE_ERR * and we'll try again later. @@ -7189,199 +7190,197 @@ void ModuleForkDoneHandler(int exitcode, int bysignal) { * * * RedisModuleEvent_ReplicationRoleChanged: * - * This event is called when the instance switches from master - * to replica or the other way around, however the event is - * also called when the replica remains a replica but starts to - * replicate with a different master. + * This event is called when the instance switches from master + * to replica or the other way around, however the event is + * also called when the replica remains a replica but starts to + * replicate with a different master. * - * The following sub events are available: + * The following sub events are available: * - * * REDISMODULE_SUBEVENT_REPLROLECHANGED_NOW_MASTER - * * REDISMODULE_SUBEVENT_REPLROLECHANGED_NOW_REPLICA + * * `REDISMODULE_SUBEVENT_REPLROLECHANGED_NOW_MASTER` + * * `REDISMODULE_SUBEVENT_REPLROLECHANGED_NOW_REPLICA` * - * The 'data' field can be casted by the callback to a - * RedisModuleReplicationInfo structure with the following fields: + * The 'data' field can be casted by the callback to a + * `RedisModuleReplicationInfo` structure with the following fields: * - * int master; // true if master, false if replica - * char *masterhost; // master instance hostname for NOW_REPLICA - * int masterport; // master instance port for NOW_REPLICA - * char *replid1; // Main replication ID - * char *replid2; // Secondary replication ID - * uint64_t repl1_offset; // Main replication offset - * uint64_t repl2_offset; // Offset of replid2 validity + * int master; // true if master, false if replica + * char *masterhost; // master instance hostname for NOW_REPLICA + * int masterport; // master instance port for NOW_REPLICA + * char *replid1; // Main replication ID + * char *replid2; // Secondary replication ID + * uint64_t repl1_offset; // Main replication offset + * uint64_t repl2_offset; // Offset of replid2 validity * * * RedisModuleEvent_Persistence * - * This event is called when RDB saving or AOF rewriting starts - * and ends. The following sub events are available: + * This event is called when RDB saving or AOF rewriting starts + * and ends. The following sub events are available: * - * * REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START - * * REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START - * * REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START - * * REDISMODULE_SUBEVENT_PERSISTENCE_ENDED - * * REDISMODULE_SUBEVENT_PERSISTENCE_FAILED + * * `REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START` + * * `REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START` + * * `REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START` + * * `REDISMODULE_SUBEVENT_PERSISTENCE_ENDED` + * * `REDISMODULE_SUBEVENT_PERSISTENCE_FAILED` * - * The above events are triggered not just when the user calls the - * relevant commands like BGSAVE, but also when a saving operation - * or AOF rewriting occurs because of internal server triggers. - * The SYNC_RDB_START sub events are happening in the forground due to - * SAVE command, FLUSHALL, or server shutdown, and the other RDB and - * AOF sub events are executed in a background fork child, so any - * action the module takes can only affect the generated AOF or RDB, - * but will not be reflected in the parent process and affect connected - * clients and commands. Also note that the AOF_START sub event may end - * up saving RDB content in case of an AOF with rdb-preamble. + * The above events are triggered not just when the user calls the + * relevant commands like BGSAVE, but also when a saving operation + * or AOF rewriting occurs because of internal server triggers. + * The SYNC_RDB_START sub events are happening in the forground due to + * SAVE command, FLUSHALL, or server shutdown, and the other RDB and + * AOF sub events are executed in a background fork child, so any + * action the module takes can only affect the generated AOF or RDB, + * but will not be reflected in the parent process and affect connected + * clients and commands. Also note that the AOF_START sub event may end + * up saving RDB content in case of an AOF with rdb-preamble. * * * RedisModuleEvent_FlushDB * - * The FLUSHALL, FLUSHDB or an internal flush (for instance - * because of replication, after the replica synchronization) - * happened. The following sub events are available: + * The FLUSHALL, FLUSHDB or an internal flush (for instance + * because of replication, after the replica synchronization) + * happened. The following sub events are available: * - * * REDISMODULE_SUBEVENT_FLUSHDB_START - * * REDISMODULE_SUBEVENT_FLUSHDB_END + * * `REDISMODULE_SUBEVENT_FLUSHDB_START` + * * `REDISMODULE_SUBEVENT_FLUSHDB_END` * - * The data pointer can be casted to a RedisModuleFlushInfo - * structure with the following fields: + * The data pointer can be casted to a RedisModuleFlushInfo + * structure with the following fields: * - * int32_t async; // True if the flush is done in a thread. - * // See for instance FLUSHALL ASYNC. - * // In this case the END callback is invoked - * // immediately after the database is put - * // in the free list of the thread. - * int32_t dbnum; // Flushed database number, -1 for all the DBs - * // in the case of the FLUSHALL operation. + * int32_t async; // True if the flush is done in a thread. + * // See for instance FLUSHALL ASYNC. + * // In this case the END callback is invoked + * // immediately after the database is put + * // in the free list of the thread. + * int32_t dbnum; // Flushed database number, -1 for all the DBs + * // in the case of the FLUSHALL operation. * - * The start event is called *before* the operation is initated, thus - * allowing the callback to call DBSIZE or other operation on the - * yet-to-free keyspace. + * The start event is called *before* the operation is initated, thus + * allowing the callback to call DBSIZE or other operation on the + * yet-to-free keyspace. * * * RedisModuleEvent_Loading * - * Called on loading operations: at startup when the server is - * started, but also after a first synchronization when the - * replica is loading the RDB file from the master. - * The following sub events are available: + * Called on loading operations: at startup when the server is + * started, but also after a first synchronization when the + * replica is loading the RDB file from the master. + * The following sub events are available: * - * * REDISMODULE_SUBEVENT_LOADING_RDB_START - * * REDISMODULE_SUBEVENT_LOADING_AOF_START - * * REDISMODULE_SUBEVENT_LOADING_REPL_START - * * REDISMODULE_SUBEVENT_LOADING_ENDED - * * REDISMODULE_SUBEVENT_LOADING_FAILED - * - * Note that AOF loading may start with an RDB data in case of - * rdb-preamble, in which case you'll only receive an AOF_START event. + * * `REDISMODULE_SUBEVENT_LOADING_RDB_START` + * * `REDISMODULE_SUBEVENT_LOADING_AOF_START` + * * `REDISMODULE_SUBEVENT_LOADING_REPL_START` + * * `REDISMODULE_SUBEVENT_LOADING_ENDED` + * * `REDISMODULE_SUBEVENT_LOADING_FAILED` * + * Note that AOF loading may start with an RDB data in case of + * rdb-preamble, in which case you'll only receive an AOF_START event. * * * RedisModuleEvent_ClientChange * - * Called when a client connects or disconnects. - * The data pointer can be casted to a RedisModuleClientInfo - * structure, documented in RedisModule_GetClientInfoById(). - * The following sub events are available: + * Called when a client connects or disconnects. + * The data pointer can be casted to a RedisModuleClientInfo + * structure, documented in RedisModule_GetClientInfoById(). + * The following sub events are available: * - * * REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED - * * REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED + * * `REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED` + * * `REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED` * * * RedisModuleEvent_Shutdown * - * The server is shutting down. No subevents are available. + * The server is shutting down. No subevents are available. * * * RedisModuleEvent_ReplicaChange * - * This event is called when the instance (that can be both a - * master or a replica) get a new online replica, or lose a - * replica since it gets disconnected. - * The following sub events are available: + * This event is called when the instance (that can be both a + * master or a replica) get a new online replica, or lose a + * replica since it gets disconnected. + * The following sub events are available: * - * * REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE - * * REDISMODULE_SUBEVENT_REPLICA_CHANGE_OFFLINE + * * `REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE` + * * `REDISMODULE_SUBEVENT_REPLICA_CHANGE_OFFLINE` * - * No additional information is available so far: future versions - * of Redis will have an API in order to enumerate the replicas - * connected and their state. + * No additional information is available so far: future versions + * of Redis will have an API in order to enumerate the replicas + * connected and their state. * * * RedisModuleEvent_CronLoop * - * This event is called every time Redis calls the serverCron() - * function in order to do certain bookkeeping. Modules that are - * required to do operations from time to time may use this callback. - * Normally Redis calls this function 10 times per second, but - * this changes depending on the "hz" configuration. - * No sub events are available. + * This event is called every time Redis calls the serverCron() + * function in order to do certain bookkeeping. Modules that are + * required to do operations from time to time may use this callback. + * Normally Redis calls this function 10 times per second, but + * this changes depending on the "hz" configuration. + * No sub events are available. * - * The data pointer can be casted to a RedisModuleCronLoop - * structure with the following fields: + * The data pointer can be casted to a RedisModuleCronLoop + * structure with the following fields: * - * int32_t hz; // Approximate number of events per second. + * int32_t hz; // Approximate number of events per second. * * * RedisModuleEvent_MasterLinkChange * - * This is called for replicas in order to notify when the - * replication link becomes functional (up) with our master, - * or when it goes down. Note that the link is not considered - * up when we just connected to the master, but only if the - * replication is happening correctly. - * The following sub events are available: + * This is called for replicas in order to notify when the + * replication link becomes functional (up) with our master, + * or when it goes down. Note that the link is not considered + * up when we just connected to the master, but only if the + * replication is happening correctly. + * The following sub events are available: * - * * REDISMODULE_SUBEVENT_MASTER_LINK_UP - * * REDISMODULE_SUBEVENT_MASTER_LINK_DOWN + * * `REDISMODULE_SUBEVENT_MASTER_LINK_UP` + * * `REDISMODULE_SUBEVENT_MASTER_LINK_DOWN` * * * RedisModuleEvent_ModuleChange * - * This event is called when a new module is loaded or one is unloaded. - * The following sub events are available: + * This event is called when a new module is loaded or one is unloaded. + * The following sub events are available: * - * * REDISMODULE_SUBEVENT_MODULE_LOADED - * * REDISMODULE_SUBEVENT_MODULE_UNLOADED + * * `REDISMODULE_SUBEVENT_MODULE_LOADED` + * * `REDISMODULE_SUBEVENT_MODULE_UNLOADED` * - * The data pointer can be casted to a RedisModuleModuleChange - * structure with the following fields: + * The data pointer can be casted to a RedisModuleModuleChange + * structure with the following fields: * - * const char* module_name; // Name of module loaded or unloaded. - * int32_t module_version; // Module version. + * const char* module_name; // Name of module loaded or unloaded. + * int32_t module_version; // Module version. * * * RedisModuleEvent_LoadingProgress * - * This event is called repeatedly called while an RDB or AOF file - * is being loaded. - * The following sub events are availble: + * This event is called repeatedly called while an RDB or AOF file + * is being loaded. + * The following sub events are availble: * - * * REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB - * * REDISMODULE_SUBEVENT_LOADING_PROGRESS_AOF + * * `REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB` + * * `REDISMODULE_SUBEVENT_LOADING_PROGRESS_AOF` * - * The data pointer can be casted to a RedisModuleLoadingProgress - * structure with the following fields: + * The data pointer can be casted to a RedisModuleLoadingProgress + * structure with the following fields: * - * int32_t hz; // Approximate number of events per second. - * int32_t progress; // Approximate progress between 0 and 1024, - * or -1 if unknown. + * int32_t hz; // Approximate number of events per second. + * int32_t progress; // Approximate progress between 0 and 1024, + * // or -1 if unknown. * * * RedisModuleEvent_SwapDB * - * This event is called when a SWAPDB command has been successfully - * Executed. - * For this event call currently there is no subevents available. + * This event is called when a SWAPDB command has been successfully + * Executed. + * For this event call currently there is no subevents available. * - * The data pointer can be casted to a RedisModuleSwapDbInfo - * structure with the following fields: + * The data pointer can be casted to a RedisModuleSwapDbInfo + * structure with the following fields: * - * int32_t dbnum_first; // Swap Db first dbnum - * int32_t dbnum_second; // Swap Db second dbnum + * int32_t dbnum_first; // Swap Db first dbnum + * int32_t dbnum_second; // Swap Db second dbnum * * * RedisModuleEvent_ReplBackup * - * Called when diskless-repl-load config is set to swapdb, - * And redis needs to backup the the current database for the - * possibility to be restored later. A module with global data and - * maybe with aux_load and aux_save callbacks may need to use this - * notification to backup / restore / discard its globals. - * The following sub events are available: - * - * * REDISMODULE_SUBEVENT_REPL_BACKUP_CREATE - * * REDISMODULE_SUBEVENT_REPL_BACKUP_RESTORE - * * REDISMODULE_SUBEVENT_REPL_BACKUP_DISCARD + * Called when diskless-repl-load config is set to swapdb, + * And redis needs to backup the the current database for the + * possibility to be restored later. A module with global data and + * maybe with aux_load and aux_save callbacks may need to use this + * notification to backup / restore / discard its globals. + * The following sub events are available: * + * * `REDISMODULE_SUBEVENT_REPL_BACKUP_CREATE` + * * `REDISMODULE_SUBEVENT_REPL_BACKUP_RESTORE` + * * `REDISMODULE_SUBEVENT_REPL_BACKUP_DISCARD` * * The function returns REDISMODULE_OK if the module was successfully subscribed * for the specified event. If the API is called from a wrong context or unsupported event @@ -8291,7 +8290,7 @@ int RM_DefragCursorSet(RedisModuleDefragCtx *ctx, unsigned long cursor) { /* Fetch a cursor value that has been previously stored using RM_DefragCursorSet(). * * If not called for a late defrag operation, REDISMODULE_ERR will be returned and - * the cursor should be ignored. See DM_DefragCursorSet() for more details on + * the cursor should be ignored. See RM_DefragCursorSet() for more details on * defrag cursors. */ int RM_DefragCursorGet(RedisModuleDefragCtx *ctx, unsigned long *cursor) { diff --git a/src/modules/gendoc.rb b/src/modules/gendoc.rb index 249c8b6ea..2fd2ec5d7 100644 --- a/src/modules/gendoc.rb +++ b/src/modules/gendoc.rb @@ -9,13 +9,21 @@ def markdown(s) s.chop! while s[-1] == "\n" || s[-1] == " " lines = s.split("\n") newlines = [] - # Backquote function, macro and type names, except if already backquoted and - # in code blocks indented by 4 spaces. + # Fix some markdown, except in code blocks indented by 4 spaces. lines.each{|l| if not l.start_with?(' ') - l = l.gsub(/(?<!`)RM_[A-z()]+/){|x| "`#{x}`"} - l = l.gsub(/(?<!`)RedisModule[A-z()]+/){|x| "`#{x}`"} - l = l.gsub(/(?<!`)REDISMODULE_[A-z]+/){|x| "`#{x}`"} + # Rewrite RM_Xyz() to `RedisModule_Xyz()`. The () suffix is + # optional. Even RM_Xyz*() with * as wildcard is handled. + l = l.gsub(/(?<!`)RM_([A-z]+(?:\*?\(\))?)/, '`RedisModule_\1`') + # Add backquotes around RedisModule functions and type where missing. + l = l.gsub(/(?<!`)RedisModule[A-z]+(?:\*?\(\))?/){|x| "`#{x}`"} + # Add backquotes around c functions like malloc() where missing. + l = l.gsub(/(?<![`A-z])[a-z_]+\(\)/, '`\0`') + # Add backquotes around macro and var names containing underscores. + l = l.gsub(/(?<![`A-z\*])[A-Za-z]+_[A-Za-z0-9_]+/){|x| "`#{x}`"} + # Link URLs preceded by space (i.e. when not already linked) + l = l.gsub(/ (https?:\/\/[A-Za-z0-9_\/\.\-]+[A-Za-z0-9\/])/, + ' [\1](\1)') end newlines << l }