Skip to content

Commit 3ccdf5d

Browse files
committed
feat(cli): support project-aware note commands
1 parent b699b55 commit 3ccdf5d

1 file changed

Lines changed: 185 additions & 2 deletions

File tree

src/commands/NoteCommand.cpp

Lines changed: 185 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,163 @@ namespace
6464
out = static_cast<std::uint16_t>(port);
6565
return true;
6666
}
67+
68+
int run_export_command(const std::vector<std::string> &args)
69+
{
70+
using namespace vix::cli::style;
71+
72+
if (args.empty() || is_help_arg(args[0]))
73+
{
74+
std::cout
75+
<< "Usage:\n"
76+
<< " vix note export <file.vixnote> --out <file.html> [options]\n\n"
77+
78+
<< "Description:\n"
79+
<< " Export a Vix Note document to a standalone HTML lesson.\n\n"
80+
81+
<< "Options:\n"
82+
<< " --out <file.html> Output HTML file\n"
83+
<< " --out=<file.html> Same as --out <file.html>\n"
84+
<< " --no-outputs Export without cell outputs\n"
85+
<< " --with-outputs Export with cell outputs, default\n"
86+
<< " -h, --help Show this help\n\n"
87+
88+
<< "Examples:\n"
89+
<< " vix note export examples/hello.vixnote --out hello.html\n"
90+
<< " vix note export lessons/pointers.vixnote --out pointers.html --no-outputs\n";
91+
92+
return args.empty() ? 1 : 0;
93+
}
94+
95+
fs::path notePath;
96+
fs::path outPath;
97+
bool includeOutputs = true;
98+
99+
for (std::size_t i = 0; i < args.size(); ++i)
100+
{
101+
const std::string &arg = args[i];
102+
103+
if (is_help_arg(arg))
104+
{
105+
return run_export_command({"--help"});
106+
}
107+
108+
if (arg == "--out")
109+
{
110+
if (i + 1 >= args.size())
111+
{
112+
error("Missing value for --out.");
113+
hint("Usage: vix note export <file.vixnote> --out <file.html>");
114+
return 1;
115+
}
116+
117+
outPath = args[++i];
118+
continue;
119+
}
120+
121+
constexpr const char outPrefix[] = "--out=";
122+
123+
if (arg.rfind(outPrefix, 0) == 0)
124+
{
125+
outPath = arg.substr(sizeof(outPrefix) - 1);
126+
continue;
127+
}
128+
129+
if (arg == "--no-outputs")
130+
{
131+
includeOutputs = false;
132+
continue;
133+
}
134+
135+
if (arg == "--with-outputs")
136+
{
137+
includeOutputs = true;
138+
continue;
139+
}
140+
141+
if (!notePath.empty())
142+
{
143+
error("Unexpected argument: " + arg);
144+
hint("Usage: vix note export <file.vixnote> --out <file.html>");
145+
return 1;
146+
}
147+
148+
notePath = arg;
149+
}
150+
151+
if (notePath.empty())
152+
{
153+
error("No Vix Note file provided.");
154+
hint("Usage: vix note export <file.vixnote> --out <file.html>");
155+
return 1;
156+
}
157+
158+
if (outPath.empty())
159+
{
160+
error("No output HTML file provided.");
161+
hint("Use --out <file.html>.");
162+
return 1;
163+
}
164+
165+
std::error_code ec;
166+
167+
if (!fs::exists(notePath, ec) || ec)
168+
{
169+
error("Note file not found: " + notePath.string());
170+
return 1;
171+
}
172+
173+
if (!fs::is_regular_file(notePath, ec) || ec)
174+
{
175+
error("Note path is not a file: " + notePath.string());
176+
return 1;
177+
}
178+
179+
if (notePath.extension() != ".vixnote")
180+
{
181+
error("Invalid note file extension: " + notePath.string());
182+
hint("Expected a .vixnote file.");
183+
return 1;
184+
}
185+
186+
vix::note::NoteLoadResult loaded =
187+
vix::note::load_note(notePath);
188+
189+
if (!loaded.ok)
190+
{
191+
error("Unable to load note file.");
192+
hint(loaded.error.empty() ? notePath.string() : loaded.error);
193+
return 1;
194+
}
195+
196+
vix::note::HtmlExporterOptions exportOptions;
197+
exportOptions.includeOutputs = includeOutputs;
198+
exportOptions.standalone = true;
199+
exportOptions.includeDocumentMetadata = true;
200+
exportOptions.includeTableOfContents = true;
201+
exportOptions.includeOutputLabels = true;
202+
exportOptions.printableLayout = true;
203+
204+
vix::note::HtmlExporter exporter(exportOptions);
205+
206+
vix::note::NoteResult exported =
207+
exporter.export_to_file(loaded.document, outPath);
208+
209+
if (!exported.ok())
210+
{
211+
error("Unable to export note.");
212+
hint(exported.message().empty()
213+
? outPath.string()
214+
: exported.message());
215+
216+
return exported.exit_code() == 0 ? 1 : exported.exit_code();
217+
}
218+
219+
success("Vix Note exported.");
220+
step(outPath.string());
221+
222+
return 0;
223+
}
67224
}
68225

69226
namespace vix::commands
@@ -77,6 +234,15 @@ namespace vix::commands
77234
return help();
78235
}
79236

237+
if (!args.empty() && args[0] == "export")
238+
{
239+
std::vector<std::string> exportArgs(
240+
args.begin() + 1,
241+
args.end());
242+
243+
return run_export_command(exportArgs);
244+
}
245+
80246
fs::path notePath;
81247
std::string host = "127.0.0.1";
82248
std::uint16_t port = 5179;
@@ -207,10 +373,14 @@ namespace vix::commands
207373
return 1;
208374
}
209375

376+
vix::note::ProjectContext projectContext =
377+
vix::note::detect_project_context(notePath);
378+
210379
vix::note::NoteServerOptions options;
211380
options.host = host;
212381
options.port = port;
213382
options.openBrowser = false;
383+
options.routeOptions.kernelOptions.projectContext = projectContext;
214384

215385
vix::note::NoteServer server(
216386
std::move(loaded.document),
@@ -232,6 +402,14 @@ namespace vix::commands
232402
info("Open this URL in your browser:");
233403
step(server.url());
234404

405+
if (projectContext.enabled)
406+
{
407+
info("Project context:");
408+
step(projectContext.projectName.empty()
409+
? projectContext.projectRoot.string()
410+
: projectContext.projectName);
411+
}
412+
235413
std::cout << "\n";
236414
hint("Press Ctrl+C to stop the note server.");
237415

@@ -253,23 +431,28 @@ namespace vix::commands
253431
{
254432
std::cout
255433
<< "Usage:\n"
256-
<< " vix note <file.vixnote> [options]\n\n"
434+
<< " vix note <file.vixnote> [options]\n"
435+
<< " vix note export <file.vixnote> --out <file.html> [options]\n\n"
257436

258437
<< "Description:\n"
259438
<< " Open a Vix Note document in a local browser UI.\n"
260439
<< " The command loads a .vixnote file, starts a local note server,\n"
261-
<< " and exposes the visual workspace through HTTP.\n\n"
440+
<< " and exposes the visual workspace through HTTP.\n"
441+
<< " It can also export a .vixnote file to a standalone HTML lesson.\n\n"
262442

263443
<< "Options:\n"
264444
<< " --host <host> Host used by the local server. Default: 127.0.0.1\n"
265445
<< " --host=<host> Same as --host <host>\n"
266446
<< " --port <port> Port used by the local server. Default: 5179\n"
267447
<< " --port=<port> Same as --port <port>\n"
448+
<< " export Export a .vixnote document to HTML\n"
268449
<< " -h, --help Show this help\n\n"
269450

270451
<< "Examples:\n"
271452
<< " vix note examples/hello.vixnote\n"
272453
<< " vix note lessons/pointers.vixnote --port 5180\n"
454+
<< " vix note export examples/hello.vixnote --out hello.html\n"
455+
<< " vix note export examples/hello.vixnote --out hello.html --no-outputs\n"
273456
<< " vix note examples/hello.vixnote --host 127.0.0.1 --port 5179\n\n"
274457

275458
<< "Routes:\n"

0 commit comments

Comments
 (0)