From 888bd0fc18057ba0a4f207895c1bfe9828a65071 Mon Sep 17 00:00:00 2001 From: Huynh Duc Dung Date: Sat, 21 Oct 2023 19:36:50 +0800 Subject: [PATCH] feat: port hurl plugin from ray-x/web-tools.nvim --- hurl.txt | 18 +++++ lua/hurl.nvim/init.lua | 11 --- lua/hurl/init.lua | 20 ++++++ lua/hurl/utils.lua | 65 ++++++++++++++++++ lua/hurl/vlog.lua | 148 +++++++++++++++++++++++++++++++++++++++++ lua/hurl/wrapper.lua | 133 ++++++++++++++++++++++++++++++++++++ plugin/hurl.nvim.lua | 1 - test/example.hurl | 1 + test/plugin_spec.lua | 6 +- 9 files changed, 389 insertions(+), 14 deletions(-) delete mode 100644 lua/hurl.nvim/init.lua create mode 100644 lua/hurl/init.lua create mode 100644 lua/hurl/utils.lua create mode 100644 lua/hurl/vlog.lua create mode 100644 lua/hurl/wrapper.lua delete mode 100644 plugin/hurl.nvim.lua create mode 100644 test/example.hurl diff --git a/hurl.txt b/hurl.txt index de9e1c0..72c6b28 100644 --- a/hurl.txt +++ b/hurl.txt @@ -1,4 +1,22 @@ nvim +vlog +vararg +getpos +tempname +tjdevries +neovim +echohl +outfile +stdpath +nameupper +lineinfo +currentline +echom +jobstart +setqflist +copen +fargs +nargs docstrings sweepai pygithub diff --git a/lua/hurl.nvim/init.lua b/lua/hurl.nvim/init.lua deleted file mode 100644 index 2859b60..0000000 --- a/lua/hurl.nvim/init.lua +++ /dev/null @@ -1,11 +0,0 @@ ---- @module hurl.nvim ---- Plugin entrypoint ----@return boolean -local function setup() - print('hello world') - return true -end - -return { - setup = setup, -} diff --git a/lua/hurl/init.lua b/lua/hurl/init.lua new file mode 100644 index 0000000..804003b --- /dev/null +++ b/lua/hurl/init.lua @@ -0,0 +1,20 @@ +--- Global configuration for hurl.nvim +_HURL_CFG = { + -- Debug mode + -- Default: false + debug = false, + --- Show headers in the response buffer + --- Default: false + show_headers = false, +} +local M = {} + +function M.setup(options) + _HURL_CFG = vim.tbl_extend('force', _HURL_CFG, options or {}) + + require('hurl.utils').log('hurl.nvim loaded') + + require('hurl.wrapper').setup() +end + +return M diff --git a/lua/hurl/utils.lua b/lua/hurl/utils.lua new file mode 100644 index 0000000..88ee72c --- /dev/null +++ b/lua/hurl/utils.lua @@ -0,0 +1,65 @@ +local log = require('hurl.vlog') + +local util = {} + +--- Log function +--- Print the variable to stdout +---@vararg any +util.log = function(...) + -- Only print when debug is on + if not _HURL_CFG.debug then + return + end + + log.info(...) +end + +--- Get visual selection +---@return string[] +util.get_visual_selection = function() + local s_start = vim.fn.getpos("'<") + local s_end = vim.fn.getpos("'>") + local n_lines = math.abs(s_end[2] - s_start[2]) + 1 + local lines = vim.api.nvim_buf_get_lines(0, s_start[2] - 1, s_end[2], false) + lines[1] = string.sub(lines[1], s_start[3], -1) + if n_lines == 1 then + lines[n_lines] = string.sub(lines[n_lines], 1, s_end[3] - s_start[3] + 1) + else + lines[n_lines] = string.sub(lines[n_lines], 1, s_end[3]) + end + return lines +end + +--- Create tmp file +---@param content any +---@return string|nil +util.create_tmp_file = function(content) + local tmp_file = vim.fn.tempname() + if not tmp_file then + return + end + + local f = io.open(tmp_file, 'w') + if not f then + return + end + if type(content) == 'table' then + local c = vim.fn.join(content, '\n') + f:write(c) + else + f:write(content) + end + f:close() + return tmp_file +end + +--- Create custom command +---@param cmd string The command name +---@param func function The function to execute +---@param opt table The options +util.create_cmd = function(cmd, func, opt) + opt = vim.tbl_extend('force', { desc = 'hurl.nvim ' .. cmd }, opt or {}) + vim.api.nvim_create_user_command(cmd, func, opt) +end + +return util diff --git a/lua/hurl/vlog.lua b/lua/hurl/vlog.lua new file mode 100644 index 0000000..b216160 --- /dev/null +++ b/lua/hurl/vlog.lua @@ -0,0 +1,148 @@ +-- -- log.lua +-- +-- Inspired by rxi/log.lua +-- Modified by tjdevries and can be found at github.com/tjdevries/vlog.nvim +-- +-- This library is free software; you can redistribute it and/or modify it +-- under the terms of the MIT license. See LICENSE for details. + +-- User configuration section +local default_config = { + -- Name of the plugin. Prepended to log messages + plugin = 'hurl.nvim', + + -- Should print the output to neovim while running + use_console = true, + + -- Should highlighting be used in console (using echohl) + highlights = true, + + -- Should write to a file + use_file = true, + + -- Any messages above this level will be logged. + level = 'trace', + + -- Level configuration + modes = { + { name = 'trace', hl = 'Comment' }, + { name = 'debug', hl = 'Comment' }, + { name = 'info', hl = 'None' }, + { name = 'warn', hl = 'WarningMsg' }, + { name = 'error', hl = 'ErrorMsg' }, + { name = 'fatal', hl = 'ErrorMsg' }, + }, + + -- Can limit the number of decimals displayed for floats + float_precision = 0.01, +} + +-- {{{ NO NEED TO CHANGE +local log = {} + +local unpack = unpack or table.unpack + +log.new = function(config, standalone) + config = vim.tbl_deep_extend('force', default_config, config) + + local outfile = + string.format('%s/%s.log', vim.api.nvim_call_function('stdpath', { 'data' }), config.plugin) + + local obj + if standalone then + obj = log + else + obj = {} + end + + local levels = {} + for i, v in ipairs(config.modes) do + levels[v.name] = i + end + + local round = function(x, increment) + increment = increment or 1 + x = x / increment + return (x > 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)) * increment + end + + local make_string = function(...) + local t = {} + for i = 1, select('#', ...) do + local x = select(i, ...) + + if type(x) == 'number' and config.float_precision then + x = tostring(round(x, config.float_precision)) + elseif type(x) == 'table' then + x = vim.inspect(x) + else + x = tostring(x) + end + + t[#t + 1] = x + end + return table.concat(t, ' ') + end + + local log_at_level = function(level, level_config, message_maker, ...) + -- Return early if we're below the config.level + if level < levels[config.level] then + return + end + local nameupper = level_config.name:upper() + + local msg = message_maker(...) + local info = debug.getinfo(2, 'Sl') + local lineinfo = info.short_src .. ':' .. info.currentline + + -- Output to console + if config.use_console then + local console_string = + string.format('[%-6s%s] %s: %s', nameupper, os.date('%H:%M:%S'), lineinfo, msg) + + if config.highlights and level_config.hl then + vim.cmd(string.format('echohl %s', level_config.hl)) + end + + local split_console = vim.split(console_string, '\n') + for _, v in ipairs(split_console) do + vim.cmd(string.format([[echom "[%s] %s"]], config.plugin, vim.fn.escape(v, '"'))) + end + + if config.highlights and level_config.hl then + vim.cmd('echohl NONE') + end + end + + -- Output to log file + if config.use_file then + local fp = io.open(outfile, 'a') + local str = string.format('[%-6s%s] %s: %s\n', nameupper, os.date(), lineinfo, msg) + fp:write(str) + fp:close() + end + end + + for i, x in ipairs(config.modes) do + obj[x.name] = function(...) + return log_at_level(i, x, make_string, ...) + end + + obj[('fmt_%s'):format(x.name)] = function() + return log_at_level(i, x, function(...) + local passed = { ... } + local fmt = table.remove(passed, 1) + local inspected = {} + for _, v in ipairs(passed) do + table.insert(inspected, vim.inspect(v)) + end + return string.format(fmt, unpack(inspected)) + end) + end + end +end + +log.new(default_config, true) +-- }}} +-- +return log diff --git a/lua/hurl/wrapper.lua b/lua/hurl/wrapper.lua new file mode 100644 index 0000000..77b8739 --- /dev/null +++ b/lua/hurl/wrapper.lua @@ -0,0 +1,133 @@ +local util = require('hurl.utils') + +local M = {} + +local response = {} + +--- Output handler +---@class Output +local on_output = function(code, data, event) + local head_state + if data[1] == '' then + table.remove(data, 1) + end + if not data[1] then + util.log('no data') + return + end + + if event == 'stderr' and #data > 1 then + util.log('stderr', data) + response.body = data + response.raw = data + response.headers = {} + return + end + local status = tonumber(string.match(data[1], '([%w+]%d+)')) + head_state = 'start' + if status then + response.status = status + response.headers = { status = data[1] } + response.headers_str = data[1] .. '\r\n' + end + for i = 2, #data do + local line = data[i] + if line == '' or line == nil then + util.log(i, 'change to body') + head_state = 'body' + elseif head_state == 'start' then + local key, value = string.match(line, '([%w-]+):%s*(.+)') + if key and value then + response.headers[key] = value + response.headers_str = response.headers_str .. line .. '\r\n' + end + elseif head_state == 'body' then + response.body = response.body or '' + response.body = response.body .. line + end + end + response.raw = data + + util.log(response) + vim.notify('hurl: response status ' .. response.status) +end + +--- Call hurl command +---@param opts table The options +---@param callback function The callback function +local function request(opts, callback) + local cmd = vim.list_extend({ 'hurl', '-i', '--no-color' }, opts) + response = {} + + vim.fn.jobstart(cmd, { + on_stdout = on_output, + on_stderr = on_output, + on_exit = function(i, code) + util.log('exit', i, code) + if code ~= 0 then + vim.notify( + string.format( + 'hurl: %s error exit_code=%s response=%s', + vim.inspect(cmd), + code, + vim.inspect(response) + ) + ) + end + + util.log(response) + if callback then + return callback(response) + else + -- show messages + local lines = response.raw or response.body + if #lines == 0 then + return + end + -- Send to quick fix and open it + vim.fn.setqflist({}, ' ', { + title = 'hurl finished', + lines = lines, + }) + vim.cmd('copen') + end + end, + }) +end + +local function run_current_file(opts) + opts = opts or {} + table.insert(opts, vim.fn.expand('%:p')) + request(opts) +end + +local function run_selection(opts, range) + opts = opts or {} + local lines = util.get_visual_selection() + if not lines then + return + end + local fname = util.create_tmp_file(lines) + + if not fname then + return + end + + table.insert(opts, fname) + request(opts) + vim.defer_fn(function() + os.remove(fname) + end, 1000) +end + +function M.setup() + util.create_cmd('HurlRun', function(opts) + if opts.range ~= 0 then + run_selection(opts.fargs, opts.range) + else + run_current_file(opts.fargs) + end + end, { nargs = '*', range = true }) +end + +return M diff --git a/plugin/hurl.nvim.lua b/plugin/hurl.nvim.lua deleted file mode 100644 index 5543e2d..0000000 --- a/plugin/hurl.nvim.lua +++ /dev/null @@ -1 +0,0 @@ -print('loading hurl.nvim plugin') diff --git a/test/example.hurl b/test/example.hurl new file mode 100644 index 0000000..bd53cde --- /dev/null +++ b/test/example.hurl @@ -0,0 +1 @@ +GET https://api.mangadex.org/statistics/manga/8b34f37a-0181-4f0b-8ce3-01217e9a602c diff --git a/test/plugin_spec.lua b/test/plugin_spec.lua index 796eb4b..2aecb72 100644 --- a/test/plugin_spec.lua +++ b/test/plugin_spec.lua @@ -1,5 +1,7 @@ +local hurl = require('hurl') + describe('Hurl.nvim plugin', function() - it('work as expect', function() - assert.is_true(true) + it('should be able to load', function() + assert.truthy(hurl) end) end)