feat(config): merge platform config model and add disabled-platform guard (#336)
## Problem Supplying any `platforms` table silently dropped all unlisted platforms, making it easy to accidentally disable platforms. Invoking a disabled platform produced no user-facing error. ## Solution Switch to a merge model: all six platforms are enabled by default and user entries are deep-merged on top. Set a platform key to `false` to disable it explicitly. Add a `check_platform_enabled` guard in `handle_command` for contest fetch, login, logout, and race actions.
This commit is contained in:
parent
82640709d6
commit
e89c57558d
3 changed files with 74 additions and 29 deletions
|
|
@ -92,26 +92,6 @@ Configuration is done via `vim.g.cp`. Set this before using the plugin:
|
||||||
cpp = { extension = 'cpp', commands = { build = { ... } } }
|
cpp = { extension = 'cpp', commands = { build = { ... } } }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
atcoder = {
|
|
||||||
enabled_languages = { 'cpp', 'python' },
|
|
||||||
default_language = 'cpp',
|
|
||||||
},
|
|
||||||
codeforces = {
|
|
||||||
enabled_languages = { 'cpp', 'python' },
|
|
||||||
default_language = 'cpp',
|
|
||||||
},
|
|
||||||
codechef = {
|
|
||||||
enabled_languages = { 'cpp', 'python' },
|
|
||||||
default_language = 'cpp',
|
|
||||||
},
|
|
||||||
usaco = {
|
|
||||||
enabled_languages = { 'cpp', 'python' },
|
|
||||||
default_language = 'cpp',
|
|
||||||
},
|
|
||||||
kattis = {
|
|
||||||
enabled_languages = { 'cpp', 'python' },
|
|
||||||
default_language = 'cpp',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
debug = false,
|
debug = false,
|
||||||
ui = {
|
ui = {
|
||||||
|
|
@ -137,21 +117,25 @@ Configuration is done via `vim.g.cp`. Set this before using the plugin:
|
||||||
<
|
<
|
||||||
|
|
||||||
By default, C++ (g++ with ISO C++17) and Python are preconfigured under
|
By default, C++ (g++ with ISO C++17) and Python are preconfigured under
|
||||||
'languages'. Platforms select which languages are enabled and which one is
|
'languages'. All six platforms are enabled by default. User-supplied
|
||||||
the default; per-platform overrides can tweak 'extension' or 'commands'.
|
platform entries are merged on top of the defaults — you only need to
|
||||||
|
specify what you want to change. To disable a platform entirely, set it
|
||||||
|
to `false`.
|
||||||
|
|
||||||
For example, to run CodeForces contests with Python by default:
|
For example, to run Codeforces contests with Python by default and
|
||||||
|
disable CodeChef:
|
||||||
>lua
|
>lua
|
||||||
vim.g.cp = {
|
vim.g.cp = {
|
||||||
platforms = {
|
platforms = {
|
||||||
codeforces = {
|
codeforces = {
|
||||||
default_language = 'python',
|
default_language = 'python',
|
||||||
},
|
},
|
||||||
|
codechef = false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
<
|
<
|
||||||
Any language is supported provided the proper configuration. For example, to
|
Any language is supported provided the proper configuration. For example, to
|
||||||
run CSES problems with Rust using the single schema:
|
add Rust and use it by default on CSES:
|
||||||
>lua
|
>lua
|
||||||
vim.g.cp = {
|
vim.g.cp = {
|
||||||
languages = {
|
languages = {
|
||||||
|
|
@ -175,8 +159,11 @@ run CSES problems with Rust using the single schema:
|
||||||
Fields: ~
|
Fields: ~
|
||||||
{languages} (table<string,|CpLanguage|>) Global language registry.
|
{languages} (table<string,|CpLanguage|>) Global language registry.
|
||||||
Each language provides an {extension} and {commands}.
|
Each language provides an {extension} and {commands}.
|
||||||
{platforms} (table<string,|CpPlatform|>) Per-platform enablement,
|
{platforms} (table<string,|CpPlatform||false>) All six platforms
|
||||||
default language, and optional overrides.
|
are enabled by default. Each entry is merged on top
|
||||||
|
of the platform defaults — omitted fields keep their
|
||||||
|
defaults and unmentioned platforms stay enabled. Set
|
||||||
|
a platform key to `false` to disable it entirely.
|
||||||
{hooks} (|cp.Hooks|) Hook functions called at various stages.
|
{hooks} (|cp.Hooks|) Hook functions called at various stages.
|
||||||
{debug} (boolean, default: false) Show info messages.
|
{debug} (boolean, default: false) Show info messages.
|
||||||
{scrapers} (string[]) Supported platform ids.
|
{scrapers} (string[]) Supported platform ids.
|
||||||
|
|
@ -476,6 +463,14 @@ COMMANDS *cp-commands*
|
||||||
If [platform] is omitted, uses the active platform.
|
If [platform] is omitted, uses the active platform.
|
||||||
Examples: >
|
Examples: >
|
||||||
:CP logout atcoder
|
:CP logout atcoder
|
||||||
|
<
|
||||||
|
:CP {platform} signup
|
||||||
|
Open the platform's registration page in the
|
||||||
|
browser via |vim.ui.open|. Works even if
|
||||||
|
{platform} is not enabled in your config.
|
||||||
|
Examples: >
|
||||||
|
:CP atcoder signup
|
||||||
|
:CP codeforces signup
|
||||||
<
|
<
|
||||||
Submit Commands ~
|
Submit Commands ~
|
||||||
:CP submit [--lang {language}]
|
:CP submit [--lang {language}]
|
||||||
|
|
@ -1019,6 +1014,12 @@ Credentials are stored under _credentials in the main cache file
|
||||||
Remove stored credentials for a platform.
|
Remove stored credentials for a platform.
|
||||||
Omit [platform] to use the currently active platform.
|
Omit [platform] to use the currently active platform.
|
||||||
|
|
||||||
|
:CP {platform} signup
|
||||||
|
Open the platform's account registration page in the browser via
|
||||||
|
|vim.ui.open|. Works even if {platform} is not enabled in your
|
||||||
|
config. {platform} is one of: atcoder, codechef, codeforces, cses,
|
||||||
|
kattis, usaco.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
SUBMIT *cp-submit*
|
SUBMIT *cp-submit*
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,7 @@ local function parse_command(args)
|
||||||
message = 'Too few arguments - specify a contest.',
|
message = 'Too few arguments - specify a contest.',
|
||||||
}
|
}
|
||||||
elseif #args == 2 then
|
elseif #args == 2 then
|
||||||
if args[2] == 'login' or args[2] == 'logout' then
|
if args[2] == 'login' or args[2] == 'logout' or args[2] == 'signup' then
|
||||||
return { type = 'action', action = args[2], requires_context = false, platform = first }
|
return { type = 'action', action = args[2], requires_context = false, platform = first }
|
||||||
end
|
end
|
||||||
local contest = args[2]
|
local contest = args[2]
|
||||||
|
|
@ -330,6 +330,22 @@ local function parse_command(args)
|
||||||
return { type = 'error', message = 'Unknown command or no contest context.' }
|
return { type = 'error', message = 'Unknown command or no contest context.' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param platform string
|
||||||
|
---@return boolean
|
||||||
|
local function check_platform_enabled(platform)
|
||||||
|
local cfg = require('cp.config').get_config()
|
||||||
|
if not cfg.platforms[platform] then
|
||||||
|
logger.log(
|
||||||
|
("Platform '%s' is not enabled. Add it to vim.g.cp.platforms to enable it."):format(
|
||||||
|
constants.PLATFORM_DISPLAY_NAMES[platform] or platform
|
||||||
|
),
|
||||||
|
{ level = vim.log.levels.ERROR }
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
--- Core logic for handling `:CP ...` commands
|
--- Core logic for handling `:CP ...` commands
|
||||||
---@return nil
|
---@return nil
|
||||||
function M.handle_command(opts)
|
function M.handle_command(opts)
|
||||||
|
|
@ -378,6 +394,9 @@ function M.handle_command(opts)
|
||||||
elseif cmd.action == 'submit' then
|
elseif cmd.action == 'submit' then
|
||||||
require('cp.submit').submit({ language = cmd.language })
|
require('cp.submit').submit({ language = cmd.language })
|
||||||
elseif cmd.action == 'race' then
|
elseif cmd.action == 'race' then
|
||||||
|
if not check_platform_enabled(cmd.platform) then
|
||||||
|
return
|
||||||
|
end
|
||||||
require('cp.race').start(cmd.platform, cmd.contest, cmd.language)
|
require('cp.race').start(cmd.platform, cmd.contest, cmd.language)
|
||||||
elseif cmd.action == 'race_stop' then
|
elseif cmd.action == 'race_stop' then
|
||||||
require('cp.race').stop()
|
require('cp.race').stop()
|
||||||
|
|
@ -396,9 +415,25 @@ function M.handle_command(opts)
|
||||||
end
|
end
|
||||||
vim.ui.open(url)
|
vim.ui.open(url)
|
||||||
elseif cmd.action == 'login' then
|
elseif cmd.action == 'login' then
|
||||||
|
if not check_platform_enabled(cmd.platform) then
|
||||||
|
return
|
||||||
|
end
|
||||||
require('cp.credentials').login(cmd.platform)
|
require('cp.credentials').login(cmd.platform)
|
||||||
elseif cmd.action == 'logout' then
|
elseif cmd.action == 'logout' then
|
||||||
|
if not check_platform_enabled(cmd.platform) then
|
||||||
|
return
|
||||||
|
end
|
||||||
require('cp.credentials').logout(cmd.platform)
|
require('cp.credentials').logout(cmd.platform)
|
||||||
|
elseif cmd.action == 'signup' then
|
||||||
|
local url = constants.SIGNUP_URLS[cmd.platform]
|
||||||
|
if not url then
|
||||||
|
logger.log(
|
||||||
|
("No signup URL available for '%s'"):format(cmd.platform),
|
||||||
|
{ level = vim.log.levels.WARN }
|
||||||
|
)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.ui.open(url)
|
||||||
end
|
end
|
||||||
elseif cmd.type == 'problem_jump' then
|
elseif cmd.type == 'problem_jump' then
|
||||||
local platform = state.get_platform()
|
local platform = state.get_platform()
|
||||||
|
|
@ -432,6 +467,9 @@ function M.handle_command(opts)
|
||||||
local cache_commands = require('cp.commands.cache')
|
local cache_commands = require('cp.commands.cache')
|
||||||
cache_commands.handle_cache_command(cmd)
|
cache_commands.handle_cache_command(cmd)
|
||||||
elseif cmd.type == 'contest_setup' then
|
elseif cmd.type == 'contest_setup' then
|
||||||
|
if not check_platform_enabled(cmd.platform) then
|
||||||
|
return
|
||||||
|
end
|
||||||
local setup = require('cp.setup')
|
local setup = require('cp.setup')
|
||||||
setup.setup_contest(cmd.platform, cmd.contest, nil, cmd.language)
|
setup.setup_contest(cmd.platform, cmd.contest, nil, cmd.language)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@
|
||||||
---@field runtime { effective: table<string, table<string, CpLanguage>> } -- computed
|
---@field runtime { effective: table<string, table<string, CpLanguage>> } -- computed
|
||||||
|
|
||||||
---@class cp.PartialConfig: cp.Config
|
---@class cp.PartialConfig: cp.Config
|
||||||
|
---@field platforms? table<string, CpPlatform|false>
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
|
@ -333,13 +334,18 @@ function M.setup(user_config)
|
||||||
vim.validate({ user_config = { user_config, { 'table', 'nil' }, true } })
|
vim.validate({ user_config = { user_config, { 'table', 'nil' }, true } })
|
||||||
local defaults = vim.deepcopy(M.defaults)
|
local defaults = vim.deepcopy(M.defaults)
|
||||||
if user_config and user_config.platforms then
|
if user_config and user_config.platforms then
|
||||||
for plat in pairs(defaults.platforms) do
|
for plat, v in pairs(user_config.platforms) do
|
||||||
if not user_config.platforms[plat] then
|
if v == false then
|
||||||
defaults.platforms[plat] = nil
|
defaults.platforms[plat] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local cfg = vim.tbl_deep_extend('force', defaults, user_config or {})
|
local cfg = vim.tbl_deep_extend('force', defaults, user_config or {})
|
||||||
|
for plat, v in pairs(cfg.platforms) do
|
||||||
|
if v == false then
|
||||||
|
cfg.platforms[plat] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if not next(cfg.languages) then
|
if not next(cfg.languages) then
|
||||||
error('[cp.nvim] At least one language must be configured')
|
error('[cp.nvim] At least one language must be configured')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue