@bniladridas/cursor 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clang-tidy +28 -0
- package/.dockerignore +56 -0
- package/.env.example +29 -0
- package/.github/CODEOWNERS +2 -0
- package/.github/ISSUE_TEMPLATE/blank.md +27 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- package/.github/SECURITY.md +24 -0
- package/.github/codeql/codeql-config.yml +8 -0
- package/.github/dependabot.yml +14 -0
- package/.github/labeler.yml +50 -0
- package/.github/packaging/brand-cursor.png +0 -0
- package/.github/packaging/database/init.sql +48 -0
- package/.github/packaging/docker/Dockerfile +111 -0
- package/.github/packaging/docker/docker-compose.yml +56 -0
- package/.github/packaging/scripts/preflight.sh +413 -0
- package/.github/packaging/scripts/prepare-release.sh +141 -0
- package/.github/packaging/scripts/release.sh +22 -0
- package/.github/packaging/scripts/setup-git-hooks.sh +73 -0
- package/.github/pull_request_template.md +31 -0
- package/.github/signed.json +9 -0
- package/.github/workflows/README.md +23 -0
- package/.github/workflows/ci.yml +181 -0
- package/.github/workflows/cla.yml +33 -0
- package/.github/workflows/formula-sha.yml +63 -0
- package/.github/workflows/issue-response.yml +44 -0
- package/.github/workflows/labeler.yml +42 -0
- package/.github/workflows/pr-body.yml +49 -0
- package/.github/workflows/release.yml +176 -0
- package/.github/workflows/security.yml +94 -0
- package/.github/workflows/stale.yml +38 -0
- package/AGENTS.md +49 -0
- package/CHANGELOG.md +3 -0
- package/CMakeLists.txt +646 -0
- package/Formula/cursor.rb +46 -0
- package/LICENSE +201 -0
- package/Makefile +28 -0
- package/README.md +46 -0
- package/cli.js +16 -0
- package/include/agent.h +86 -0
- package/include/agent_mode.h +17 -0
- package/include/memory_manager.h +102 -0
- package/include/services/ai_service.h +31 -0
- package/include/services/auth_service.h +87 -0
- package/include/services/checkpoint_service.h +69 -0
- package/include/services/codebase_service.h +38 -0
- package/include/services/command_service.h +23 -0
- package/include/services/context_service.h +74 -0
- package/include/services/database_service.h +56 -0
- package/include/services/error_service.h +106 -0
- package/include/services/file_service.h +51 -0
- package/include/services/git_service.h +29 -0
- package/include/services/github_service.h +85 -0
- package/include/services/mcp_service.h +85 -0
- package/include/services/multi_file_service.h +93 -0
- package/include/services/sandbox_service.h +96 -0
- package/include/services/theme_service.h +67 -0
- package/include/services/web_service.h +52 -0
- package/include/utils/config.h +68 -0
- package/include/utils/memory_utils.h +79 -0
- package/include/utils/platform.h +56 -0
- package/include/utils/ui.h +43 -0
- package/include/utils/validation.h +63 -0
- package/include/utils/version.h.in +17 -0
- package/install.js +49 -0
- package/package.json +16 -0
- package/release/checksums.txt +3 -0
- package/release/cursor-linux/cursor_v0.1.7_linux_amd64.tar.gz +0 -0
- package/release/cursor-macos/cursor_v0.1.7_darwin_arm64.tar.gz +0 -0
- package/release/cursor-windows/cursor__windows_amd64.zip +0 -0
- package/src/agent.cpp +2026 -0
- package/src/main.cpp +97 -0
- package/src/memory_manager.cpp +814 -0
- package/src/services/ai_service.cpp +366 -0
- package/src/services/auth_service.cpp +779 -0
- package/src/services/checkpoint_service.cpp +465 -0
- package/src/services/codebase_service.cpp +233 -0
- package/src/services/command_service.cpp +82 -0
- package/src/services/context_service.cpp +348 -0
- package/src/services/database_service.cpp +148 -0
- package/src/services/error_service.cpp +438 -0
- package/src/services/file_service.cpp +349 -0
- package/src/services/git_service.cpp +148 -0
- package/src/services/github_service.cpp +435 -0
- package/src/services/mcp_service.cpp +481 -0
- package/src/services/multi_file_service.cpp +591 -0
- package/src/services/sandbox_service.cpp +678 -0
- package/src/services/theme_service.cpp +429 -0
- package/src/services/web_service.cpp +532 -0
- package/src/utils/config.cpp +77 -0
- package/src/utils/memory_utils.cpp +93 -0
- package/src/utils/ui.cpp +307 -0
- package/src/utils/validation.cpp +306 -0
- package/src/utils/version.cpp +175 -0
- package/tests/e2e/docker-compose.yml +195 -0
- package/tests/e2e/run_e2e_tests.sh +70 -0
- package/tests/e2e/run_tests_in_docker.sh +115 -0
- package/tests/main_test.cpp +16 -0
- package/tests/mocks/mock_ollama.py +98 -0
- package/tests/mocks/start_nginx.sh +64 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#include "services/command_service.h"
|
|
2
|
+
|
|
3
|
+
#include "utils/platform.h"
|
|
4
|
+
|
|
5
|
+
#include <array>
|
|
6
|
+
#include <chrono>
|
|
7
|
+
#include <cstdio>
|
|
8
|
+
#include <cstring>
|
|
9
|
+
#include <future>
|
|
10
|
+
#include <regex>
|
|
11
|
+
#include <stdexcept>
|
|
12
|
+
|
|
13
|
+
namespace Services {
|
|
14
|
+
const std::array<std::string, 9> CommandService::dangerous_commands = {
|
|
15
|
+
"rm", "sudo rm", "format", "del /", "shutdown",
|
|
16
|
+
"reboot", "mkfs", "fdisk", "dd"};
|
|
17
|
+
|
|
18
|
+
bool CommandService::is_dangerous_command(const std::string &command) {
|
|
19
|
+
// Token-based matching to avoid substring false positives
|
|
20
|
+
for (const auto &danger : dangerous_commands) {
|
|
21
|
+
std::regex pattern(
|
|
22
|
+
"\\b" + std::regex_replace(danger, std::regex(" "), "\\s+") + "\\b");
|
|
23
|
+
if (std::regex_search(command, pattern)) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
std::string
|
|
31
|
+
CommandService::execute_command([[maybe_unused]] const std::string &command,
|
|
32
|
+
FILE *pipe) {
|
|
33
|
+
if (!pipe) {
|
|
34
|
+
throw std::runtime_error("Failed to execute command");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
std::string result;
|
|
38
|
+
char buffer[256];
|
|
39
|
+
size_t total_bytes = 0;
|
|
40
|
+
const size_t MAX_BYTES = 10 * 1024; // 10 KB limit
|
|
41
|
+
|
|
42
|
+
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
|
43
|
+
total_bytes += strlen(buffer);
|
|
44
|
+
if (total_bytes > MAX_BYTES) {
|
|
45
|
+
result += "\n[Output truncated]";
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
result += buffer;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
int exit_code = Utils::Platform::close_process(pipe);
|
|
52
|
+
if (exit_code != 0) {
|
|
53
|
+
result += "\nExit code: " + std::to_string(exit_code);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return result.empty() ? "Command completed" : result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
std::string CommandService::execute(const std::string &command) {
|
|
60
|
+
if (is_dangerous_command(command)) {
|
|
61
|
+
return "Error: Dangerous command blocked";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
auto future = std::async(std::launch::async, [&]() -> std::string {
|
|
66
|
+
// Use platform-agnostic process handling
|
|
67
|
+
FILE *pipe = Utils::Platform::open_process(
|
|
68
|
+
command + Utils::Platform::get_shell_redirect_both(), "r");
|
|
69
|
+
return execute_command(command, pipe);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Timeout: 5 seconds
|
|
73
|
+
if (future.wait_for(std::chrono::seconds(5)) ==
|
|
74
|
+
std::future_status::timeout) {
|
|
75
|
+
return "Error: Command timed out";
|
|
76
|
+
}
|
|
77
|
+
return future.get();
|
|
78
|
+
} catch (const std::exception &e) {
|
|
79
|
+
return std::string("Error executing command: ") + e.what();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} // namespace Services
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
#include "services/context_service.h"
|
|
2
|
+
#include <algorithm>
|
|
3
|
+
#include <cstdlib>
|
|
4
|
+
#include <filesystem>
|
|
5
|
+
#include <fstream>
|
|
6
|
+
#include <iostream>
|
|
7
|
+
#include <sstream>
|
|
8
|
+
|
|
9
|
+
namespace Services {
|
|
10
|
+
|
|
11
|
+
// Static member definitions
|
|
12
|
+
std::map<std::string, std::string> ContextService::context_cache;
|
|
13
|
+
std::string ContextService::cached_working_directory;
|
|
14
|
+
|
|
15
|
+
std::string ContextService::load_hierarchical_context(
|
|
16
|
+
const std::string &working_directory) {
|
|
17
|
+
// Check cache first
|
|
18
|
+
if (cached_working_directory == working_directory &&
|
|
19
|
+
context_cache.find("hierarchical") != context_cache.end()) {
|
|
20
|
+
return context_cache["hierarchical"];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
std::vector<ContextFile> all_files = find_context_files(working_directory);
|
|
24
|
+
std::string merged_context = merge_context_files(all_files);
|
|
25
|
+
|
|
26
|
+
// Update cache
|
|
27
|
+
cached_working_directory = working_directory;
|
|
28
|
+
context_cache["hierarchical"] = merged_context;
|
|
29
|
+
|
|
30
|
+
return merged_context;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
std::vector<ContextFile>
|
|
34
|
+
ContextService::find_context_files(const std::string &working_directory) {
|
|
35
|
+
std::vector<ContextFile> files;
|
|
36
|
+
|
|
37
|
+
// 1. Load global context (highest priority)
|
|
38
|
+
ContextFile global_context = load_global_context();
|
|
39
|
+
if (!global_context.content.empty()) {
|
|
40
|
+
files.push_back(global_context);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 2. Load project context files (from root to current)
|
|
44
|
+
auto project_files = load_project_context(working_directory);
|
|
45
|
+
files.insert(files.end(), project_files.begin(), project_files.end());
|
|
46
|
+
|
|
47
|
+
// 3. Load local context files (current directory and subdirectories)
|
|
48
|
+
auto local_files = load_local_context(working_directory);
|
|
49
|
+
files.insert(files.end(), local_files.begin(), local_files.end());
|
|
50
|
+
|
|
51
|
+
// Sort by priority (higher priority first)
|
|
52
|
+
std::sort(files.begin(), files.end(),
|
|
53
|
+
[](const ContextFile &a, const ContextFile &b) {
|
|
54
|
+
return a.priority > b.priority;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return files;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
ContextFile ContextService::load_global_context() {
|
|
61
|
+
ContextFile context;
|
|
62
|
+
context.source = "global";
|
|
63
|
+
context.priority = 100; // Highest priority
|
|
64
|
+
|
|
65
|
+
std::string home_dir = get_home_directory();
|
|
66
|
+
if (home_dir.empty()) {
|
|
67
|
+
return context;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
std::string global_path = home_dir + "/.cursor/CURSOR.md";
|
|
71
|
+
|
|
72
|
+
if (is_readable_file(global_path)) {
|
|
73
|
+
context.file_path = global_path;
|
|
74
|
+
context.content = load_file_content(global_path);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return context;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
std::vector<ContextFile>
|
|
81
|
+
ContextService::load_project_context(const std::string &working_directory) {
|
|
82
|
+
std::vector<ContextFile> files;
|
|
83
|
+
|
|
84
|
+
// Find project root
|
|
85
|
+
std::string project_root = find_project_root(working_directory);
|
|
86
|
+
if (project_root.empty()) {
|
|
87
|
+
return files;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Walk from project root to current directory
|
|
91
|
+
std::filesystem::path current_path =
|
|
92
|
+
std::filesystem::absolute(working_directory);
|
|
93
|
+
std::filesystem::path root_path = std::filesystem::absolute(project_root);
|
|
94
|
+
|
|
95
|
+
std::vector<std::filesystem::path> path_hierarchy;
|
|
96
|
+
|
|
97
|
+
// Build path hierarchy from root to current
|
|
98
|
+
std::filesystem::path temp_path = current_path;
|
|
99
|
+
while (temp_path != root_path && temp_path.has_parent_path()) {
|
|
100
|
+
path_hierarchy.push_back(temp_path);
|
|
101
|
+
temp_path = temp_path.parent_path();
|
|
102
|
+
}
|
|
103
|
+
path_hierarchy.push_back(root_path);
|
|
104
|
+
|
|
105
|
+
// Reverse to go from root to current
|
|
106
|
+
std::reverse(path_hierarchy.begin(), path_hierarchy.end());
|
|
107
|
+
|
|
108
|
+
// Load CURSOR.md files in hierarchy order
|
|
109
|
+
int priority = 90; // Start below global priority
|
|
110
|
+
for (const auto &path : path_hierarchy) {
|
|
111
|
+
std::string context_file = path.string() + "/CURSOR.md";
|
|
112
|
+
|
|
113
|
+
if (is_readable_file(context_file)) {
|
|
114
|
+
ContextFile context;
|
|
115
|
+
context.file_path = context_file;
|
|
116
|
+
context.content = load_file_content(context_file);
|
|
117
|
+
context.priority = priority;
|
|
118
|
+
context.source = "project";
|
|
119
|
+
|
|
120
|
+
files.push_back(context);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
priority -= 10; // Decrease priority as we go deeper
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return files;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
std::vector<ContextFile>
|
|
130
|
+
ContextService::load_local_context(const std::string &working_directory) {
|
|
131
|
+
std::vector<ContextFile> files;
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
// Look for CURSOR.md files in subdirectories
|
|
135
|
+
for (const auto &entry :
|
|
136
|
+
std::filesystem::recursive_directory_iterator(working_directory)) {
|
|
137
|
+
if (entry.is_regular_file() &&
|
|
138
|
+
entry.path().filename() == "CURSOR.md") {
|
|
139
|
+
|
|
140
|
+
std::string entry_path = entry.path().string();
|
|
141
|
+
|
|
142
|
+
// Simple check - if it's in a parent directory, skip
|
|
143
|
+
std::filesystem::path rel_path =
|
|
144
|
+
std::filesystem::relative(entry.path(), working_directory);
|
|
145
|
+
if (rel_path.string().find("..") == 0) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
ContextFile context;
|
|
150
|
+
context.file_path = entry_path;
|
|
151
|
+
context.content = load_file_content(entry_path);
|
|
152
|
+
context.priority = 10; // Lower priority than project files
|
|
153
|
+
context.source = "local";
|
|
154
|
+
|
|
155
|
+
files.push_back(context);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch (const std::exception &e) {
|
|
159
|
+
std::cerr << "Error loading local context: " << e.what() << std::endl;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return files;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
std::string
|
|
166
|
+
ContextService::merge_context_files(const std::vector<ContextFile> &files) {
|
|
167
|
+
if (files.empty()) {
|
|
168
|
+
return "";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
std::ostringstream merged;
|
|
172
|
+
merged << "# Cursor Context\n\n";
|
|
173
|
+
|
|
174
|
+
// Group files by source
|
|
175
|
+
std::map<std::string, std::vector<const ContextFile *>> files_by_source;
|
|
176
|
+
for (const auto &file : files) {
|
|
177
|
+
files_by_source[file.source].push_back(&file);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Output in order: global, project, local
|
|
181
|
+
std::vector<std::string> source_order = {"global", "project", "local"};
|
|
182
|
+
|
|
183
|
+
for (const auto &source : source_order) {
|
|
184
|
+
if (files_by_source.find(source) == files_by_source.end()) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
merged << "## " << source << " Context\n\n";
|
|
189
|
+
|
|
190
|
+
for (const auto *file : files_by_source[source]) {
|
|
191
|
+
std::string display_path = get_display_path(file->file_path, ".");
|
|
192
|
+
merged << "### From: " << display_path << "\n\n";
|
|
193
|
+
merged << file->content;
|
|
194
|
+
|
|
195
|
+
if (!file->content.empty() && file->content.back() != '\n') {
|
|
196
|
+
merged << "\n";
|
|
197
|
+
}
|
|
198
|
+
merged << "\n";
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return merged.str();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
std::string
|
|
206
|
+
ContextService::find_project_root(const std::string &starting_directory) {
|
|
207
|
+
std::filesystem::path current_path =
|
|
208
|
+
std::filesystem::absolute(starting_directory);
|
|
209
|
+
|
|
210
|
+
while (current_path.has_parent_path()) {
|
|
211
|
+
if (is_project_root(current_path.string())) {
|
|
212
|
+
return current_path.string();
|
|
213
|
+
}
|
|
214
|
+
current_path = current_path.parent_path();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return ""; // No project root found
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
bool ContextService::is_project_root(const std::string &directory) {
|
|
221
|
+
std::vector<std::string> project_markers = {
|
|
222
|
+
".git", "package.json", "CMakeLists.txt", "Makefile",
|
|
223
|
+
"Cargo.toml", "pom.xml", "build.gradle", "requirements.txt",
|
|
224
|
+
"setup.py", "go.mod", ".gitignore"};
|
|
225
|
+
|
|
226
|
+
for (const auto &marker : project_markers) {
|
|
227
|
+
std::string marker_path = directory + "/" + marker;
|
|
228
|
+
if (std::filesystem::exists(marker_path)) {
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
void ContextService::refresh_context_cache() {
|
|
237
|
+
context_cache.clear();
|
|
238
|
+
cached_working_directory.clear();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
std::string ContextService::get_context_template() {
|
|
242
|
+
return R"(# Cursor Context File
|
|
243
|
+
|
|
244
|
+
This file provides context and instructions for Cursor AI assistant.
|
|
245
|
+
|
|
246
|
+
## Project Overview
|
|
247
|
+
|
|
248
|
+
Brief description of this project/component.
|
|
249
|
+
|
|
250
|
+
## Key Information
|
|
251
|
+
|
|
252
|
+
- Important facts about the codebase
|
|
253
|
+
- Coding standards and conventions
|
|
254
|
+
- Architecture decisions
|
|
255
|
+
- Dependencies and requirements
|
|
256
|
+
|
|
257
|
+
## Instructions for AI
|
|
258
|
+
|
|
259
|
+
- Specific guidance for AI when working in this context
|
|
260
|
+
- Preferred approaches or patterns
|
|
261
|
+
- Things to avoid or be careful about
|
|
262
|
+
|
|
263
|
+
## Examples
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
Example code or commands that are commonly used in this context
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## References
|
|
270
|
+
|
|
271
|
+
- Links to documentation
|
|
272
|
+
- Related files or components
|
|
273
|
+
- External resources
|
|
274
|
+
)";
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
bool ContextService::create_context_file(const std::string &directory,
|
|
278
|
+
const std::string &content) {
|
|
279
|
+
try {
|
|
280
|
+
std::string file_path = directory + "/CURSOR.md";
|
|
281
|
+
|
|
282
|
+
// Don't overwrite existing file
|
|
283
|
+
if (std::filesystem::exists(file_path)) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
std::ofstream file(file_path);
|
|
288
|
+
if (!file.is_open()) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (content.empty()) {
|
|
293
|
+
file << get_context_template();
|
|
294
|
+
} else {
|
|
295
|
+
file << content;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return true;
|
|
299
|
+
} catch (const std::exception &) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
std::string ContextService::get_home_directory() {
|
|
305
|
+
const char *home = std::getenv("HOME");
|
|
306
|
+
if (!home) {
|
|
307
|
+
home = std::getenv("USERPROFILE"); // Windows
|
|
308
|
+
}
|
|
309
|
+
return home ? std::string(home) : "";
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
bool ContextService::is_readable_file(const std::string &file_path) {
|
|
313
|
+
try {
|
|
314
|
+
return std::filesystem::exists(file_path) &&
|
|
315
|
+
std::filesystem::is_regular_file(file_path);
|
|
316
|
+
} catch (const std::exception &) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
std::string ContextService::load_file_content(const std::string &file_path) {
|
|
322
|
+
try {
|
|
323
|
+
std::ifstream file(file_path);
|
|
324
|
+
if (!file.is_open()) {
|
|
325
|
+
return "";
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
std::ostringstream content;
|
|
329
|
+
content << file.rdbuf();
|
|
330
|
+
return content.str();
|
|
331
|
+
} catch (const std::exception &) {
|
|
332
|
+
return "";
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
std::string
|
|
337
|
+
ContextService::get_display_path(const std::string &file_path,
|
|
338
|
+
const std::string &base_directory) {
|
|
339
|
+
try {
|
|
340
|
+
std::filesystem::path rel_path =
|
|
341
|
+
std::filesystem::relative(file_path, base_directory);
|
|
342
|
+
return rel_path.string();
|
|
343
|
+
} catch (const std::exception &) {
|
|
344
|
+
return file_path;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
} // namespace Services
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#include "services/database_service.h"
|
|
2
|
+
#include <iostream>
|
|
3
|
+
#include <vector>
|
|
4
|
+
|
|
5
|
+
namespace cursor {
|
|
6
|
+
|
|
7
|
+
DatabaseService::DatabaseService()
|
|
8
|
+
#ifdef HAVE_PQXX
|
|
9
|
+
: connection_(nullptr)
|
|
10
|
+
#endif
|
|
11
|
+
{
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
DatabaseService::~DatabaseService() { disconnect(); }
|
|
15
|
+
|
|
16
|
+
bool DatabaseService::connect(const std::string &host, int port,
|
|
17
|
+
const std::string &dbname,
|
|
18
|
+
const std::string &user,
|
|
19
|
+
const std::string &password) {
|
|
20
|
+
#ifdef HAVE_PQXX
|
|
21
|
+
try {
|
|
22
|
+
std::string connection_string =
|
|
23
|
+
"host=" + host + " port=" + std::to_string(port) + " dbname=" + dbname +
|
|
24
|
+
" user=" + user + " password=" + password;
|
|
25
|
+
|
|
26
|
+
connection_ = std::make_unique<pqxx::connection>(connection_string);
|
|
27
|
+
|
|
28
|
+
// The pqxx::connection constructor throws on failure, so if we get here,
|
|
29
|
+
// the connection is open.
|
|
30
|
+
std::cout << "Connected to database: " << connection_->dbname()
|
|
31
|
+
<< std::endl;
|
|
32
|
+
return true;
|
|
33
|
+
} catch (const std::exception &e) {
|
|
34
|
+
std::cerr << "Database connection error: " << e.what() << std::endl;
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
#else
|
|
38
|
+
(void)host;
|
|
39
|
+
(void)port;
|
|
40
|
+
(void)dbname;
|
|
41
|
+
(void)user;
|
|
42
|
+
(void)password;
|
|
43
|
+
return false;
|
|
44
|
+
#endif
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
void DatabaseService::disconnect() {
|
|
48
|
+
#ifdef HAVE_PQXX
|
|
49
|
+
connection_.reset();
|
|
50
|
+
#endif
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
bool DatabaseService::isConnected() const {
|
|
54
|
+
#ifdef HAVE_PQXX
|
|
55
|
+
return connection_ && connection_->is_open();
|
|
56
|
+
#else
|
|
57
|
+
return false;
|
|
58
|
+
#endif
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
bool DatabaseService::executeQuery(const std::string &query,
|
|
62
|
+
const std::vector<std::string> ¶ms) {
|
|
63
|
+
#ifdef HAVE_PQXX
|
|
64
|
+
if (!isConnected()) {
|
|
65
|
+
std::cerr << "Not connected to database" << std::endl;
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
pqxx::work txn(*connection_);
|
|
71
|
+
#if defined(__APPLE__)
|
|
72
|
+
pqxx::params p;
|
|
73
|
+
for (const auto ¶m : params) {
|
|
74
|
+
p.append(param);
|
|
75
|
+
}
|
|
76
|
+
txn.exec(query, p);
|
|
77
|
+
#else
|
|
78
|
+
std::string q = query;
|
|
79
|
+
for (size_t i = 0; i < params.size(); ++i) {
|
|
80
|
+
std::string placeholder = "$" + std::to_string(i + 1);
|
|
81
|
+
size_t pos = q.find(placeholder);
|
|
82
|
+
if (pos != std::string::npos) {
|
|
83
|
+
q.replace(pos, placeholder.size(), txn.quote(params[i]));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
txn.exec(q);
|
|
87
|
+
#endif
|
|
88
|
+
txn.commit();
|
|
89
|
+
return true;
|
|
90
|
+
} catch (const std::exception &e) {
|
|
91
|
+
std::cerr << "Query execution error: " << e.what() << std::endl;
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
#else
|
|
95
|
+
(void)query;
|
|
96
|
+
(void)params;
|
|
97
|
+
std::cerr << "Database support is disabled as libpqxx is not available."
|
|
98
|
+
<< std::endl;
|
|
99
|
+
return false;
|
|
100
|
+
#endif
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
std::unique_ptr<pqxx::result>
|
|
104
|
+
DatabaseService::executeSelect(const std::string &query,
|
|
105
|
+
const std::vector<std::string> ¶ms) {
|
|
106
|
+
#ifdef HAVE_PQXX
|
|
107
|
+
if (!isConnected()) {
|
|
108
|
+
std::cerr << "Not connected to database" << std::endl;
|
|
109
|
+
return nullptr;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
pqxx::work txn(*connection_);
|
|
114
|
+
#if defined(__APPLE__)
|
|
115
|
+
pqxx::params p;
|
|
116
|
+
for (const auto ¶m : params) {
|
|
117
|
+
p.append(param);
|
|
118
|
+
}
|
|
119
|
+
auto result = txn.exec(query, p);
|
|
120
|
+
txn.commit();
|
|
121
|
+
return std::make_unique<pqxx::result>(std::move(result));
|
|
122
|
+
#else
|
|
123
|
+
std::string q = query;
|
|
124
|
+
for (size_t i = 0; i < params.size(); ++i) {
|
|
125
|
+
std::string placeholder = "$" + std::to_string(i + 1);
|
|
126
|
+
size_t pos = q.find(placeholder);
|
|
127
|
+
if (pos != std::string::npos) {
|
|
128
|
+
q.replace(pos, placeholder.size(), txn.quote(params[i]));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
auto result = txn.exec(q);
|
|
132
|
+
txn.commit();
|
|
133
|
+
return std::make_unique<pqxx::result>(std::move(result));
|
|
134
|
+
#endif
|
|
135
|
+
} catch (const std::exception &e) {
|
|
136
|
+
std::cerr << "Select query error: " << e.what() << std::endl;
|
|
137
|
+
return nullptr;
|
|
138
|
+
}
|
|
139
|
+
#else
|
|
140
|
+
(void)query;
|
|
141
|
+
(void)params;
|
|
142
|
+
std::cerr << "Database support is disabled as libpqxx is not available."
|
|
143
|
+
<< std::endl;
|
|
144
|
+
return nullptr;
|
|
145
|
+
#endif
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
} // namespace cursor
|