From 48e1a2f0a79105702b2093209888ffb25e143476 Mon Sep 17 00:00:00 2001 From: linrongbin16 Date: Fri, 17 Nov 2023 13:35:28 +0800 Subject: [PATCH] feat(url): support url template (#128) fix(command): fix command range in both manual enter & key mapping --- README.md | 125 +++++++++++++++++++---- lua/gitlinker.lua | 205 +++++++++++++++++++++++++++++++++++--- lua/gitlinker/linker.lua | 8 +- lua/gitlinker/range.lua | 4 +- lua/gitlinker/routers.lua | 8 +- test.lua | 13 +++ test/gitlinker_spec.lua | 8 +- test/linker_spec.lua | 8 +- 8 files changed, 327 insertions(+), 52 deletions(-) create mode 100644 test.lua diff --git a/README.md b/README.md index 5a9019e..1aaec4a 100644 --- a/README.md +++ b/README.md @@ -170,17 +170,61 @@ require('gitlinker').setup({ desc = "Generate git permanent link", }, - -- router + -- router bindings router = { browse = { - ["^github%.com"] = require("gitlinker.routers").github_browse, - ["^gitlab%.com"] = require("gitlinker.routers").gitlab_browse, - ["^bitbucket%.org"] = require("gitlinker.routers").bitbucket_browse, + -- example: https://github.com/linrongbin16/gitlinker.nvim/blob/9679445c7a24783d27063cd65f525f02def5f128/lua/gitlinker.lua#L3-L4 + ["^github%.com"] = "https://github.com/" + .. "{_A.USER}/" + .. "{_A.REPO}/blob/" + .. "{_A.REV}/" + .. "{_A.FILE}" + .. "{(string.len(_A.FILE) >= 3 and _A.FILE:sub(#_A.FILE-2) == '.md') and '?plain=1' or ''}" -- '?plain=1' + .. "#L{_A.LSTART}" + .. "{(_A.LEND > _A.LSTART and ('-L' .. _A.LEND) or '')}", + -- example: https://gitlab.com/linrongbin16/gitlinker.nvim/blob/9679445c7a24783d27063cd65f525f02def5f128/lua/gitlinker.lua#L3-L4 + ["^gitlab%.com"] = "https://gitlab.com/" + .. "{_A.USER}/" + .. "{_A.REPO}/blob/" + .. "{_A.REV}/" + .. "{_A.FILE}" + .. "#L{_A.LSTART}" + .. "{(_A.LEND > _A.LSTART and ('-L' .. _A.LEND) or '')}", + -- example: https://bitbucket.org/linrongbin16/gitlinker.nvim/src/9679445c7a24783d27063cd65f525f02def5f128/lua/gitlinker.lua#lines-3:4 + ["^bitbucket%.org"] = "https://bitbucket.org/" + .. "{_A.USER}/" + .. "{_A.REPO}/src/" + .. "{_A.REV}/" + .. "{_A.FILE}" + .. "#lines-{_A.LSTART}" + .. "{(_A.LEND > _A.LSTART and (':' .. _A.LEND) or '')}", }, blame = { - ["^github%.com"] = require("gitlinker.routers").github_blame, - ["^gitlab%.com"] = require("gitlinker.routers").gitlab_blame, - ["^bitbucket%.org"] = require("gitlinker.routers").bitbucket_blame, + -- example: https://github.com/linrongbin16/gitlinker.nvim/blame/9679445c7a24783d27063cd65f525f02def5f128/lua/gitlinker.lua#L3-L4 + ["^github%.com"] = "https://github.com/" + .. "{_A.USER}/" + .. "{_A.REPO}/blame/" + .. "{_A.REV}/" + .. "{_A.FILE}" + .. "{(string.len(_A.FILE) >= 3 and _A.FILE:sub(#_A.FILE-2) == '.md') and '?plain=1' or ''}" + .. "#L{_A.LSTART}" + .. "{(_A.LEND > _A.LSTART and ('-L' .. _A.LEND) or '')}", + -- example: https://gitlab.com/linrongbin16/gitlinker.nvim/blame/9679445c7a24783d27063cd65f525f02def5f128/lua/gitlinker.lua#L3-L4 + ["^gitlab%.com"] = "https://gitlab.com/" + .. "{_A.USER}/" + .. "{_A.REPO}/blame/" + .. "{_A.REV}/" + .. "{_A.FILE}" + .. "#L{_A.LSTART}" + .. "{(_A.LEND > _A.LSTART and ('-L' .. _A.LEND) or '')}", + -- example: https://bitbucket.org/linrongbin16/gitlinker.nvim/annotate/9679445c7a24783d27063cd65f525f02def5f128/lua/gitlinker.lua#lines-3:4 + ["^bitbucket%.org"] = "https://bitbucket.org/" + .. "{_A.USER}/" + .. "{_A.REPO}/annotate/" + .. "{_A.REV}/" + .. "{_A.FILE}" + .. "#lines-{_A.LSTART}" + .. "{(_A.LEND > _A.LSTART and (':' .. _A.LEND) or '')}", }, }, @@ -232,22 +276,27 @@ require('gitlinker').setup({ }) ``` -> Note: -> -> - `github_browse` is a builtin router for [github.com](https://github.com/). -> - the [lua pattern](https://www.lua.org/pil/20.2.html) needs to escape the `.` to `%.`. +There're 3 groups of builtin APIs you can directly use: + +- `github_browse`/`github_blame`: for [github.com](https://github.com/). +- `gitlab_browse`/`gitlab_blame`: for [gitlab.com](https://gitlab.com/). +- `bitbucket_browse`/`bitbucket_blame`: for [bitbucket.org](https://bitbucket.org/). ### Fully Customize Urls +
+Click here to see how to fully customize urls +
+ To fully customize url generation, please refer to the implementation of [routers.lua](https://github.com/linrongbin16/gitlinker.nvim/blob/master/lua/gitlinker/routers.lua), a router is simply construct the string from below components: -- Protocol: `git`, `https`, etc. -- Host: `github.com`, `gitlab.com`, `bitbucket.org`, etc. -- User: `linrongbin16` (for this plugin), `neovim` (for [neovim](https://github.com/neovim/neovim)), etc. -- Repo: `gitlinker.nvim`, `neovim`, etc. -- Rev: git commit, e.g. `dbf3922382576391fbe50b36c55066c1768b08b6`. -- File name: file path, e.g. `lua/gitlinker/routers.lua`. -- Line range: start/end line numbers, e.g. `#L37-L156`. +- `protocol`: `git@`, `ssh://git@`, `https`, etc. +- `host`: `github.com`, `gitlab.com`, `bitbucket.org`, etc. +- `user`: `linrongbin16` (for this plugin), `neovim` (for [neovim](https://github.com/neovim/neovim)), etc. +- `repo`: `gitlinker.nvim.git`, `neovim.git`, etc. +- `rev`: git commit, e.g. `dbf3922382576391fbe50b36c55066c1768b08b6`. +- `file`: file name, e.g. `lua/gitlinker/routers.lua`. +- `lstart`/`lend`: start/end line numbers, e.g. `#L37-L156`. For example you can customize the line numbers in form `&line=1&lines-count=2` like this: @@ -290,11 +339,49 @@ end require('gitlinker').setup({ router = { - ["^github%.your%.host"] = your_router, + browse = { + ["^github%.your%.host"] = your_router, + } } }) ``` +It seems quite a lot of engineering effort, isn't it? You can also use the url template, which should be easier to define the url schema. + +The url template is also the default implementation of builtin routers (see `router` option in [Configuration](#configuration)), while the error message could be confusing if there's any syntax issue: + +```lua +require("gitlinker").setup({ + router = { + browse = { + ["^github%.your%.host"] = "https://github.your.host/" + .. "{_A.USER}/" + .. "{_A.REPO}/blob/" + .. "{_A.REV}/" + .. "{_A.FILE}" + .. "&lines={_A.LSTART}" + .. "{_A.LEND > _A.LSTART and ('&lines-count=' .. _A.LEND) or ''}", + }, + }, +}) +``` + +The template string use curly braces `{}` to contains lua scripts, and evaluate via [luaeval()](https://neovim.io/doc/user/lua.html#lua-eval). + +The available variables are the same with the `lk` parameter passing to hook functions, but in upper case, and with the `_A.` prefix: + +- `_A.PROTOCOL`: `git@`, `ssh://git@`, `https`, etc. +- `_A.HOST`: `github.com`, `gitlab.com`, `bitbucket.org`, etc. +- `_A.USER`: `linrongbin16` (for this plugin), `neovim` (for [neovim](https://github.com/neovim/neovim)), etc. +- `_A.REPO`: `gitlinker.nvim`, `neovim`, etc. + - **Note**: for easier writing, the `.git` suffix has been removed. +- `_A.REV`: git commit, e.g. `dbf3922382576391fbe50b36c55066c1768b08b6`. +- `_A.FILE`: file name, e.g. `lua/gitlinker/routers.lua`. +- `_A.LSTART`/`_A.LEND`: start/end line numbers, e.g. `#L37-L156`. + - **Note**: for easier writing, the `_A.LEND` will always exists so no NPE will be throwed. + +
+ ## Highlight Group | Highlight Group | Default Group | Description | diff --git a/lua/gitlinker.lua b/lua/gitlinker.lua index e3bbc28..1a34f4b 100644 --- a/lua/gitlinker.lua +++ b/lua/gitlinker.lua @@ -1,7 +1,9 @@ +local range = require("gitlinker.range") local logger = require("gitlinker.logger") local linker = require("gitlinker.linker") local highlight = require("gitlinker.highlight") local deprecation = require("gitlinker.deprecation") +local utils = require("gitlinker.utils") --- @alias gitlinker.Options table --- @type gitlinker.Options @@ -25,14 +27,58 @@ local Defaults = { -- router bindings router = { browse = { - ["^github%.com"] = require("gitlinker.routers").github_browse, - ["^gitlab%.com"] = require("gitlinker.routers").gitlab_browse, - ["^bitbucket%.org"] = require("gitlinker.routers").bitbucket_browse, + -- example: https://github.com/linrongbin16/gitlinker.nvim/blob/9679445c7a24783d27063cd65f525f02def5f128/lua/gitlinker.lua#L3-L4 + ["^github%.com"] = "https://github.com/" + .. "{_A.USER}/" + .. "{_A.REPO}/blob/" + .. "{_A.REV}/" + .. "{_A.FILE}" + .. "{(string.len(_A.FILE) >= 3 and _A.FILE:sub(#_A.FILE-2) == '.md') and '?plain=1' or ''}" -- '?plain=1' + .. "#L{_A.LSTART}" + .. "{(_A.LEND > _A.LSTART and ('-L' .. _A.LEND) or '')}", + -- example: https://gitlab.com/linrongbin16/gitlinker.nvim/blob/9679445c7a24783d27063cd65f525f02def5f128/lua/gitlinker.lua#L3-L4 + ["^gitlab%.com"] = "https://gitlab.com/" + .. "{_A.USER}/" + .. "{_A.REPO}/blob/" + .. "{_A.REV}/" + .. "{_A.FILE}" + .. "#L{_A.LSTART}" + .. "{(_A.LEND > _A.LSTART and ('-L' .. _A.LEND) or '')}", + -- example: https://bitbucket.org/linrongbin16/gitlinker.nvim/src/9679445c7a24783d27063cd65f525f02def5f128/lua/gitlinker.lua#L3-L4 + ["^bitbucket%.org"] = "https://bitbucket.org/" + .. "{_A.USER}/" + .. "{_A.REPO}/src/" + .. "{_A.REV}/" + .. "{_A.FILE}" + .. "#lines-{_A.LSTART}" + .. "{(_A.LEND > _A.LSTART and (':' .. _A.LEND) or '')}", }, blame = { - ["^github%.com"] = require("gitlinker.routers").github_blame, - ["^gitlab%.com"] = require("gitlinker.routers").gitlab_blame, - ["^bitbucket%.org"] = require("gitlinker.routers").bitbucket_blame, + -- example: https://github.com/linrongbin16/gitlinker.nvim/blame/9679445c7a24783d27063cd65f525f02def5f128/lua/gitlinker.lua#L3-L4 + ["^github%.com"] = "https://github.com/" + .. "{_A.USER}/" + .. "{_A.REPO}/blame/" + .. "{_A.REV}/" + .. "{_A.FILE}" + .. "{(string.len(_A.FILE) >= 3 and _A.FILE:sub(#_A.FILE-2) == '.md') and '?plain=1' or ''}" + .. "#L{_A.LSTART}" + .. "{(_A.LEND > _A.LSTART and ('-L' .. _A.LEND) or '')}", + -- example: https://gitlab.com/linrongbin16/gitlinker.nvim/blame/9679445c7a24783d27063cd65f525f02def5f128/lua/gitlinker.lua#L3-L4 + ["^gitlab%.com"] = "https://gitlab.com/" + .. "{_A.USER}/" + .. "{_A.REPO}/blame/" + .. "{_A.REV}/" + .. "{_A.FILE}" + .. "#L{_A.LSTART}" + .. "{(_A.LEND > _A.LSTART and ('-L' .. _A.LEND) or '')}", + -- example: https://bitbucket.org/linrongbin16/gitlinker.nvim/annotate/9679445c7a24783d27063cd65f525f02def5f128/lua/gitlinker.lua#L3-L4 + ["^bitbucket%.org"] = "https://bitbucket.org/" + .. "{_A.USER}/" + .. "{_A.REPO}/annotate/" + .. "{_A.REV}/" + .. "{_A.FILE}" + .. "#lines-{_A.LSTART}" + .. "{(_A.LEND > _A.LSTART and (':' .. _A.LEND) or '')}", }, }, @@ -68,18 +114,125 @@ local function deprecated_notification(opts) end end +--- @param lk gitlinker.Linker +--- @param template string +--- @return string +local function _url_template_engine(lk, template) + local OPEN_BRACE = "{" + local CLOSE_BRACE = "}" + if type(template) ~= "string" or string.len(template) == 0 then + return template + end + + --- @alias gitlinker.UrlTemplateExpr {plain:boolean,body:string} + --- @type gitlinker.UrlTemplateExpr[] + local exprs = {} + + local i = 1 + local n = string.len(template) + while i <= n do + local open_pos = utils.string_find(template, OPEN_BRACE, i) + if not open_pos then + table.insert(exprs, { plain = true, body = string.sub(template, i) }) + break + end + table.insert( + exprs, + { plain = true, body = string.sub(template, i, open_pos - 1) } + ) + local close_pos = utils.string_find( + template, + CLOSE_BRACE, + open_pos + string.len(OPEN_BRACE) + ) + assert( + type(close_pos) == "number" and close_pos > open_pos, + string.format( + "failed to evaluate url template(%s) at pos %d", + vim.inspect(template), + open_pos + string.len(OPEN_BRACE) + ) + ) + table.insert(exprs, { + plain = false, + body = string.sub( + template, + open_pos + string.len(OPEN_BRACE), + close_pos - 1 + ), + }) + logger.debug( + "|routers.url_template| expressions:%s (%d-%d)", + vim.inspect(exprs), + vim.inspect(open_pos), + vim.inspect(close_pos) + ) + i = close_pos + string.len(CLOSE_BRACE) + end + logger.debug( + "|routers.url_template| final expressions:%s", + vim.inspect(exprs) + ) + + local results = {} + for _, exp in ipairs(exprs) do + if exp.plain then + table.insert(results, exp.body) + else + local evaluated = vim.fn.luaeval(exp.body, { + PROTOCOL = lk.protocol, + HOST = lk.host, + USER = lk.user, + REPO = utils.string_endswith(lk.repo, ".git") + and lk.repo:sub(1, #lk.repo - 4) + or lk.repo, + REV = lk.rev, + FILE = lk.file, + LSTART = lk.lstart, + LEND = (type(lk.lend) == "number" and lk.lend > lk.lstart) and lk.lend + or lk.lstart, + }) + logger.debug( + "|_url_template_engine| exp:%s, lk:%s, evaluated:%s", + vim.inspect(exp.body), + vim.inspect(lk), + vim.inspect(evaluated) + ) + table.insert(results, evaluated) + end + end + + return table.concat(results, "") +end + --- @alias gitlinker.Router fun(lk:gitlinker.Linker):string --- @param lk gitlinker.Linker --- @return string? local function _browse(lk) for pattern, route in pairs(Configs.router.browse) do - if string.match(lk.host, pattern) then + if + string.match(lk.host, pattern) + or string.match(lk.protocol .. lk.host, pattern) + then logger.debug( "|browse| match router:%s with pattern:%s", vim.inspect(route), vim.inspect(pattern) ) - return route(lk) + if type(route) == "function" then + return route(lk) + elseif type(route) == "string" then + return _url_template_engine(lk, route) + else + assert( + false, + string.format( + "unsupported router %s on pattern %s", + vim.inspect(route), + vim.inspect(pattern) + ) + ) + end end end assert( @@ -96,13 +249,29 @@ end --- @return string? local function _blame(lk) for pattern, route in pairs(Configs.router.blame) do - if string.match(lk.host, pattern) then + if + string.match(lk.host, pattern) + or string.match(lk.protocol .. lk.host, pattern) + then logger.debug( "|blame| match router:%s with pattern:%s", vim.inspect(route), vim.inspect(pattern) ) - return route(lk) + if type(route) == "function" then + return route(lk) + elseif type(route) == "string" then + return _url_template_engine(lk, route) + else + assert( + false, + string.format( + "unsupported router %s on pattern %s", + vim.inspect(route), + vim.inspect(pattern) + ) + ) + end end end assert( @@ -115,7 +284,7 @@ local function _blame(lk) return nil end ---- @param opts {action:gitlinker.Action,router:gitlinker.Router} +--- @param opts {action:gitlinker.Action,router:gitlinker.Router,lstart:integer,lend:integer} --- @return string? local function link(opts) -- logger.debug("[link] merged opts: %s", vim.inspect(opts)) @@ -124,6 +293,8 @@ local function link(opts) if not lk then return nil end + lk.lstart = opts.lstart + lk.lend = opts.lend local ok, url = pcall(opts.router, lk, true) logger.debug( @@ -238,6 +409,7 @@ local function setup(opts) -- command vim.api.nvim_create_user_command(Configs.command.name, function(command_opts) + local r = range.make_range() local parsed_args = ( type(command_opts.args) == "string" and string.len(command_opts.args) > 0 @@ -245,10 +417,15 @@ local function setup(opts) and vim.trim(command_opts.args) or nil logger.debug( - "command opts:%s, parsed:%s", + "command opts:%s, parsed:%s, range:%s", vim.inspect(command_opts), - vim.inspect(parsed_args) + vim.inspect(parsed_args), + vim.inspect(r) ) + local lstart = + math.min(r.lstart, r.lend, command_opts.line1, command_opts.line2) + local lend = + math.max(r.lstart, r.lend, command_opts.line1, command_opts.line2) local router = _browse if parsed_args == "blame" then router = _blame @@ -257,7 +434,7 @@ local function setup(opts) if command_opts.bang then action = require("gitlinker.actions").system end - link({ action = action, router = router }) + link({ action = action, router = router, lstart = lstart, lend = lend }) end, { nargs = "*", range = true, diff --git a/lua/gitlinker/linker.lua b/lua/gitlinker/linker.lua index 6246a77..2955a97 100644 --- a/lua/gitlinker/linker.lua +++ b/lua/gitlinker/linker.lua @@ -1,4 +1,3 @@ -local range = require("gitlinker.range") local git = require("gitlinker.git") local path = require("gitlinker.path") local logger = require("gitlinker.logger") @@ -153,9 +152,6 @@ local function make_linker() -- vim.inspect(buf_path_on_cwd) -- ) - local r = range.make_range() - -- logger.debug("[linker - Linker:make] range:%s", vim.inspect(r)) - local o = { remote_url = remote_url, protocol = parsed_remote_url.protocol, @@ -165,9 +161,9 @@ local function make_linker() rev = rev, file = buf_path_on_root, ---@diagnostic disable-next-line: need-check-nil - lstart = r.lstart, + lstart = nil, ---@diagnostic disable-next-line: need-check-nil - lend = r.lend, + lend = nil, file_changed = file_changed, } diff --git a/lua/gitlinker/range.lua b/lua/gitlinker/range.lua index 3881ffc..1c584a2 100644 --- a/lua/gitlinker/range.lua +++ b/lua/gitlinker/range.lua @@ -12,12 +12,12 @@ end --- @alias gitlinker.Range {lstart:integer,lend:integer,cstart:integer?,cend:integer?} --- @return gitlinker.Range local function make_range() - vim.cmd([[execute "normal! \"]]) - local m = vim.fn.visualmode() + local m = vim.fn.mode() logger.debug("|range.make_range| mode:%s", vim.inspect(m)) local l1 = nil local l2 = nil if _is_visual_mode(m) then + vim.cmd([[execute "normal! \"]]) l1 = vim.fn.getpos("'<")[2] l2 = vim.fn.getpos("'>")[2] else diff --git a/lua/gitlinker/routers.lua b/lua/gitlinker/routers.lua index bbd345d..4e54746 100644 --- a/lua/gitlinker/routers.lua +++ b/lua/gitlinker/routers.lua @@ -1,6 +1,5 @@ local utils = require("gitlinker.utils") local range = require("gitlinker.range") -local logger = require("gitlinker.logger") --- @class gitlinker.Builder --- @field domain string? @@ -55,10 +54,9 @@ function Builder:new(lk, range_maker) lk.host ), user = lk.user, - repo = (utils.string_endswith(lk.repo, ".git") and lk.repo:sub( - 1, - #lk.repo - 4 - ) or lk.repo), + repo = utils.string_endswith(lk.repo, ".git") + and lk.repo:sub(1, #lk.repo - 4) + or lk.repo, rev = lk.rev, location = string.format( "%s%s", diff --git a/test.lua b/test.lua new file mode 100644 index 0000000..031827c --- /dev/null +++ b/test.lua @@ -0,0 +1,13 @@ +require("gitlinker").setup({ + router = { + browse = { + ["^github%.your%.host"] = "https://github.your.host/" + .. "{_A.USER}/" + .. "{_A.REPO}/blob/" + .. "{_A.REV}/" + .. "{_A.FILE}" + .. "#L{_A.LSTART}" + .. "{_A.LEND > _A.LSTART and ('-L' .. _A.LEND) or ''}", + }, + }, +}) diff --git a/test/gitlinker_spec.lua b/test/gitlinker_spec.lua index 6aa5911..3a40d3e 100644 --- a/test/gitlinker_spec.lua +++ b/test/gitlinker_spec.lua @@ -26,10 +26,12 @@ describe("gitlinker", function() rev = "399b1d05473c711fc5592a6ffc724e231c403486", file = "lua/gitlinker/logger.lua", file_changed = false, + lstart = 1, + lend = 1, } --[[@as gitlinker.Linker]], true) assert_eq( actual, - "https://github.com/linrongbin16/gitlinker.nvim/blob/399b1d05473c711fc5592a6ffc724e231c403486/lua/gitlinker/logger.lua" + "https://github.com/linrongbin16/gitlinker.nvim/blob/399b1d05473c711fc5592a6ffc724e231c403486/lua/gitlinker/logger.lua#L1" ) end) it("with line start", function() @@ -96,10 +98,12 @@ describe("gitlinker", function() rev = "399b1d05473c711fc5592a6ffc724e231c403486", file = "lua/gitlinker/logger.lua", file_changed = false, + lstart = 1, + lend = 1, } --[[@as gitlinker.Linker]], true) assert_eq( actual, - "https://github.com/linrongbin16/gitlinker.nvim/blame/399b1d05473c711fc5592a6ffc724e231c403486/lua/gitlinker/logger.lua" + "https://github.com/linrongbin16/gitlinker.nvim/blame/399b1d05473c711fc5592a6ffc724e231c403486/lua/gitlinker/logger.lua#L1" ) end) it("with line start", function() diff --git a/test/linker_spec.lua b/test/linker_spec.lua index 31235c6..1eadbe1 100644 --- a/test/linker_spec.lua +++ b/test/linker_spec.lua @@ -66,8 +66,8 @@ describe("linker", function() else assert_eq(type(lk), "table") assert_eq(lk.file, "lua/gitlinker.lua") - assert_eq(lk.lstart, 1) - assert_eq(lk.lend, 1) + assert_true(lk.lstart == nil) + assert_true(lk.lend == nil) assert_eq(type(lk.rev), "string") assert_true(string.len(lk.rev) > 0) assert_eq(type(lk.remote_url), "string") @@ -85,8 +85,8 @@ describe("linker", function() else assert_eq(type(lk), "table") assert_eq(lk.file, "lua/gitlinker.lua") - assert_true(type(lk.lstart) == "number" and lk.lstart > 0) - assert_true(type(lk.lend) == "number" and lk.lend > 0) + assert_true(lk.lstart == nil) + assert_true(lk.lend == nil) assert_eq(type(lk.rev), "string") assert_true(string.len(lk.rev) > 0) assert_eq(type(lk.remote_url), "string")