Skip to content

Commit b36b59c

Browse files
committed
feat(parser): shared block
1 parent 2260121 commit b36b59c

File tree

14 files changed

+627
-36
lines changed

14 files changed

+627
-36
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Version 5.3.4
44

5+
### Feature: `Shared blocks` [Shared blocks](usage/shared-blocks.md)
56
### Enhancement: add `winbar_labels` and `winbar_labels_keymaps` config options to customize winbar labels [Configuration](getting-started/configuration-options.mdx)
67
### Feature: support for `custom_dynamic_variables`
78
### Feature: support `run` command with metadata and without URL [Import and Run](usage/import-and-run-http.md)

docs/docs/getting-started/configuration-options.mdx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ Example:
147147
}
148148
```
149149

150+
### variables_scope
151+
152+
Set variable scope: "document" - variables shared across all requests in the document, no matter where they are defined, later declarations shadow earlier ones.
153+
"request" - variables scoped to the request they are defined in, variables in Shared block are immutable.
154+
155+
Default: `document`
156+
150157
### custom_dynamic_variables
151158

152159
Define your own dynamic variables here, e.g. $randomEmail

docs/docs/usage/shared-blocks.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Shared Blocks
2+
3+
Shared blocks can be used to share variables, metadata, scripts and requests between multiple requests.
4+
5+
To declare a shared block, use the `### Shared` or `### Shared each` request name.
6+
7+
Shared variables and metadata will apply to all requests that follow the shared block.
8+
Variables and metadata declared in a request will shadow shared variables and metadata.
9+
10+
Scripts and requests declared in the shared block and called with `run` command will be executed before the request you run.
11+
12+
## `### Shared` block
13+
14+
- When executing `run request`, the shared scripts and requests will be executed before the request you run.
15+
- When executing `run all requests`, the shared scripts and requests will be executed `once` before all requests.
16+
17+
## `### Shared each` block
18+
19+
- When executing `run request`, the shared scripts and requests will be executed before the request you run.
20+
- When executing `run all requests`, the shared scripts and requests will be executed before `each` request.
21+
22+
## Variable Scope
23+
24+
By default variables are scoped to `document`, which means they are shared across all requests in the document
25+
and later declarations will override previous ones, including the ones in shared blocks.
26+
27+
You can change the scope to `variables_scope = "request"` in the options, which will make variables scoped to the current request only
28+
and shared variables will not be overridden by request variables.
29+
30+
```http
31+
### Shared
32+
33+
@shared_var_1 = shared_value_1
34+
@shared_var_2 = shared_value_2
35+
36+
# @curl-connect-timeout 20
37+
# @curl-location
38+
39+
run ./login.http
40+
41+
< {%
42+
console.log("pre request 0");
43+
%}
44+
45+
< ./pre_request.js
46+
47+
POST https://httpbin.org/post HTTP/1.1
48+
Content-Type: application/json
49+
50+
{
51+
"shared_var_1": 1,
52+
"shared_var_2": 2
53+
}
54+
55+
> ./post_request.js
56+
57+
> {%
58+
console.log("post request 0");
59+
%}
60+
61+
62+
### request 1
63+
64+
@local_var_1 = local_value_1
65+
@shared_var_2 = local_value_2
66+
67+
# @curl-connect-timeout 10
68+
69+
POST https://httpbin.org/post HTTP/1.1
70+
Content-Type: application/json
71+
72+
{
73+
"shared_var_1": 3,
74+
"shared_var_2": 4
75+
}
76+
```

docs/docs/usage/using-variables.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,10 @@ you will be prompted to enter a value for `pokemon`.
3838

3939
These variables are available for the current request and
4040
all subsequent requests in the file.
41+
42+
## Variables scope
43+
44+
By default, variables are scoped to the entire document, i.e. they are available in all requests in the file,
45+
no matter where they are declared and later declarations will override earlier ones.
46+
47+
You can change the scope to `variables_scope = "request"` in the options, which will make variables scoped to the current request only.

docs/sidebars.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
1+
import type { SidebarsConfig } from '@docusaurus/plugin-content-docs';
22

33
const sidebars: SidebarsConfig = {
44
defaultSidebar: [
@@ -38,6 +38,7 @@ const sidebars: SidebarsConfig = {
3838
"usage/magic-variables",
3939
"usage/authentication",
4040
"usage/import-and-run-http",
41+
"usage/shared-blocks",
4142
"usage/reading-file-data",
4243
"usage/filter-response",
4344
"usage/redirect-the-response",

lua/kulala/cmd/init.lua

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -330,12 +330,12 @@ local function process_pre_request_commands(request)
330330
local processor
331331
for _, metadata in ipairs(request.metadata) do
332332
processor = int_meta_processors[metadata.name]
333-
_ = processor and INT_PROCESSING[processor](metadata.value, response)
333+
_ = processor and INT_PROCESSING[processor](metadata.value)
334334
end
335335

336336
for _, metadata in ipairs(request.metadata) do
337337
processor = ext_meta_processors[metadata.name]
338-
_ = processor and EXT_PROCESSING[processor](metadata.value, response)
338+
_ = processor and EXT_PROCESSING[processor](metadata.value)
339339
end
340340

341341
return true
@@ -345,9 +345,14 @@ local function parse_request(requests, request, variables)
345345
if not process_pre_request_commands(request) then return end
346346

347347
local parsed_request, status = REQUEST_PARSER.parse(requests, variables, request)
348+
348349
if not parsed_request then
349-
status = status == "skipped" and "is skipped" or "could not be parsed"
350-
return Logger.warn(("Request at line: %s " .. status):format(request.start_line or request.show_icon_line_number))
350+
if status == "empty" then return status end
351+
352+
local msg = status == "skipped" and "is skipped" or "could not be parsed"
353+
Logger.warn(("Request at line: %s " .. msg):format(request.start_line or request.show_icon_line_number))
354+
355+
return status
351356
end
352357

353358
return parsed_request
@@ -383,6 +388,8 @@ function process_request(requests, request, variables, callback)
383388
handle_response = vim.schedule_wrap(handle_response)
384389

385390
local parsed_request = parse_request(requests, request, variables)
391+
392+
if parsed_request == "empty" or parsed_request == "skipped" then return M.queue:run_next() end
386393
if not parsed_request then
387394
callback(false, 0, request.start_line)
388395
return config.halt_on_error and M.queue:reset() or M.queue:run_next()
@@ -438,7 +445,7 @@ end
438445

439446
---Parses and executes DocumentRequest/s:
440447
---if requests is nil then it parses the current document
441-
---if line_nr is nil then runs the first request in the list
448+
---if line_nr is nil then runs the first request in the list (used for replaying last request)
442449
---if line_nr > 0 then runs the request from current buffer around the line number
443450
---if line_nr is 0 then runs all or visually selected requests
444451
---@param requests? DocumentRequest[]|nil

lua/kulala/config/defaults.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ local M = {
2222
-- enable reading vscode rest client environment variables
2323
vscode_rest_client_environmentvars = false,
2424

25+
-- set variable scope: document or request
26+
variables_scope = "document", ---@type "document"|"request"
2527
-- define your own dynamic variables here, e.g. $randomEmail
2628
custom_dynamic_variables = {}, ---@type { [string]: fun():string }
2729

lua/kulala/formatter/formatter.lua

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ format_rules = {
258258
insert_formatted(#section.variables > 0, table.concat(section.variables, "\n"), "variable_declaration")
259259
insert_formatted(#section.commands > 0, table.concat(section.commands, "\n"), "command")
260260
insert_formatted(#section.metadata > 0, table.concat(section.metadata, "\n"), "metadata")
261+
-- force parsing request child node, when there is no request node, like in Shared section
262+
if #section.request.formatted == 0 then format_rules["request"](node) end
261263
insert_formatted(#section.request.formatted > 0, section.request.formatted, "request")
262264

263265
section.formatted = section.formatted:gsub("\n*$", "")
@@ -344,9 +346,11 @@ format_rules = {
344346
or (method ~= "GRPC" and method ~= "WEBSOCKET" and method ~= "WS" and "HTTP/1.1" or "")
345347

346348
local request = current_section().request
347-
request.url = ("%s %s %s"):format(method, target_url, http_version)
348349

349-
format_children(node)
350+
if #target_url > 0 then -- format url and children only if url is present
351+
request.url = ("%s %s %s"):format(method, target_url, http_version)
352+
format_children(node)
353+
end
350354

351355
_ = #request.pre_request_script > 0
352356
and table.insert(formatted, table.concat(request.pre_request_script, "\n\n") .. "\n")

lua/kulala/parser/document.lua

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
local Config = require("kulala.config")
12
local DB = require("kulala.db")
23
local Diagnostics = require("kulala.cmd.diagnostics")
34
local FS = require("kulala.utils.fs")
@@ -9,7 +10,9 @@ local Table = require("kulala.utils.table")
910
local M = {}
1011

1112
---@class DocumentRequest
13+
---@field shared DocumentRequest
1214
---@field metadata table<{name: string, value: string}>
15+
---@field variables table<{name: string, value: string|number|boolean}>
1316
---@field comments string[]
1417
---
1518
---@field method string
@@ -53,7 +56,10 @@ local M = {}
5356

5457
---@type DocumentRequest
5558
local default_document_request = {
59+
---@diagnostic disable-next-line: missing-fields
60+
shared = {},
5661
metadata = {},
62+
variables = {},
5763
comments = {},
5864
method = "",
5965
url = "",
@@ -179,15 +185,17 @@ local function parse_redirect_response(request, line)
179185
})
180186
end
181187

182-
-- Variable
183188
-- Variables are defined as `@variable_name=value`
184189
-- The value can be a string, a number or boolean
185-
local function parse_variables(variables, line)
190+
local function parse_variables(request, variables, line)
186191
local variable_name, variable_value = line:match("^@([%w_]+)%s*=%s*(.*)$")
187192
if variable_name and variable_value then
188193
-- remove the @ symbol from the variable name
189194
variable_name = variable_name:sub(1)
190195
variables[variable_name] = variable_value
196+
197+
request.variables[variable_name] = variable_value
198+
if Config.options.variables_scope == "document" then request.shared.variables[variable_name] = variable_value end
191199
end
192200
end
193201

@@ -361,7 +369,7 @@ local function parse_run_command(requests, imported_requests, variables, request
361369
end)
362370

363371
vim.iter(vim.split(variables_to_replace or "", ",%s*")):each(function(variable)
364-
parse_variables(variables, variable)
372+
parse_variables(request, variables, variable)
365373
end)
366374
end
367375

@@ -409,7 +417,10 @@ function parse_document(lines, path)
409417

410418
if not content_lines then return end
411419

412-
local variables = {}
420+
local shared = vim.deepcopy(default_document_request)
421+
shared.url = nil
422+
423+
local variables = {} -- TODO: remove
413424
local requests = {}
414425
local imported_requests = {}
415426
local blocks = split_content_by_blocks(content_lines, line_offset)
@@ -426,6 +437,7 @@ function parse_document(lines, path)
426437
request.end_line = block.end_lnum
427438
request.name = block.name
428439
request.file = path or vim.fn.fnamemodify(vim.fn.bufname(DB.get_current_buffer()), ":p")
440+
request.shared = shared
429441

430442
for relative_linenr, line in ipairs(block.lines) do
431443
local lnum = request.start_line + relative_linenr
@@ -454,7 +466,7 @@ function parse_document(lines, path)
454466
elseif is_prerequest_handler_script_inline then
455467
request.scripts.pre_request.priority = request.scripts.pre_request.priority or "inline"
456468
table.insert(request.scripts.pre_request.inline, line)
457-
-- we're still in(/before) the request line and we have a pre-request inline handler script
469+
-- we're still in(/before) the request line and we have a pre-request inline handler script
458470
elseif is_request_line and line:match("^< %{%%$") then
459471
is_prerequest_handler_script_inline = true
460472
-- we're still in(/before) the request line and we have a pre-request file handler script
@@ -476,7 +488,7 @@ function parse_document(lines, path)
476488
local scriptfile = line:match("^> (.*)$")
477489
table.insert(request.scripts.post_request.files, scriptfile)
478490
elseif line:match("^@([%w_]+)") then
479-
parse_variables(variables, line)
491+
parse_variables(request, variables, line)
480492
elseif is_body_section then
481493
parse_body(request, line, lnum)
482494
elseif not is_request_line and line:match("^%s*/.+") then
@@ -502,13 +514,15 @@ function parse_document(lines, path)
502514
infer_headers_from_body(request)
503515
end
504516

505-
if request.url and #request.url > 0 then
517+
if request.name == "Shared" or request.name == "Shared each" then
518+
shared = request
519+
shared.url = #shared.url > 0 and shared.url or nil
520+
elseif request.url and #request.url > 0 then
506521
table.insert(requests, request)
507522
elseif #request.nested_requests > 0 then
508523
vim.iter(request.nested_requests or {}):each(function(r)
509524
r.metadata = vim.tbl_extend("force", r.metadata, request.metadata)
510-
r.start_line = request.start_line
511-
r.end_line = request.end_line
525+
r.start_line, r.end_line = request.start_line, request.end_line
512526
end)
513527
vim.list_extend(requests, request.nested_requests)
514528
end
@@ -533,16 +547,58 @@ M.get_document = function(lines, path)
533547
return unpack(result)
534548
end
535549

550+
local function apply_shared_data(shared, request)
551+
local request_metadata = vim
552+
.iter(request.metadata)
553+
:map(function(metadata)
554+
return metadata.name
555+
end)
556+
:totable()
557+
558+
vim.iter(shared.metadata):each(function(metadata)
559+
if not vim.tbl_contains(request_metadata, metadata.name) then table.insert(request.metadata, metadata) end
560+
end)
561+
562+
vim.iter(shared.variables):each(function(k, v)
563+
if not request.variables[k] then request.variables[k] = v end
564+
end)
565+
566+
return request
567+
end
568+
569+
local function is_runnable(request)
570+
local pre_scripts = request.scripts.pre_request
571+
return request.url or #pre_scripts.inline + #pre_scripts.files > 0 or #request.nested_requests > 0
572+
end
573+
536574
local function expand_nested_requests(requests, lnum)
537575
requests = vim.islist(requests) and requests or { requests }
538576

539577
local expanded = {}
578+
local shared = requests[1].shared
579+
580+
if not requests[1].name:match("Shared") and is_runnable(shared) then
581+
if shared.name == "Shared each" then
582+
local requests_ = vim.deepcopy(requests)
583+
requests = {}
584+
585+
vim.iter(requests_):each(function(request)
586+
table.insert(requests, shared)
587+
table.insert(requests, request)
588+
end)
589+
else
590+
table.insert(requests, 1, shared)
591+
end
592+
end
540593

541594
vim.iter(requests):each(function(request)
595+
request = apply_shared_data(shared, request)
596+
542597
vim.iter(request.nested_requests):each(function(nested_request)
543598
nested_request.show_icon_line_number = lnum or nested_request.show_icon_line_number
544599
vim.list_extend(expanded, expand_nested_requests(nested_request, nested_request.show_icon_line_number))
545600
end)
601+
546602
table.insert(expanded, request)
547603
end)
548604

0 commit comments

Comments
 (0)