diff --git a/doc/rest-nvim.txt b/doc/rest-nvim.txt index 6180a787..efc8dcbc 100644 --- a/doc/rest-nvim.txt +++ b/doc/rest-nvim.txt @@ -43,6 +43,7 @@ FEATURES *rest-nvim-features* - Run request under cursor - Syntax highlight for http files and output - Possibility of using environment variables in http files +- Set environment variables based on the response =============================================================================== @@ -118,6 +119,9 @@ COMMANDS *rest-nvim-usage-commands* Same as `RestNvim` but it returns the cURL command without executing the request. Intended for debugging purposes. +- `:RestSelectEnv path/to/env` + Set the path to an env file. + =============================================================================== REQUESTS *rest-nvim-usage-requests* @@ -151,6 +155,40 @@ These environment variables can be obtained from: - File in the current working directory (env_file in config or '.env') - System +Environment variables can be set in .env format or in json. + +To change the environment for the session use :RestSelectEnv path/to/environment + +Environment variables can be set dynamically from the response body. +(see rest-nvim-usage-dynamic-variables) + + +=============================================================================== +RESPONSE SCRIPT *rest-nvim-response-script* + +A lua script can be run after a request has completed. This script must below +the body and wrapped in {% script %}. A context table is avaliable in the +response script. The context table can be used to read the response and set +environment variables. + +The context table: +`{` +` result = res,` +` pretty_print = vim.pretty_print,` +` json_decode = vim.fn.json_decode,` +` set_env = utils.set_env,` +`}` + +Now environment variables can be set like so: + +`GET https://jsonplaceholder.typicode.com/posts/3` +` ` +`{%` +` ` +`local body = context.json_decode(context.result.body)` +`context.set_env("postId", body.id)` +` ` +`%}` =============================================================================== DYNAMIC VARIABLES *rest-nvim-usage-dynamic-variables* diff --git a/doc/tags b/doc/tags index d902e553..eec56832 100644 --- a/doc/tags +++ b/doc/tags @@ -6,6 +6,7 @@ rest-nvim-intro rest-nvim.txt /*rest-nvim-intro* rest-nvim-issues rest-nvim.txt /*rest-nvim-issues* rest-nvim-license rest-nvim.txt /*rest-nvim-license* rest-nvim-quick-start rest-nvim.txt /*rest-nvim-quick-start* +rest-nvim-response-script rest-nvim.txt /*rest-nvim-response-script* rest-nvim-usage rest-nvim.txt /*rest-nvim-usage* rest-nvim-usage-commands rest-nvim.txt /*rest-nvim-usage-commands* rest-nvim-usage-dynamic-variables rest-nvim.txt /*rest-nvim-usage-dynamic-variables* diff --git a/lua/rest-nvim/curl/init.lua b/lua/rest-nvim/curl/init.lua index 48f4a911..00b93bb7 100644 --- a/lua/rest-nvim/curl/init.lua +++ b/lua/rest-nvim/curl/init.lua @@ -51,7 +51,7 @@ M.get_or_create_buf = function() return new_bufnr end -local function create_callback(method, url) +local function create_callback(method, url, script_str) return function(res) if res.exit ~= 0 then log.error("[rest.nvim] " .. utils.curl_error(res.exit)) @@ -68,6 +68,21 @@ local function create_callback(method, url) end end + if script_str ~= nil then + local context = { + result = res, + pretty_print = vim.pretty_print, + json_decode = vim.fn.json_decode, + set_env = utils.set_env, + } + local env = { context = context } + setmetatable(env, { __index = _G }) + local f = load(script_str, nil, "bt", env) + if f ~= nil then + f() + end + end + if config.get("result").show_url then --- Add metadata into the created buffer (status code, date, etc) -- Request statement (METHOD URL) @@ -212,7 +227,7 @@ M.curl_cmd = function(opts) vim.api.nvim_echo({ { "[rest.nvim] Request preview:\n", "Comment" }, { curl_cmd } }, false, {}) return else - opts.callback = vim.schedule_wrap(create_callback(opts.method, opts.url)) + opts.callback = vim.schedule_wrap(create_callback(opts.method, opts.url, opts.script_str)) curl[opts.method](opts) end end diff --git a/lua/rest-nvim/init.lua b/lua/rest-nvim/init.lua index 8947d3b7..bc18112e 100644 --- a/lua/rest-nvim/init.lua +++ b/lua/rest-nvim/init.lua @@ -56,8 +56,8 @@ end rest.run_request = function(req, opts) local result = req opts = vim.tbl_deep_extend( - "force", -- use value from rightmost map - {verbose = false}, -- defaults + "force", -- use value from rightmost map + { verbose = false }, -- defaults opts or {} ) @@ -74,6 +74,7 @@ rest.run_request = function(req, opts) bufnr = result.bufnr, start_line = result.start_line, end_line = result.end_line, + script_str = result.script_str, } if not opts.verbose then @@ -118,4 +119,13 @@ end rest.request = request +rest.select_env = function(path) + if path ~= nil then + vim.validate({ path = { path, "string" } }) + config.set({ env_file = path }) + else + print("No path given") + end +end + return rest diff --git a/lua/rest-nvim/request/init.lua b/lua/rest-nvim/request/init.lua index 62256640..9b82b426 100644 --- a/lua/rest-nvim/request/init.lua +++ b/lua/rest-nvim/request/init.lua @@ -21,7 +21,7 @@ local function get_importfile_name(bufnr, start_line, stop_line) local fileimport_spliced fileimport_line = vim.api.nvim_buf_get_lines(bufnr, import_line - 1, import_line, false) fileimport_string = - string.gsub(fileimport_line[1], "<", "", 1):gsub("^%s+", ""):gsub("%s+$", "") + string.gsub(fileimport_line[1], "<", "", 1):gsub("^%s+", ""):gsub("%s+$", "") fileimport_spliced = utils.replace_vars(fileimport_string) if path:new(fileimport_spliced):is_absolute() then return fileimport_spliced @@ -56,14 +56,19 @@ local function get_body(bufnr, start_line, stop_line, has_json) end local body = "" + local vars = utils.read_variables() -- nvim_buf_get_lines is zero based and end-exclusive -- but start_line and stop_line are one-based and inclusive -- magically, this fits :-) start_line is the CRLF between header and body -- which should not be included in the body, stop_line is the last line of the body for _, line in ipairs(lines) do + -- stop if a script opening tag is found + if line:find("{%%") then + break + end -- Ignore commented lines with and without indent if not utils.contains_comments(line) then - body = body .. utils.replace_vars(line) + body = body .. utils.replace_vars(line, vars) end end @@ -80,13 +85,46 @@ local function get_body(bufnr, start_line, stop_line, has_json) json_body[key] = vim.fn.json_encode(val) end end - return json_body + return vim.fn.json_encode(json_body) end end + return body end +local function get_response_script(bufnr, start_line, stop_line) + local all_lines = vim.api.nvim_buf_get_lines(bufnr, start_line, stop_line, false) + -- Check if there is a script + local script_start_rel + for i, line in ipairs(all_lines) do + -- stop if a script opening tag is found + if line:find("{%%") then + script_start_rel = i + break + end + end + + if script_start_rel == nil then + return nil + end + + -- Convert the relative script line number to the line number of the buffer + local script_start = start_line + script_start_rel - 1 + + local script_lines = vim.api.nvim_buf_get_lines(bufnr, script_start, stop_line, false) + local script_str = "" + + for _, line in ipairs(script_lines) do + script_str = script_str .. line .. "\n" + if line:find("%%}") then + break + end + end + + return script_str:match("{%%(.-)%%}") +end + -- is_request_line checks if the given line is a http request line according to RFC 2616 local function is_request_line(line) local http_methods = { "GET", "POST", "PUT", "PATCH", "DELETE" } @@ -202,7 +240,8 @@ local function end_request(bufnr, linenumber) end utils.move_cursor(bufnr, linenumber) - local next = vim.fn.search("^GET\\|^POST\\|^PUT\\|^PATCH\\|^DELETE", "cn", vim.fn.line("$")) + local next = vim.fn.search("^GET\\|^POST\\|^PUT\\|^PATCH\\|^DELETE\\^###\\", "cn", + vim.fn.line("$")) -- restore cursor position utils.move_cursor(bufnr, oldlinenumber) @@ -296,6 +335,9 @@ M.buf_get_request = function(bufnr, curpos) content_type:find("application/[^ ]*json") ) + local script_str = get_response_script(bufnr, headers_end, end_line) + + if config.get("jump_to_request") then utils.move_cursor(bufnr, start_line) else @@ -303,17 +345,18 @@ M.buf_get_request = function(bufnr, curpos) end return true, - { - method = parsed_url.method, - url = parsed_url.url, - http_version = parsed_url.http_version, - headers = headers, - raw = curl_args, - body = body, - bufnr = bufnr, - start_line = start_line, - end_line = end_line, - } + { + method = parsed_url.method, + url = parsed_url.url, + http_version = parsed_url.http_version, + headers = headers, + raw = curl_args, + body = body, + bufnr = bufnr, + start_line = start_line, + end_line = end_line, + script_str = script_str + } end M.print_request = function(req) diff --git a/lua/rest-nvim/utils/init.lua b/lua/rest-nvim/utils/init.lua index 79c4f27b..2250013a 100644 --- a/lua/rest-nvim/utils/init.lua +++ b/lua/rest-nvim/utils/init.lua @@ -16,6 +16,41 @@ M.move_cursor = function(bufnr, line, column) end) end +M.set_env = function(key, value) + local variables = M.get_env_variables() + variables[key] = value + M.write_env_file(variables) +end + +M.write_env_file = function(variables) + local env_file = "/" .. (config.get("env_file") or ".env") + + -- Directories to search for env files + local env_file_paths = { + -- current working directory + vim.fn.getcwd() .. env_file, + -- directory of the currently opened file + vim.fn.expand("%:p:h") .. env_file, + } + + -- If there's an env file in the current working dir + for _, env_file_path in ipairs(env_file_paths) do + if M.file_exists(env_file_path) then + local file = io.open(env_file_path, "w+") + if file ~= nil then + if string.match(env_file_path, "(.-)%.json$") then + file:write(vim.fn.json_encode(variables)) + else + for key, value in pairs(variables) do + file:write(key .. "=" .. value .. "\n") + end + end + file:close() + end + end + end +end + M.uuid = function() local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" return string.gsub(template, "[xy]", function(c) @@ -43,10 +78,8 @@ M.read_file = function(file) return lines end --- get_variables Reads the environment variables found in the env_file option --- (defualt: .env) specified in configuration or from the files being read --- with variables beginning with @ and returns a table with the variables -M.get_variables = function() +-- reads the variables contained in the current file +M.get_file_variables = function() local variables = {} -- If there is a line at the beginning with @ first @@ -64,7 +97,11 @@ M.get_variables = function() end end end - + return variables +end +-- Gets the variables from the currently selected env_file +M.get_env_variables = function() + local variables = {} local env_file = "/" .. (config.get("env_file") or ".env") -- Directories to search for env files @@ -78,12 +115,39 @@ M.get_variables = function() -- If there's an env file in the current working dir for _, env_file_path in ipairs(env_file_paths) do if M.file_exists(env_file_path) then - for line in io.lines(env_file_path) do - local vars = M.split(line, "%s*=%s*", 1) - variables[vars[1]] = vars[2] + if string.match(env_file_path, "(.-)%.json$") then + local f = io.open(env_file_path, "r") + if f ~= nil then + local json_vars = f:read("*all") + variables = vim.fn.json_decode(json_vars) + f:close() + end + else + for line in io.lines(env_file_path) do + local vars = M.split(line, "%s*=%s*", 1) + variables[vars[1]] = vars[2] + end end end end + return variables +end + +-- get_variables Reads the environment variables found in the env_file option +-- (defualt: .env) specified in configuration or from the files being read +-- with variables beginning with @ and returns a table with the variables +M.get_variables = function() + local variables = {} + local file_variables = M.get_file_variables() + local env_variables = M.get_env_variables() + + for k, v in pairs(file_variables) do + variables[k] = v + end + + for k, v in pairs(env_variables) do + variables[k] = v + end -- For each variable name for name, _ in pairs(variables) do @@ -165,8 +229,10 @@ end -- replace_vars replaces the env variables fields in the provided string -- with the env variable value -- @param str Where replace the placers for the env variables -M.replace_vars = function(str) - local vars = M.read_variables() +M.replace_vars = function(str, vars) + if vars == nil then + vars = M.read_variables() + end -- remove $dotenv tags, which are used by the vscode rest client for cross compatibility str = str:gsub("%$dotenv ", ""):gsub("%$DOTENV ", "") diff --git a/plugin/rest-nvim.vim b/plugin/rest-nvim.vim index 69f7dabd..110b2d1f 100644 --- a/plugin/rest-nvim.vim +++ b/plugin/rest-nvim.vim @@ -8,6 +8,9 @@ if exists('g:loaded_rest_nvim') | finish | endif nnoremap RestNvim :lua require('rest-nvim').run() nnoremap RestNvimPreview :lua require('rest-nvim').run(true) nnoremap RestNvimLast :lua require('rest-nvim').last() +" nnoremap RestNvimSelectEnv :lua require('rest-nvim').last() + +command! -nargs=? -complete=file RestSelectEnv :lua require('rest-nvim').select_env() let s:save_cpo = &cpo set cpo&vim diff --git a/tests/post_create_user.http b/tests/post_create_user.http index 49d979e8..10a69a90 100644 --- a/tests/post_create_user.http +++ b/tests/post_create_user.http @@ -7,5 +7,5 @@ Content-Type: application/json "array": ["a", "b", "c"], "object_ugly_closing": { "some_key": "some_value" -} + } } diff --git a/tests/script_vars/.env b/tests/script_vars/.env new file mode 100644 index 00000000..d470e56b --- /dev/null +++ b/tests/script_vars/.env @@ -0,0 +1,2 @@ +postId=3 +userId=1 diff --git a/tests/script_vars/script_vars.http b/tests/script_vars/script_vars.http new file mode 100644 index 00000000..492b65de --- /dev/null +++ b/tests/script_vars/script_vars.http @@ -0,0 +1,15 @@ +# Should write the returned variables to the env file. +# Then the second request can be run +GET https://jsonplaceholder.typicode.com/posts/3 + +{% + +local body = context.json_decode(context.result.body) + +context.set_env("userId", body.userId) +context.set_env("postId", body.id) + +%} + +### +GET https://jsonplaceholder.typicode.com/posts/{{postId}} diff --git a/tests/swappable_env/.env b/tests/swappable_env/.env new file mode 100644 index 00000000..e4ac6f22 --- /dev/null +++ b/tests/swappable_env/.env @@ -0,0 +1,2 @@ +TITLE=Title from .env +BODY=Body from .env diff --git a/tests/swappable_env/.env.json b/tests/swappable_env/.env.json new file mode 100644 index 00000000..9ce9919e --- /dev/null +++ b/tests/swappable_env/.env.json @@ -0,0 +1 @@ +{"userId": 1, "TITLE": "Json TITLE", "postId": 3, "BODY": "This Body is from the json env"} \ No newline at end of file diff --git a/tests/swappable_env/swappable_env.http b/tests/swappable_env/swappable_env.http new file mode 100644 index 00000000..fce48754 --- /dev/null +++ b/tests/swappable_env/swappable_env.http @@ -0,0 +1,10 @@ +# Use command :RestSelectEnv ./tests/swappable_env/.env.json +# to select the env file for this request +POST https://jsonplaceholder.typicode.com/posts +Content-type: application/json + +{ + "title": "{{TITLE}}", + "body": "{{BODY}}", + "userId": 1 +}