[mirror] Make your go dev experience better github.com/olexsmir/gopher.nvim
neovim golang

feat: json2go (#130)

* feat(json2go): implement

* feat(json2go): support manual input

* chore(readme): add json2go

* chore(docs): add docs

authored by olexsmir.xyz and committed by GitHub 6a3924ce 4a2384ad

+28
README.md
··· 62 62 - [impl](https://github.com/josharian/impl) 63 63 - [gotests](https://github.com/cweill/gotests) 64 64 - [iferr](https://github.com/koron/iferr) 65 + - [json2go](https://github.com/olexsmir/json2go) 65 66 </details> 66 67 67 68 <details> ··· 185 186 ``` 186 187 </details> 187 188 189 + <details> 190 + <summary> 191 + <b>Convert json to Go types</b> 192 + </summary> 193 + 194 + ![Convert JSON to Go types](./vhs/json2go.gif) 195 + 196 + `:GoJson` opens a temporary buffer where you can paste or write JSON. 197 + Saving the buffer (`:w` or `:wq`) automatically closes it and inserts the generated Go struct into the original buffer at the cursor position. 198 + 199 + Alternatively, you can pass JSON directly as an argument: 200 + ```vim 201 + :GoJson {"name": "Alice", "age": 30} 202 + ``` 203 + 204 + Additionally, `gopher.json2go` provides lua api, see `:h gopher.nvim-json2go` for details. 205 + </details> 206 + 188 207 189 208 <details> 190 209 <summary> ··· 252 271 -- choose a custom error message, nil to use default 253 272 -- e.g: `message = 'fmt.Errorf("failed to %w", err)'` 254 273 message = nil, 274 + }, 275 + json2go = { 276 + -- command used to open interactive input. 277 + -- e.g: `split`, `botright split`, `tabnew` 278 + interactive_cmd = "vsplit", 279 + 280 + -- name of autogenerated struct 281 + -- e.g: "MySuperCoolName" 282 + type_name = nil, 255 283 }, 256 284 } 257 285 ```
+50
doc/gopher.nvim.txt
··· 13 13 Config ................................................ |gopher.nvim-config| 14 14 Commands ............................................ |gopher.nvim-commands| 15 15 Modify struct tags ............................... |gopher.nvim-struct-tags| 16 + json2go .............................................. |gopher.nvim-json2go| 16 17 Auto implementation of interface methods ................ |gopher.nvim-impl| 17 18 Generating unit tests boilerplate .................... |gopher.nvim-gotests| 18 19 Iferr .................................................. |gopher.nvim-iferr| ··· 69 70 gotests = "gotests", 70 71 impl = "impl", 71 72 iferr = "iferr", 73 + json2go = "json2go", 72 74 }, 73 75 ---@class gopher.ConfigGotests 74 76 gotests = { ··· 96 98 ---@type string|nil 97 99 option = nil, 98 100 }, 101 + ---@class gopher.ConfigIfErr 99 102 iferr = { 100 103 -- choose a custom error message, nil to use default 101 104 -- e.g: `message = 'fmt.Errorf("failed to %w", err)'` 102 105 ---@type string|nil 103 106 message = nil, 104 107 }, 108 + ---@class gopher.ConfigJson2Go 109 + json2go = { 110 + -- command used to open interactive input. 111 + -- e.g: `split`, `botright split`, `tabnew` 112 + interactive_cmd = "vsplit", 113 + 114 + -- name of autogenerated struct, if nil none, will the default one of json2go. 115 + -- e.g: "MySuperCoolName" 116 + ---@type string|nil 117 + type_name = nil, 118 + }, 105 119 } 106 120 < 107 121 Class ~ ··· 156 170 Name string `yaml:name` 157 171 } 158 172 < 173 + 174 + ============================================================================== 175 + ------------------------------------------------------------------------------ 176 + *gopher.nvim-json2go* 177 + 178 + Convert json to go type annotations. 179 + 180 + Usage ~ 181 + 182 + `:GoJson` opens a temporary buffer where you can paste or write JSON. 183 + Saving the buffer (`:w` or `:wq`) automatically closes it and inserts the 184 + generated Go struct into the original buffer at the cursor position. 185 + 186 + Alternatively, you can pass JSON directly as an argument: 187 + >vim 188 + :GoJson {"name": "Alice", "age": 30} 189 + < 190 + ------------------------------------------------------------------------------ 191 + *json2go.transform()* 192 + `json2go.transform`({json_str}) 193 + 194 + Parameters ~ 195 + {json_str} `(string)` Json string that is going to be converted to go type. 196 + Return ~ 197 + `(string)` `(optional)` Go type, or nil if failed. 198 + 199 + ------------------------------------------------------------------------------ 200 + *json2go.json2go()* 201 + `json2go.json2go`({json_str}) 202 + Converts json string to go type, and puts result under the cursor. If 203 + [json_str] is nil, will open an interactive prompt (with cmd set in 204 + config). 205 + 206 + Parameters ~ 207 + {json_str} `(optional)` `(string)` 208 + 159 209 160 210 ============================================================================== 161 211 ------------------------------------------------------------------------------
+16
lua/gopher/config.lua
··· 41 41 gotests = "gotests", 42 42 impl = "impl", 43 43 iferr = "iferr", 44 + json2go = "json2go", 44 45 }, 45 46 ---@class gopher.ConfigGotests 46 47 gotests = { ··· 68 69 ---@type string|nil 69 70 option = nil, 70 71 }, 72 + ---@class gopher.ConfigIfErr 71 73 iferr = { 72 74 -- choose a custom error message, nil to use default 73 75 -- e.g: `message = 'fmt.Errorf("failed to %w", err)'` 74 76 ---@type string|nil 75 77 message = nil, 76 78 }, 79 + ---@class gopher.ConfigJson2Go 80 + json2go = { 81 + -- command used to open interactive input. 82 + -- e.g: `split`, `botright split`, `tabnew` 83 + interactive_cmd = "vsplit", 84 + 85 + -- name of autogenerated struct, if nil none, will the default one of json2go. 86 + -- e.g: "MySuperCoolName" 87 + ---@type string|nil 88 + type_name = nil, 89 + }, 77 90 } 78 91 --minidoc_afterlines_end 79 92 ··· 104 117 vim.validate("commands.gotests", _config.commands.gotests, "string") 105 118 vim.validate("commands.impl", _config.commands.impl, "string") 106 119 vim.validate("commands.iferr", _config.commands.iferr, "string") 120 + vim.validate("commands.json2go", _config.commands.json2go, "string") 107 121 vim.validate("gotests", _config.gotests, "table") 108 122 vim.validate("gotests.template", _config.gotests.template, "string") 109 123 vim.validate("gotests.template_dir", _config.gotests.template_dir, { "string", "nil" }) ··· 114 128 vim.validate("gotag.option", _config.gotag.option, { "string", "nil" }) 115 129 vim.validate("iferr", _config.iferr, "table") 116 130 vim.validate("iferr.message", _config.iferr.message, { "string", "nil" }) 131 + vim.validate("json2go.installer_timeout", _config.json2go.interactive_cmd, "string") 132 + vim.validate("json2go.type_name", _config.json2go.type_name, { "string", "nil" }) 117 133 end 118 134 119 135 setmetatable(config, {
+1
lua/gopher/installer.lua
··· 9 9 impl = "github.com/josharian/impl@latest", 10 10 gotests = "github.com/cweill/gotests/...@develop", 11 11 iferr = "github.com/koron/iferr@latest", 12 + json2go = "olexsmir.xyz/json2go/cmd/json2go@latest", 12 13 } 13 14 14 15 ---@param opt vim.SystemCompleted
+137
lua/gopher/json2go.lua
··· 1 + ---@toc_entry json2go 2 + ---@tag gopher.nvim-json2go 3 + ---@text 4 + --- Convert json to go type annotations. 5 + --- 6 + ---@usage 7 + --- `:GoJson` opens a temporary buffer where you can paste or write JSON. 8 + --- Saving the buffer (`:w` or `:wq`) automatically closes it and inserts the 9 + --- generated Go struct into the original buffer at the cursor position. 10 + --- 11 + --- Alternatively, you can pass JSON directly as an argument: 12 + --- >vim 13 + --- :GoJson {"name": "Alice", "age": 30} 14 + --- < 15 + 16 + local c = require "gopher.config" 17 + local log = require "gopher._utils.log" 18 + local u = require "gopher._utils" 19 + local r = require "gopher._utils.runner" 20 + local json2go = {} 21 + 22 + ---@dochide 23 + ---@param bufnr integer 24 + ---@param cpos integer 25 + ---@param type_ string 26 + local function apply(bufnr, cpos, type_) 27 + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) 28 + local input_lines = u.remove_empty_lines(vim.split(type_, "\n")) 29 + for i, line in pairs(input_lines) do 30 + table.insert(lines, cpos + i, line) 31 + end 32 + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines) 33 + end 34 + 35 + -- Convert json string to go type. 36 + --- 37 + ---@param json_str string Json string that is going to be converted to go type. 38 + ---@return string? Go type, or nil if failed. 39 + function json2go.transform(json_str) 40 + local cmd = { c.commands.json2go } 41 + if c.json2go.type_name then 42 + table.insert(cmd, "-type", c.json2go.type_name) 43 + end 44 + 45 + local rs = r.sync(cmd, { stdin = json_str }) 46 + if rs.code ~= 0 then 47 + u.notify("json2go: got this error: " .. rs.stdout, vim.log.levels.ERROR) 48 + log.error("json2go: got this error: " .. rs.stdout) 49 + return 50 + end 51 + return rs.stdout 52 + end 53 + 54 + ---@dochide 55 + ---@param ocpos integer 56 + local function interactive(ocpos) 57 + local obuf = vim.api.nvim_get_current_buf() 58 + local owin = vim.api.nvim_get_current_win() 59 + 60 + -- setup the input window 61 + local buf = vim.api.nvim_create_buf(false, true) 62 + vim.cmd(c.json2go.interactive_cmd) 63 + 64 + local win = vim.api.nvim_get_current_win() 65 + vim.api.nvim_win_set_buf(win, buf) 66 + vim.api.nvim_buf_set_name(buf, "[GoJson input]") 67 + vim.api.nvim_set_option_value("filetype", "jsonc", { buf = buf }) 68 + vim.api.nvim_set_option_value("buftype", "acwrite", { buf = buf }) 69 + vim.api.nvim_set_option_value("swapfile", false, { buf = buf }) 70 + vim.api.nvim_set_option_value("bufhidden", "delete", { buf = buf }) 71 + vim.api.nvim_buf_set_lines(buf, 0, -1, false, { 72 + "// Write your json here.", 73 + "// Writing and quitting (:wq), will generate go struct under the cursor.", 74 + "", 75 + "", 76 + }) 77 + 78 + vim.api.nvim_create_autocmd("BufLeave", { buffer = buf, command = "stopinsert" }) 79 + vim.api.nvim_create_autocmd("BufWriteCmd", { 80 + buffer = buf, 81 + once = true, 82 + callback = function() 83 + local input = vim.api.nvim_buf_get_lines(buf, 0, -1, true) 84 + local inp = table.concat( 85 + vim 86 + .iter(input) 87 + :filter(function(line) 88 + local found = string.find(line, "^//.*") 89 + return (not found) or (line == "") 90 + end) 91 + :totable(), 92 + "\n" 93 + ) 94 + 95 + local go_type = json2go.transform(inp) 96 + if not go_type then 97 + error "cound't convert json to go type" 98 + end 99 + 100 + vim.api.nvim_set_option_value("modified", false, { buf = buf }) 101 + apply(obuf, ocpos, go_type) 102 + 103 + vim.api.nvim_set_current_win(owin) 104 + vim.api.nvim_win_set_cursor(owin, { ocpos + 1, 0 }) 105 + 106 + vim.schedule(function() 107 + pcall(vim.api.nvim_win_close, win, true) 108 + pcall(vim.api.nvim_buf_delete, buf, {}) 109 + end) 110 + end, 111 + }) 112 + 113 + vim.cmd "normal! G" 114 + vim.cmd "startinsert" 115 + end 116 + 117 + --- Converts json string to go type, and puts result under the cursor. If 118 + --- [json_str] is nil, will open an interactive prompt (with cmd set in 119 + --- config). 120 + --- 121 + ---@param json_str? string 122 + function json2go.json2go(json_str) 123 + local cur_line = vim.api.nvim_win_get_cursor(0)[1] 124 + if not json_str then 125 + interactive(cur_line) 126 + return 127 + end 128 + 129 + local go_type = json2go.transform(json_str) 130 + if not go_type then 131 + error "cound't convert json to go type" 132 + end 133 + 134 + apply(0, cur_line, go_type) 135 + end 136 + 137 + return json2go
+6 -1
plugin/gopher.lua
··· 70 70 require("gopher").tags.clear() 71 71 end) 72 72 73 + -- :GoJson 74 + cmd("GoJson", function(opts) 75 + local inp = ((opts.args ~= "" and opts.args) or nil) 76 + require("gopher.json2go").json2go(inp) 77 + end, "*") 78 + 73 79 -- :GoTest 74 80 cmd("GoTestAdd", function() 75 81 require("gopher").test.add() ··· 89 95 end, "*") 90 96 91 97 cmd("GoGet", function(opts) 92 - vim.print(opts) 93 98 require("gopher").get(opts.fargs) 94 99 end, "*") 95 100
+1
scripts/docgen.lua
··· 12 12 "lua/gopher/config.lua", 13 13 "plugin/gopher.lua", 14 14 "lua/gopher/struct_tags.lua", 15 + "lua/gopher/json2go.lua", 15 16 "lua/gopher/impl.lua", 16 17 "lua/gopher/gotests.lua", 17 18 "lua/gopher/iferr.lua",
+2
spec/fixtures/json2go/interativly_input.go
··· 1 + package main 2 +
+5
spec/fixtures/json2go/interativly_output.go
··· 1 + package main 2 + 3 + type AutoGenerated struct { 4 + Json bool `json:"json"` 5 + }
+2
spec/fixtures/json2go/manual_input.go
··· 1 + package main 2 +
+7
spec/fixtures/json2go/manual_output.go
··· 1 + package main 2 + 3 + type AutoGenerated struct { 4 + User struct { 5 + Name string `json:"name"` 6 + } `json:"user"` 7 + }
+25
spec/integration/json2go_test.lua
··· 1 + local t = require "spec.testutils" 2 + local child, T, json2go = t.setup "json2go" 3 + 4 + json2go["should convert interativly"] = function() 5 + local rs = t.setup_test("json2go/interativly", child, { 2, 0 }) 6 + child.cmd "GoJson" 7 + child.type_keys [[{"json": true}]] 8 + child.type_keys "<Esc>" 9 + child.cmd "wq" -- quit prompt 10 + child.cmd "write" -- the fixture file 11 + 12 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 13 + t.cleanup(rs) 14 + end 15 + 16 + json2go["should convert argument"] = function() 17 + local rs = t.setup_test("json2go/manual", child, { 2, 0 }) 18 + child.cmd [[GoJson {"user": {"name": "user-ovic"}}]] 19 + child.cmd "write" 20 + 21 + t.eq(t.readfile(rs.tmp), rs.fixtures.output) 22 + t.cleanup(rs) 23 + end 24 + 25 + return T
+3
vhs/Taskfile.yml
··· 18 18 19 19 impl: 20 20 cmd: vhs impl.tape 21 + 22 + json2go: 23 + cmd: vhs json2go.tape
vhs/json2go.gif

This is a binary file and will not be displayed.

+2
vhs/json2go.go
··· 1 + package main 2 +
+27
vhs/json2go.tape
··· 1 + Output json2go.gif 2 + Require nvim 3 + Require json2go 4 + 5 + Set FontFamily "JetBrains Mono" 6 + Set Height 800 7 + Set Width 1500 8 + Set Padding 20 9 + Set Shell "bash" 10 + Set Theme "tokyonight" 11 + Set TypingSpeed 250ms 12 + 13 + Hide Type@0ms "./nvim.sh json2go.go" Enter Show 14 + 15 + Type@0ms "G" 16 + Type@400ms ":GoJson" Sleep 500ms Enter 17 + Type@70ms "{" Enter 18 + Type@70ms `"json": true,` Enter 19 + Type@70ms `"user": {"name": "Name", "of_age": true}` Enter 20 + Type@70ms "}" 21 + Escape Type@500ms ":wq" Enter 22 + Sleep 1s 23 + 24 + Type@25ms "G2o" Escape 25 + Type@120ms `:GoJson {"json": true}` Enter 26 + 27 + Sleep 5s