@@ -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
69226namespace 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