Merge pull request #7 from barrett-ruth/feat/validation
config specs and window layout fixes
This commit is contained in:
commit
42a3fa370f
24 changed files with 352 additions and 586 deletions
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
|
|
@ -29,6 +29,18 @@ jobs:
|
|||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --display-style quiet .
|
||||
|
||||
lua-typecheck:
|
||||
name: Lua Type Checking
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run Lua LS Type Check
|
||||
uses: mrcjkb/lua-typecheck-action@v0
|
||||
with:
|
||||
checklevel: Warning
|
||||
directories: lua
|
||||
configpath: .luarc.json
|
||||
|
||||
python-format:
|
||||
name: Python Formatting
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -39,7 +51,7 @@ jobs:
|
|||
- name: Install ruff
|
||||
run: uv tool install ruff
|
||||
- name: Check Python formatting with ruff
|
||||
run: ruff format --check templates/scrapers/
|
||||
run: ruff format --check scrapers/
|
||||
|
||||
python-lint:
|
||||
name: Python Linting
|
||||
|
|
@ -51,4 +63,4 @@ jobs:
|
|||
- name: Install ruff
|
||||
run: uv tool install ruff
|
||||
- name: Lint Python files with ruff
|
||||
run: ruff check templates/scrapers/
|
||||
run: ruff check scrapers/
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
|||
.venv/
|
||||
venv/
|
||||
CLAUDE.md
|
||||
|
|
|
|||
16
.luarc.json
Normal file
16
.luarc.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"runtime.version": "LuaJIT",
|
||||
"runtime.path": [
|
||||
"lua/?.lua",
|
||||
"lua/?/init.lua"
|
||||
],
|
||||
"diagnostics.globals": [
|
||||
"vim"
|
||||
],
|
||||
"workspace.library": [
|
||||
"$VIMRUNTIME/lua",
|
||||
"${3rd}/luv/library"
|
||||
],
|
||||
"workspace.checkThirdParty": false,
|
||||
"completion.callSnippet": "Replace"
|
||||
}
|
||||
188
CLAUDE.md
188
CLAUDE.md
|
|
@ -1,188 +0,0 @@
|
|||
# CP.nvim Modernization Status & Upcoming Changes
|
||||
|
||||
## ✅ Completed Modernization
|
||||
|
||||
### Core System Overhaul
|
||||
- **Eliminated makefile dependency** - Pure Lua implementation using `vim.system()`
|
||||
- **Native Neovim 0.10+ APIs** throughout (no more shell scripts or `vim.fn.system`)
|
||||
- **JSON-based problem scraping** replacing `---INPUT---` format with structured data
|
||||
- **Modular architecture** with dedicated modules:
|
||||
- `execute.lua` - Compilation and execution
|
||||
- `scrape.lua` - Problem scraping with JSON parsing
|
||||
- `window.lua` - Native window management
|
||||
- `config.lua` - Base + extension configuration system
|
||||
|
||||
### Enhanced Features
|
||||
- **Configurable timeouts** per contest type
|
||||
- **Contest-specific compiler flags** with inheritance
|
||||
- **Direct Python scraper integration** without shell script middleware
|
||||
- **Native diff mode** with proper window state management (eliminated vim-zoom dependency)
|
||||
- **LuaSnip integration** with automatic snippet expansion
|
||||
- **Debug flag accuracy** in output formatting
|
||||
|
||||
### Configuration System
|
||||
- **Base + extension pattern** for contest configuration
|
||||
- **Dynamic C++ standard flag generation** (`-std=c++XX`)
|
||||
- **Default snippet templates** built into the plugin
|
||||
|
||||
## 🔧 Upcoming Development Tasks
|
||||
|
||||
### 1. Multi-Test Case Support
|
||||
**Current**: Only writes first scraped test case to `.in`/`.expected`
|
||||
**Needed**:
|
||||
- Write all test cases or let users cycle through them
|
||||
- Support for test case selection/management
|
||||
|
||||
### 2. Template System Enhancement
|
||||
**Current**: Basic snippet expansion with contest types
|
||||
**Needed**:
|
||||
- Template customization beyond snippets
|
||||
- Problem-specific template variables
|
||||
- Integration with scraped problem metadata
|
||||
|
||||
### 3. Enhanced Output Processing
|
||||
**Current**: Basic output comparison with expected results
|
||||
**Needed**:
|
||||
- Better diff visualization in output window
|
||||
- Partial match scoring
|
||||
- Test case result breakdown for multi-case problems
|
||||
|
||||
### 4. Error Handling & User Experience
|
||||
**Current**: Basic error messages and logging
|
||||
**Needed**:
|
||||
- Better error recovery from failed scraping
|
||||
- Progress indicators for long-running operations
|
||||
- More descriptive compilation error formatting
|
||||
|
||||
### 5. Contest Platform Extensions
|
||||
**Current**: AtCoder, Codeforces, CSES support
|
||||
**Potential**:
|
||||
- USACO integration (mentioned in TODO)
|
||||
- Additional platform support as needed
|
||||
|
||||
### 6. Documentation & Examples
|
||||
**Current**: Basic README
|
||||
**Needed**:
|
||||
- Video demonstration (TODO item)
|
||||
- Comprehensive vim docs (`:help cp.nvim`)
|
||||
- Configuration examples and best practices
|
||||
|
||||
## 🏗️ Architecture Notes for Future Development
|
||||
|
||||
### Plugin Structure
|
||||
```
|
||||
lua/cp/
|
||||
├── init.lua # Main plugin logic & command setup
|
||||
├── config.lua # Configuration with inheritance
|
||||
├── execute.lua # Compilation & execution (vim.system)
|
||||
├── scrape.lua # JSON-based problem scraping
|
||||
├── snippets.lua # Default LuaSnip templates
|
||||
└── window.lua # Native window management
|
||||
```
|
||||
|
||||
### Key Design Principles
|
||||
1. **Pure Lua** - No external shell dependencies
|
||||
2. **Modular** - Each concern in separate module
|
||||
3. **Configurable** - Base + extension configuration pattern
|
||||
4. **Modern APIs** - Neovim 0.10+ native functions
|
||||
5. **Graceful Fallbacks** - Degrade gracefully when optional deps unavailable
|
||||
|
||||
## 📊 Current Feature Completeness
|
||||
|
||||
| Feature | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| Problem Setup | ✅ Complete | Native scraping + file generation |
|
||||
| Code Execution | ✅ Complete | Direct compilation with timeouts |
|
||||
| Debug Mode | ✅ Complete | Separate flags + debug output |
|
||||
| Diff Comparison | ✅ Complete | Native window management |
|
||||
| LuaSnip Integration | ✅ Complete | Auto-expanding templates |
|
||||
| Multi-platform | ✅ Complete | AtCoder, Codeforces, CSES |
|
||||
| Configuration | ✅ Complete | Flexible inheritance system |
|
||||
|
||||
## 📋 PLAN - Remaining Tasks
|
||||
|
||||
### Phase 1: Core System Testing (High Priority)
|
||||
1. **End-to-end functionality testing**
|
||||
- Test all contest types: `CP atcoder`, `CP codeforces`, `CP cses`
|
||||
- Verify problem setup with scraping: `CP atcoder abc123 a`
|
||||
- Test run/debug/diff workflow for each platform
|
||||
- Validate compilation with different C++ standards
|
||||
|
||||
2. **Python environment validation**
|
||||
- Verify `uv sync` creates proper virtual environment
|
||||
- Test Python scrapers output valid JSON
|
||||
- Handle scraping failures gracefully
|
||||
- Check scraper dependencies (requests, beautifulsoup, etc.)
|
||||
|
||||
3. **Configuration system testing**
|
||||
- Test default vs custom contest configurations
|
||||
- Verify timeout settings work correctly
|
||||
- Test compiler flag inheritance and overrides
|
||||
- Validate C++ version flag generation
|
||||
|
||||
### Phase 2: Integration Testing (Medium Priority)
|
||||
4. **LuaSnip integration validation**
|
||||
- Test snippet expansion for contest types
|
||||
- Verify fallback when LuaSnip unavailable
|
||||
- Test custom user snippets alongside defaults
|
||||
- Validate snippet triggering in empty files
|
||||
|
||||
5. **Window management testing**
|
||||
- Test diff mode enter/exit functionality
|
||||
- Verify layout save/restore works correctly
|
||||
- Test window state persistence across operations
|
||||
- Validate split ratios and window positioning
|
||||
|
||||
6. **File I/O and path handling**
|
||||
- Test with various problem ID formats
|
||||
- Verify input/output/expected file handling
|
||||
- Test directory creation (build/, io/)
|
||||
- Validate file path resolution across platforms
|
||||
|
||||
### Phase 3: Error Handling & Edge Cases (Medium Priority)
|
||||
7. **Error scenario testing**
|
||||
- Test with invalid contest types
|
||||
- Test with missing Python dependencies
|
||||
- Test compilation failures
|
||||
- Test scraping failures (network issues, rate limits)
|
||||
- Test with malformed JSON from scrapers
|
||||
|
||||
8. **Performance validation**
|
||||
- Test timeout handling for slow compilations
|
||||
- Verify non-blocking execution with vim.system
|
||||
- Test with large output files
|
||||
- Validate memory usage with multiple test cases
|
||||
|
||||
### Phase 4: User Experience Testing (Lower Priority)
|
||||
9. **Command completion and validation**
|
||||
- Test command-line completion for contest types
|
||||
- Test argument validation and error messages
|
||||
- Verify help text accuracy
|
||||
|
||||
10. **Documentation verification**
|
||||
- Test all examples in README work correctly
|
||||
- Verify installation instructions are accurate
|
||||
- Test user configuration examples
|
||||
|
||||
### Phase 5: Regression Testing (Ongoing)
|
||||
11. **Compare with original functionality**
|
||||
- Ensure no features were lost in migration
|
||||
- Verify output format matches expectations
|
||||
- Test backward compatibility with existing workflows
|
||||
|
||||
### Testing Priority Order:
|
||||
1. **Critical Path**: Problem setup → Run → Output verification
|
||||
2. **Core Features**: Debug mode, diff mode, LuaSnip integration
|
||||
3. **Error Handling**: Graceful failures, helpful error messages
|
||||
4. **Edge Cases**: Unusual problem formats, network issues
|
||||
5. **Performance**: Large problems, slow networks, timeout handling
|
||||
|
||||
### Success Criteria:
|
||||
- All basic workflows complete without errors
|
||||
- Python scrapers produce valid problem files
|
||||
- Compilation and execution work for all contest types
|
||||
- LuaSnip integration functions or degrades gracefully
|
||||
- Error messages are clear and actionable
|
||||
- No vim.fn.system calls remain (all migrated to vim.system)
|
||||
|
||||
The plugin is now fully modernized and ready for comprehensive testing to ensure production readiness.
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
|
||||
pattern = "*/io/*.in",
|
||||
callback = function()
|
||||
vim.bo.filetype = "cpinput"
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
|
||||
pattern = "*/io/*.out",
|
||||
callback = function()
|
||||
vim.bo.filetype = "cpoutput"
|
||||
end,
|
||||
})
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
vim.opt_local.number = false
|
||||
vim.opt_local.relativenumber = false
|
||||
vim.opt_local.statuscolumn = ""
|
||||
vim.opt_local.signcolumn = "no"
|
||||
vim.opt_local.wrap = true
|
||||
vim.opt_local.linebreak = true
|
||||
|
|
@ -14,4 +14,4 @@ highlight default link cpOutputDebug Comment
|
|||
highlight default link cpOutputMatchesTrue DiffAdd
|
||||
highlight default link cpOutputMatchesFalse DiffDelete
|
||||
|
||||
let b:current_syntax = "cpoutput"
|
||||
let b:current_syntax = "cp"
|
||||
|
|
@ -1,5 +1,11 @@
|
|||
---@class cp.Config
|
||||
---@field contests table
|
||||
---@field snippets table
|
||||
---@field hooks table
|
||||
|
||||
local M = {}
|
||||
|
||||
---@type cp.Config
|
||||
M.defaults = {
|
||||
contests = {
|
||||
default = {
|
||||
|
|
@ -14,11 +20,20 @@ M.defaults = {
|
|||
codeforces = {
|
||||
cpp_version = 23,
|
||||
},
|
||||
cses = {},
|
||||
cses = {
|
||||
cpp_version = 20,
|
||||
},
|
||||
},
|
||||
snippets = {},
|
||||
hooks = {
|
||||
before_run = nil,
|
||||
before_debug = nil,
|
||||
},
|
||||
}
|
||||
|
||||
---@param base_config table
|
||||
---@param contest_config table
|
||||
---@return table
|
||||
local function extend_contest_config(base_config, contest_config)
|
||||
local result = vim.tbl_deep_extend("force", base_config, contest_config)
|
||||
|
||||
|
|
@ -29,7 +44,28 @@ local function extend_contest_config(base_config, contest_config)
|
|||
return result
|
||||
end
|
||||
|
||||
---@param user_config table|nil
|
||||
---@return table
|
||||
function M.setup(user_config)
|
||||
vim.validate({
|
||||
user_config = { user_config, { "table", "nil" }, true },
|
||||
})
|
||||
|
||||
if user_config then
|
||||
vim.validate({
|
||||
contests = { user_config.contests, { "table", "nil" }, true },
|
||||
snippets = { user_config.snippets, { "table", "nil" }, true },
|
||||
hooks = { user_config.hooks, { "table", "nil" }, true },
|
||||
})
|
||||
|
||||
if user_config.hooks then
|
||||
vim.validate({
|
||||
before_run = { user_config.hooks.before_run, { "function", "nil" }, true },
|
||||
before_debug = { user_config.hooks.before_debug, { "function", "nil" }, true },
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local config = vim.tbl_deep_extend("force", M.defaults, user_config or {})
|
||||
|
||||
local default_contest = config.contests.default
|
||||
|
|
|
|||
107
lua/cp/health.lua
Normal file
107
lua/cp/health.lua
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
local M = {}
|
||||
|
||||
local function check_nvim_version()
|
||||
if vim.fn.has("nvim-0.10.0") == 1 then
|
||||
vim.health.ok("Neovim 0.10.0+ detected")
|
||||
else
|
||||
vim.health.error("cp.nvim requires Neovim 0.10.0+")
|
||||
end
|
||||
end
|
||||
|
||||
local function check_uv()
|
||||
if vim.fn.executable("uv") == 1 then
|
||||
vim.health.ok("uv executable found")
|
||||
|
||||
local result = vim.system({ "uv", "--version" }, { text = true }):wait()
|
||||
if result.code == 0 then
|
||||
vim.health.info("uv version: " .. result.stdout:gsub("\n", ""))
|
||||
end
|
||||
else
|
||||
vim.health.warn("uv not found - install from https://docs.astral.sh/uv/ for problem scraping")
|
||||
end
|
||||
end
|
||||
|
||||
local function check_python_env()
|
||||
local plugin_path = debug.getinfo(1, "S").source:sub(2)
|
||||
plugin_path = vim.fn.fnamemodify(plugin_path, ":h:h:h:h")
|
||||
local venv_dir = plugin_path .. "/.venv"
|
||||
|
||||
if vim.fn.isdirectory(venv_dir) == 1 then
|
||||
vim.health.ok("Python virtual environment found at " .. venv_dir)
|
||||
else
|
||||
vim.health.warn("Python virtual environment not set up - run :CP command to initialize")
|
||||
end
|
||||
end
|
||||
|
||||
local function check_scrapers()
|
||||
local plugin_path = debug.getinfo(1, "S").source:sub(2)
|
||||
plugin_path = vim.fn.fnamemodify(plugin_path, ":h:h:h:h")
|
||||
|
||||
local scrapers = { "atcoder.py", "codeforces.py", "cses.py" }
|
||||
for _, scraper in ipairs(scrapers) do
|
||||
local scraper_path = plugin_path .. "/scrapers/" .. scraper
|
||||
if vim.fn.filereadable(scraper_path) == 1 then
|
||||
vim.health.ok("Scraper found: " .. scraper)
|
||||
else
|
||||
vim.health.error("Missing scraper: " .. scraper)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function check_luasnip()
|
||||
local has_luasnip, luasnip = pcall(require, "luasnip")
|
||||
if has_luasnip then
|
||||
vim.health.ok("LuaSnip integration available")
|
||||
local snippet_count = #luasnip.get_snippets("all")
|
||||
vim.health.info("LuaSnip snippets loaded: " .. snippet_count)
|
||||
else
|
||||
vim.health.info("LuaSnip not available - template expansion will be limited")
|
||||
end
|
||||
end
|
||||
|
||||
local function check_directories()
|
||||
local cwd = vim.fn.getcwd()
|
||||
local build_dir = cwd .. "/build"
|
||||
local io_dir = cwd .. "/io"
|
||||
|
||||
if vim.fn.isdirectory(build_dir) == 1 then
|
||||
vim.health.ok("Build directory exists: " .. build_dir)
|
||||
else
|
||||
vim.health.info("Build directory will be created when needed")
|
||||
end
|
||||
|
||||
if vim.fn.isdirectory(io_dir) == 1 then
|
||||
vim.health.ok("IO directory exists: " .. io_dir)
|
||||
else
|
||||
vim.health.info("IO directory will be created when needed")
|
||||
end
|
||||
end
|
||||
|
||||
local function check_config()
|
||||
local cp = require("cp")
|
||||
if cp.is_initialized() then
|
||||
vim.health.ok("Plugin initialized")
|
||||
|
||||
if vim.g.cp_contest then
|
||||
vim.health.info("Current contest: " .. vim.g.cp_contest)
|
||||
else
|
||||
vim.health.info("No contest mode set")
|
||||
end
|
||||
else
|
||||
vim.health.warn("Plugin not initialized - configuration may be incomplete")
|
||||
end
|
||||
end
|
||||
|
||||
function M.check()
|
||||
vim.health.start("cp.nvim health check")
|
||||
|
||||
check_nvim_version()
|
||||
check_uv()
|
||||
check_python_env()
|
||||
check_scrapers()
|
||||
check_luasnip()
|
||||
check_directories()
|
||||
check_config()
|
||||
end
|
||||
|
||||
return M
|
||||
128
lua/cp/init.lua
128
lua/cp/init.lua
|
|
@ -16,36 +16,6 @@ if not vim.fn.has("nvim-0.10.0") then
|
|||
return M
|
||||
end
|
||||
|
||||
local function get_plugin_path()
|
||||
local plugin_path = debug.getinfo(1, "S").source:sub(2)
|
||||
return vim.fn.fnamemodify(plugin_path, ":h:h:h")
|
||||
end
|
||||
|
||||
local function setup_python_env()
|
||||
local plugin_path = get_plugin_path()
|
||||
local venv_dir = plugin_path .. "/.venv"
|
||||
|
||||
if vim.fn.executable("uv") == 0 then
|
||||
log(
|
||||
"uv is not installed. Install it to enable problem scraping: https://docs.astral.sh/uv/",
|
||||
vim.log.levels.WARN
|
||||
)
|
||||
return false
|
||||
end
|
||||
|
||||
if vim.fn.isdirectory(venv_dir) == 0 then
|
||||
log("setting up Python environment for scrapers...")
|
||||
local result = vim.system({ "uv", "sync" }, { cwd = plugin_path, text = true }):wait()
|
||||
if result.code ~= 0 then
|
||||
log("failed to setup Python environment: " .. result.stderr, vim.log.levels.ERROR)
|
||||
return false
|
||||
end
|
||||
log("python environment setup complete")
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local competition_types = { "atcoder", "codeforces", "cses" }
|
||||
|
||||
local function setup_contest(contest_type)
|
||||
|
|
@ -82,7 +52,7 @@ local function setup_problem(problem_id, problem_letter)
|
|||
vim.g.cp_diff_mode = false
|
||||
end
|
||||
|
||||
vim.cmd.only()
|
||||
vim.cmd("silent only")
|
||||
|
||||
local scrape_result = scrape.scrape_problem(vim.g.cp_contest, problem_id, problem_letter)
|
||||
|
||||
|
|
@ -136,10 +106,12 @@ local function setup_problem(problem_id, problem_letter)
|
|||
|
||||
vim.cmd.vsplit(output)
|
||||
vim.cmd.w()
|
||||
vim.bo.filetype = "cp"
|
||||
window.clearcol()
|
||||
vim.cmd(("vertical resize %d"):format(math.floor(vim.o.columns * 0.3)))
|
||||
vim.cmd.split(input)
|
||||
vim.cmd.w()
|
||||
vim.bo.filetype = "cp"
|
||||
window.clearcol()
|
||||
vim.cmd.wincmd("h")
|
||||
|
||||
|
|
@ -161,9 +133,8 @@ local function run_problem()
|
|||
return
|
||||
end
|
||||
|
||||
local has_lsp, lsp = pcall(require, "lsp")
|
||||
if has_lsp and lsp.lsp_format then
|
||||
lsp.lsp_format({ async = true })
|
||||
if config.hooks and config.hooks.before_run then
|
||||
config.hooks.before_run(problem_id)
|
||||
end
|
||||
|
||||
if not vim.g.cp_contest then
|
||||
|
|
@ -185,9 +156,8 @@ local function debug_problem()
|
|||
return
|
||||
end
|
||||
|
||||
local has_lsp, lsp = pcall(require, "lsp")
|
||||
if has_lsp and lsp.lsp_format then
|
||||
lsp.lsp_format({ async = true })
|
||||
if config.hooks and config.hooks.before_debug then
|
||||
config.hooks.before_debug(problem_id)
|
||||
end
|
||||
|
||||
if not vim.g.cp_contest then
|
||||
|
|
@ -239,71 +209,57 @@ end
|
|||
|
||||
local initialized = false
|
||||
|
||||
function M.is_initialized()
|
||||
return initialized
|
||||
end
|
||||
|
||||
function M.setup(user_config)
|
||||
if initialized and not user_config then
|
||||
return
|
||||
end
|
||||
|
||||
config = config_module.setup(user_config)
|
||||
|
||||
local plugin_path = get_plugin_path()
|
||||
config.snippets.path = plugin_path .. "/templates/snippets"
|
||||
|
||||
snippets.setup(config)
|
||||
initialized = true
|
||||
end
|
||||
|
||||
if initialized then
|
||||
function M.handle_command(opts)
|
||||
local args = opts.fargs
|
||||
if #args == 0 then
|
||||
log("Usage: :CP <contest|problem_id|run|debug|diff>", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
initialized = true
|
||||
|
||||
setup_python_env()
|
||||
local cmd = args[1]
|
||||
|
||||
vim.api.nvim_create_user_command("CP", function(opts)
|
||||
local args = opts.fargs
|
||||
if #args == 0 then
|
||||
log("Usage: :CP <contest|problem_id|run|debug|diff>", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
local cmd = args[1]
|
||||
|
||||
if vim.tbl_contains(competition_types, cmd) then
|
||||
if args[2] then
|
||||
setup_contest(cmd)
|
||||
if (cmd == "atcoder" or cmd == "codeforces") and args[3] then
|
||||
setup_problem(args[2], args[3])
|
||||
else
|
||||
setup_problem(args[2])
|
||||
end
|
||||
if vim.tbl_contains(competition_types, cmd) then
|
||||
if args[2] then
|
||||
setup_contest(cmd)
|
||||
if (cmd == "atcoder" or cmd == "codeforces") and args[3] then
|
||||
setup_problem(args[2], args[3])
|
||||
else
|
||||
setup_contest(cmd)
|
||||
setup_problem(args[2])
|
||||
end
|
||||
elseif cmd == "run" then
|
||||
run_problem()
|
||||
elseif cmd == "debug" then
|
||||
debug_problem()
|
||||
elseif cmd == "diff" then
|
||||
diff_problem()
|
||||
else
|
||||
if vim.g.cp_contest then
|
||||
if (vim.g.cp_contest == "atcoder" or vim.g.cp_contest == "codeforces") and args[2] then
|
||||
setup_problem(cmd, args[2])
|
||||
else
|
||||
setup_problem(cmd)
|
||||
end
|
||||
else
|
||||
log("no contest mode set. run :CP <contest> first or use full command", vim.log.levels.ERROR)
|
||||
end
|
||||
setup_contest(cmd)
|
||||
end
|
||||
end, {
|
||||
nargs = "*",
|
||||
complete = function(ArgLead, _, _)
|
||||
local commands = vim.list_extend(vim.deepcopy(competition_types), { "run", "debug", "diff" })
|
||||
return vim.tbl_filter(function(cmd)
|
||||
return cmd:find(ArgLead, 1, true) == 1
|
||||
end, commands)
|
||||
end,
|
||||
})
|
||||
elseif cmd == "run" then
|
||||
run_problem()
|
||||
elseif cmd == "debug" then
|
||||
debug_problem()
|
||||
elseif cmd == "diff" then
|
||||
diff_problem()
|
||||
else
|
||||
if vim.g.cp_contest then
|
||||
if (vim.g.cp_contest == "atcoder" or vim.g.cp_contest == "codeforces") and args[2] then
|
||||
setup_problem(cmd, args[2])
|
||||
else
|
||||
setup_problem(cmd)
|
||||
end
|
||||
else
|
||||
log("no contest mode set. run :CP <contest> first or use full command", vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -9,11 +9,47 @@ local function ensure_io_directory()
|
|||
vim.fn.mkdir("io", "p")
|
||||
end
|
||||
|
||||
local function log(msg, level)
|
||||
vim.notify(("[cp.nvim]: %s"):format(msg), level or vim.log.levels.INFO)
|
||||
end
|
||||
|
||||
local function setup_python_env()
|
||||
local plugin_path = get_plugin_path()
|
||||
local venv_dir = plugin_path .. "/.venv"
|
||||
|
||||
if vim.fn.executable("uv") == 0 then
|
||||
log(
|
||||
"uv is not installed. Install it to enable problem scraping: https://docs.astral.sh/uv/",
|
||||
vim.log.levels.WARN
|
||||
)
|
||||
return false
|
||||
end
|
||||
|
||||
if vim.fn.isdirectory(venv_dir) == 0 then
|
||||
log("setting up Python environment for scrapers...")
|
||||
local result = vim.system({ "uv", "sync" }, { cwd = plugin_path, text = true }):wait()
|
||||
if result.code ~= 0 then
|
||||
log("failed to setup Python environment: " .. result.stderr, vim.log.levels.ERROR)
|
||||
return false
|
||||
end
|
||||
log("python environment setup complete")
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function M.scrape_problem(contest, problem_id, problem_letter)
|
||||
ensure_io_directory()
|
||||
|
||||
if not setup_python_env() then
|
||||
return {
|
||||
success = false,
|
||||
error = "Python environment setup failed",
|
||||
}
|
||||
end
|
||||
|
||||
local plugin_path = get_plugin_path()
|
||||
local scraper_path = plugin_path .. "/templates/scrapers/" .. contest .. ".py"
|
||||
local scraper_path = plugin_path .. "/scrapers/" .. contest .. ".py"
|
||||
|
||||
local args
|
||||
if contest == "cses" then
|
||||
|
|
@ -47,14 +83,29 @@ function M.scrape_problem(contest, problem_id, problem_letter)
|
|||
return data
|
||||
end
|
||||
|
||||
local full_problem_id = data.problem_id
|
||||
local full_problem_id = data.problem_id:lower()
|
||||
local input_file = "io/" .. full_problem_id .. ".in"
|
||||
local expected_file = "io/" .. full_problem_id .. ".expected"
|
||||
|
||||
if #data.test_cases > 0 then
|
||||
local first_test = data.test_cases[1]
|
||||
vim.fn.writefile(vim.split(first_test.input, "\n"), input_file)
|
||||
vim.fn.writefile(vim.split(first_test.output, "\n"), expected_file)
|
||||
local all_inputs = {}
|
||||
local all_outputs = {}
|
||||
|
||||
for _, test_case in ipairs(data.test_cases) do
|
||||
local input_lines = vim.split(test_case.input:gsub("\r", ""):gsub("\n+$", ""), "\n")
|
||||
local output_lines = vim.split(test_case.output:gsub("\r", ""):gsub("\n+$", ""), "\n")
|
||||
|
||||
for _, line in ipairs(input_lines) do
|
||||
table.insert(all_inputs, line)
|
||||
end
|
||||
|
||||
for _, line in ipairs(output_lines) do
|
||||
table.insert(all_outputs, line)
|
||||
end
|
||||
end
|
||||
|
||||
vim.fn.writefile(all_inputs, input_file)
|
||||
vim.fn.writefile(all_outputs, expected_file)
|
||||
end
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -35,41 +35,77 @@ function M.restore_layout(state)
|
|||
end
|
||||
|
||||
vim.cmd.diffoff()
|
||||
vim.cmd(state.layout)
|
||||
|
||||
for win, win_state in pairs(state.windows) do
|
||||
if vim.api.nvim_win_is_valid(win) then
|
||||
vim.api.nvim_set_current_win(win)
|
||||
if vim.api.nvim_get_current_buf() == win_state.bufnr then
|
||||
vim.fn.winrestview(win_state.view)
|
||||
local problem_id = vim.fn.expand("%:t:r")
|
||||
if problem_id == "" then
|
||||
for win, win_state in pairs(state.windows) do
|
||||
if vim.api.nvim_win_is_valid(win) and vim.api.nvim_buf_is_valid(win_state.bufnr) then
|
||||
local bufname = vim.api.nvim_buf_get_name(win_state.bufnr)
|
||||
if bufname:match("%.cc$") then
|
||||
problem_id = vim.fn.fnamemodify(bufname, ":t:r")
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if vim.api.nvim_win_is_valid(state.current_win) then
|
||||
vim.api.nvim_set_current_win(state.current_win)
|
||||
if problem_id ~= "" then
|
||||
vim.cmd("silent only")
|
||||
|
||||
local base_fp = vim.fn.getcwd()
|
||||
local input = ("%s/io/%s.in"):format(base_fp, problem_id)
|
||||
local output = ("%s/io/%s.out"):format(base_fp, problem_id)
|
||||
local source = problem_id .. ".cc"
|
||||
|
||||
vim.cmd.edit(source)
|
||||
vim.cmd.vsplit(output)
|
||||
vim.bo.filetype = "cp"
|
||||
M.clearcol()
|
||||
vim.cmd(("vertical resize %d"):format(math.floor(vim.o.columns * 0.3)))
|
||||
vim.cmd.split(input)
|
||||
vim.bo.filetype = "cp"
|
||||
M.clearcol()
|
||||
vim.cmd.wincmd("h")
|
||||
else
|
||||
vim.cmd(state.layout)
|
||||
|
||||
for win, win_state in pairs(state.windows) do
|
||||
if vim.api.nvim_win_is_valid(win) then
|
||||
vim.api.nvim_set_current_win(win)
|
||||
if vim.api.nvim_get_current_buf() == win_state.bufnr then
|
||||
vim.fn.winrestview(win_state.view)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if vim.api.nvim_win_is_valid(state.current_win) then
|
||||
vim.api.nvim_set_current_win(state.current_win)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.setup_diff_layout(actual_output, expected_output, input_file)
|
||||
vim.cmd.diffoff()
|
||||
vim.cmd.only()
|
||||
vim.cmd("silent only")
|
||||
|
||||
local output_lines = vim.split(actual_output, "\n")
|
||||
local output_buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(output_buf, 0, -1, false, output_lines)
|
||||
vim.bo[output_buf].filetype = "cpoutput"
|
||||
vim.bo[output_buf].filetype = "cp"
|
||||
|
||||
vim.cmd.edit()
|
||||
vim.api.nvim_set_current_buf(output_buf)
|
||||
vim.cmd.diffthis()
|
||||
M.clearcol()
|
||||
vim.cmd.diffthis()
|
||||
|
||||
vim.cmd.vsplit(expected_output)
|
||||
vim.cmd.diffthis()
|
||||
vim.bo.filetype = "cp"
|
||||
M.clearcol()
|
||||
vim.cmd.diffthis()
|
||||
|
||||
vim.cmd.wincmd("h")
|
||||
vim.cmd(("botright split %s"):format(input_file))
|
||||
vim.bo.filetype = "cp"
|
||||
M.clearcol()
|
||||
vim.cmd.wincmd("k")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,4 +3,20 @@ if vim.g.loaded_cp then
|
|||
end
|
||||
vim.g.loaded_cp = 1
|
||||
|
||||
require("cp").setup()
|
||||
local competition_types = { "atcoder", "codeforces", "cses" }
|
||||
|
||||
vim.api.nvim_create_user_command("CP", function(opts)
|
||||
local cp = require("cp")
|
||||
if not cp.is_initialized() then
|
||||
cp.setup()
|
||||
end
|
||||
cp.handle_command(opts)
|
||||
end, {
|
||||
nargs = "*",
|
||||
complete = function(ArgLead, _, _)
|
||||
local commands = vim.list_extend(vim.deepcopy(competition_types), { "run", "debug", "diff" })
|
||||
return vim.tbl_filter(function(cmd)
|
||||
return cmd:find(ArgLead, 1, true) == 1
|
||||
end, commands)
|
||||
end,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -41,9 +41,6 @@ Using [lazy.nvim](https://github.com/folke/lazy.nvim):
|
|||
|
||||
## TODO
|
||||
|
||||
- update templates to minimum vrsion that's comatible with scrapers (i.e.
|
||||
aggregated testcases, ifdef local, etc.)
|
||||
- vimdocs
|
||||
- vimdocs
|
||||
- example video
|
||||
- more flexible setup (more of a question of philosophy)
|
||||
- USACO support
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ def main():
|
|||
|
||||
contest_id = sys.argv[1]
|
||||
problem_letter = sys.argv[2]
|
||||
problem_id = contest_id + problem_letter
|
||||
problem_id = contest_id + problem_letter.lower()
|
||||
|
||||
url = parse_problem_url(contest_id, problem_letter)
|
||||
print(f"Scraping: {url}", file=sys.stderr)
|
||||
|
|
@ -89,6 +89,13 @@ def main():
|
|||
for input_data, output_data in tests:
|
||||
test_cases.append({"input": input_data, "output": output_data})
|
||||
|
||||
if test_cases:
|
||||
combined_input = (
|
||||
str(len(test_cases)) + "\n" + "\n".join(tc["input"] for tc in test_cases)
|
||||
)
|
||||
combined_output = "\n".join(tc["output"] for tc in test_cases)
|
||||
test_cases = [{"input": combined_input, "output": combined_output}]
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"problem_id": problem_id,
|
||||
|
|
@ -43,16 +43,9 @@ def scrape(url: str):
|
|||
output_lines.append(line_div.get_text().strip())
|
||||
|
||||
if input_lines and output_lines:
|
||||
if len(input_lines) > 1 and input_lines[0].isdigit():
|
||||
test_count = int(input_lines[0])
|
||||
remaining_input = input_lines[1:]
|
||||
for i in range(min(test_count, len(output_lines))):
|
||||
if i < len(remaining_input):
|
||||
tests.append((remaining_input[i], output_lines[i]))
|
||||
else:
|
||||
input_text = "\n".join(input_lines)
|
||||
output_text = "\n".join(output_lines)
|
||||
tests.append((input_text, output_text))
|
||||
input_text = "\n".join(input_lines)
|
||||
output_text = "\n".join(output_lines)
|
||||
tests.append((input_text, output_text))
|
||||
|
||||
return tests
|
||||
|
||||
|
|
@ -84,7 +77,7 @@ def main():
|
|||
|
||||
contest_id = sys.argv[1]
|
||||
problem_letter = sys.argv[2]
|
||||
problem_id = contest_id + problem_letter.upper()
|
||||
problem_id = contest_id + problem_letter.lower()
|
||||
|
||||
url = parse_problem_url(contest_id, problem_letter)
|
||||
tests = scrape_sample_tests(url)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
BasedOnStyle: Google
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortCompoundRequirementOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLambdasOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
.PHONY: run debug clean setup init scrape
|
||||
|
||||
VERSION ?= 20
|
||||
|
||||
SRC = $(word 2,$(MAKECMDGOALS))
|
||||
|
||||
.SILENT:
|
||||
|
||||
run:
|
||||
sh scripts/run.sh $(SRC)
|
||||
|
||||
debug:
|
||||
sh scripts/debug.sh $(SRC)
|
||||
|
||||
clean:
|
||||
rm -rf build/*
|
||||
|
||||
setup:
|
||||
test -d build || mkdir -p build
|
||||
test -d io || mkdir -p io
|
||||
test -f compile_flags.txt && echo -std=c++$(VERSION) >>compile_flags.txt
|
||||
|
||||
init:
|
||||
make setup
|
||||
|
||||
scrape:
|
||||
sh scripts/scrape.sh $(word 2,$(MAKECMDGOALS)) $(word 3,$(MAKECMDGOALS)) $(word 4,$(MAKECMDGOALS))
|
||||
|
||||
%:
|
||||
@:
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
. ./scripts/utils.sh
|
||||
|
||||
SRC="$1"
|
||||
BASE=$(basename "$SRC" .cc)
|
||||
INPUT="${BASE}.in"
|
||||
OUTPUT="${BASE}.out"
|
||||
DBG_BIN="${BASE}.debug"
|
||||
|
||||
test -d build || mkdir -p build
|
||||
test -d io || mkdir -p io
|
||||
|
||||
test -f "$INPUT" && test ! -f "io/$INPUT" && mv "$INPUT" "io/"
|
||||
test -f "$OUTPUT" && test ! -f "io/$OUTPUT" && mv "$OUTPUT" "io/"
|
||||
|
||||
test -f "io/$INPUT" || touch "io/$INPUT"
|
||||
test -f "io/$OUTPUT" || touch "io/$OUTPUT"
|
||||
|
||||
INPUT="io/$INPUT"
|
||||
OUTPUT="io/$OUTPUT"
|
||||
DBG_BIN="build/$DBG_BIN"
|
||||
|
||||
compile_source "$SRC" "$DBG_BIN" "$OUTPUT" @debug_flags.txt
|
||||
CODE=$?
|
||||
test $CODE -gt 0 && exit $CODE
|
||||
|
||||
execute_binary "$DBG_BIN" "$INPUT" "$OUTPUT" true
|
||||
exit $?
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
. ./scripts/utils.sh
|
||||
|
||||
SRC="$1"
|
||||
BASE=$(basename "$SRC" .cc)
|
||||
INPUT="${BASE}.in"
|
||||
OUTPUT="${BASE}.out"
|
||||
RUN_BIN="${BASE}.run"
|
||||
|
||||
test -d build || mkdir -p build
|
||||
test -d io || mkdir -p io
|
||||
|
||||
test -f "$INPUT" && test ! -f "io/$INPUT" && mv "$INPUT" "io/"
|
||||
test -f "$OUTPUT" && test ! -f "io/$OUTPUT" && mv "$OUTPUT" "io/"
|
||||
|
||||
test -f "io/$INPUT" || touch "io/$INPUT"
|
||||
test -f "io/$OUTPUT" || touch "io/$OUTPUT"
|
||||
|
||||
INPUT="io/$INPUT"
|
||||
OUTPUT="io/$OUTPUT"
|
||||
RUN_BIN="build/$RUN_BIN"
|
||||
|
||||
compile_source "$SRC" "$RUN_BIN" "$OUTPUT" ""
|
||||
CODE=$?
|
||||
test $CODE -gt 0 && exit $CODE
|
||||
|
||||
execute_binary "$RUN_BIN" "$INPUT" "$OUTPUT"
|
||||
exit $?
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
CONTEST="$1"
|
||||
PROBLEM="$2"
|
||||
PROBLEM_LETTER="$3"
|
||||
|
||||
if [ -z "$CONTEST" ] || [ -z "$PROBLEM" ]; then
|
||||
echo "Usage: make scrape <contest> <problem_id> [problem_letter]"
|
||||
echo "Available contests: cses, atcoder, codeforces"
|
||||
echo "Examples:"
|
||||
echo " make scrape cses 1068"
|
||||
echo " make scrape atcoder abc042 a"
|
||||
echo " make scrape codeforces 1234 A"
|
||||
exit
|
||||
fi
|
||||
|
||||
test -d io && true || mkdir -p io
|
||||
TMPFILE=$(mktemp)
|
||||
ORIGDIR=$(pwd)
|
||||
|
||||
case "$CONTEST" in
|
||||
cses)
|
||||
cd "$(dirname "$0")/../.." && uv run scrapers/cses.py "$PROBLEM" > "$TMPFILE"
|
||||
if [ $? -eq 0 ]; then
|
||||
cd "$ORIGDIR"
|
||||
awk '/^---INPUT---$/ {getline; while ($0 != "---OUTPUT---") {print; getline}} END {}' "$TMPFILE" > "io/$PROBLEM.in"
|
||||
awk '/^---OUTPUT---$/ {getline; while ($0 != "---END---") {print; getline}} END {}' "$TMPFILE" > "io/$PROBLEM.expected"
|
||||
echo "Scraped problem $PROBLEM to io/$PROBLEM.in and io/$PROBLEM.expected"
|
||||
else
|
||||
echo "Failed to scrape problem $PROBLEM"
|
||||
cat "$TMPFILE"
|
||||
rm "$TMPFILE"
|
||||
exit
|
||||
fi
|
||||
;;
|
||||
atcoder)
|
||||
if [ -z "$PROBLEM_LETTER" ]; then
|
||||
echo "AtCoder requires problem letter (e.g., make scrape atcoder abc042 a)"
|
||||
rm "$TMPFILE"
|
||||
exit
|
||||
fi
|
||||
FULL_PROBLEM_ID="${PROBLEM}${PROBLEM_LETTER}"
|
||||
cd "$(dirname "$0")/../.." && uv run scrapers/atcoder.py "$PROBLEM" "$PROBLEM_LETTER" > "$TMPFILE"
|
||||
if [ $? -eq 0 ]; then
|
||||
cd "$ORIGDIR"
|
||||
awk '/^---INPUT---$/ {getline; while ($0 != "---OUTPUT---") {print; getline}} END {}' "$TMPFILE" > "io/$FULL_PROBLEM_ID.in"
|
||||
awk '/^---OUTPUT---$/ {getline; while ($0 != "---END---") {print; getline}} END {}' "$TMPFILE" > "io/$FULL_PROBLEM_ID.expected"
|
||||
echo "Scraped problem $FULL_PROBLEM_ID to io/$FULL_PROBLEM_ID.in and io/$FULL_PROBLEM_ID.expected"
|
||||
else
|
||||
echo "Failed to scrape problem $FULL_PROBLEM_ID"
|
||||
cat "$TMPFILE"
|
||||
rm "$TMPFILE"
|
||||
exit
|
||||
fi
|
||||
;;
|
||||
codeforces)
|
||||
if [ -z "$PROBLEM_LETTER" ]; then
|
||||
echo "Codeforces requires problem letter (e.g., make scrape codeforces 1234 A)"
|
||||
rm "$TMPFILE"
|
||||
exit
|
||||
fi
|
||||
FULL_PROBLEM_ID="${PROBLEM}${PROBLEM_LETTER}"
|
||||
cd "$(dirname "$0")/../.." && uv run scrapers/codeforces.py "$PROBLEM" "$PROBLEM_LETTER" > "$TMPFILE"
|
||||
if [ $? -eq 0 ]; then
|
||||
cd "$ORIGDIR"
|
||||
awk '/^---INPUT---$/ {getline; while ($0 != "---OUTPUT---") {print; getline}} END {}' "$TMPFILE" > "io/$FULL_PROBLEM_ID.in"
|
||||
awk '/^---OUTPUT---$/ {getline; while ($0 != "---END---") {print; getline}} END {}' "$TMPFILE" > "io/$FULL_PROBLEM_ID.expected"
|
||||
echo "Scraped problem $FULL_PROBLEM_ID to io/$FULL_PROBLEM_ID.in and io/$FULL_PROBLEM_ID.expected"
|
||||
else
|
||||
echo "Failed to scrape problem $FULL_PROBLEM_ID"
|
||||
echo "You can manually add test cases to io/$FULL_PROBLEM_ID.in and io/$FULL_PROBLEM_ID.expected"
|
||||
cat "$TMPFILE"
|
||||
rm "$TMPFILE"
|
||||
exit
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Unknown contest type: $CONTEST"
|
||||
echo "Available contests: cses, atcoder, codeforces"
|
||||
rm "$TMPFILE"
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
|
||||
rm "$TMPFILE"
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
execute_binary() {
|
||||
binary="$1"
|
||||
input="$2"
|
||||
output="$3"
|
||||
is_debug="$4"
|
||||
|
||||
start=$(date '+%s.%N')
|
||||
if [ -n "$is_debug" ]; then
|
||||
asan="$(ldconfig -p | grep libasan.so | head -n1 | awk '{print $4}')"
|
||||
LD_PRELOAD="$asan" timeout 2s ./"$binary" <"$input" >"$output" 2>&1
|
||||
else
|
||||
timeout 2s ./"$binary" <"$input" >"$output" 2>&1
|
||||
fi
|
||||
CODE=$?
|
||||
end=$(date '+%s.%N')
|
||||
truncate -s "$(head -n 1000 "$output" | wc -c)" "$output"
|
||||
|
||||
if [ $CODE -ge 124 ]; then
|
||||
MSG=''
|
||||
case $CODE in
|
||||
124) MSG='TIMEOUT' ;;
|
||||
128) MSG='SIGILL' ;;
|
||||
130) MSG='SIGABRT' ;;
|
||||
131) MSG='SIGBUS' ;;
|
||||
136) MSG='SIGFPE' ;;
|
||||
135) MSG='SIGSEGV' ;;
|
||||
137) MSG='SIGPIPE' ;;
|
||||
139) MSG='SIGTERM' ;;
|
||||
esac
|
||||
[ $CODE -ne 124 ] && sed -i '$d' "$output"
|
||||
test -n "$MSG" && printf '\n[code]: %s (%s)' "$CODE" "$MSG" >>"$output"
|
||||
else
|
||||
printf '\n[code]: %s' "$CODE" >>"$output"
|
||||
fi
|
||||
|
||||
printf '\n[time]: %s ms' "$(awk "BEGIN {print ($end - $start) * 1000}")" >>$output
|
||||
test -n "$is_debug" && is_debug_string=true || is_debug_string=false
|
||||
printf '\n[debug]: %s' "$is_debug_string" >>$output
|
||||
|
||||
expected_file="${output%.out}.expected"
|
||||
if [ -f "$expected_file" ] && [ $CODE -eq 0 ]; then
|
||||
awk '/^\[[^]]*\]:/ {exit} {print}' "$output" > /tmp/program_output
|
||||
if cmp -s /tmp/program_output "$expected_file"; then
|
||||
printf '\n[matches]: true' >>"$output"
|
||||
else
|
||||
printf '\n[matches]: false' >>"$output"
|
||||
fi
|
||||
rm -f /tmp/program_output
|
||||
fi
|
||||
|
||||
return $CODE
|
||||
}
|
||||
|
||||
compile_source() {
|
||||
src="$1"
|
||||
bin="$2"
|
||||
output="$3"
|
||||
flags="$4"
|
||||
|
||||
test -f "$bin" && rm "$bin" || true
|
||||
g++ @compile_flags.txt $flags "$src" -o "$bin" 2>"$output"
|
||||
CODE=$?
|
||||
|
||||
if [ $CODE -gt 0 ]; then
|
||||
printf '\n[code]: %s' "$CODE" >>"$output"
|
||||
return $CODE
|
||||
else
|
||||
echo '' >"$output"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue