1use workspace_tools::{ workspace, Workspace };
7use std::{ fs, collections::HashMap };
8
9fn main() -> Result< (), Box< dyn core::error::Error > >
10{
11 println!( "š advanced workspace patterns and extensibility\n" );
12
13 let manager = AdvancedWorkspaceManager::new()?;
14 manager.demonstrate_patterns()?;
15 manager.cleanup()?;
16
17 println!( "\nšÆ this example demonstrates:" );
18 println!( " ⢠workspace plugin architecture" );
19 println!( " ⢠configuration overlays and environments" );
20 println!( " ⢠workspace templates and scaffolding" );
21 println!( " ⢠integration with other rust tools" );
22 println!( " ⢠advanced path resolution patterns" );
23 println!( " ⢠workspace composition and multi-workspace setups" );
24
25 println!( "\nā
congratulations! you've completed all workspace_tools examples" );
26 println!( " you now have a comprehensive understanding of workspace-relative development" );
27 println!( " start using workspace_tools in your projects to eliminate path resolution pain!" );
28
29 Ok( () )
30}
31
32struct AdvancedWorkspaceManager
33{
34 workspace : Workspace,
35 plugins : Vec< Box< dyn WorkspacePlugin > >,
36 environments : HashMap< String, EnvironmentConfig >,
37}
38
39trait WorkspacePlugin : Send + Sync
40{
41 fn name( &self ) -> &str;
42 fn initialize( &mut self, workspace : &Workspace ) -> Result< (), Box< dyn core::error::Error > >;
43 fn process( &self, workspace : &Workspace ) -> Result< PluginResult, Box< dyn core::error::Error > >;
44}
45
46struct PluginResult
47{
48 success : bool,
49 message : String,
50 data : HashMap< String, String >,
51}
52
53#[ derive( Clone ) ]
54struct EnvironmentConfig
55{
56 #[ allow( dead_code ) ]
57 name : String,
58 variables : HashMap< String, String >,
59 paths : HashMap< String, String >,
60 features : Vec< String >,
61}
62
63impl AdvancedWorkspaceManager
64{
65 fn new() -> Result< Self, Box< dyn core::error::Error > >
66 {
67 println!( "1ļøā£ initializing advanced workspace manager..." );
68
69 if std::env::var( "WORKSPACE_PATH" ).is_err()
70 {
71 std::env::set_var( "WORKSPACE_PATH", std::env::current_dir()? );
72 }
73
74 let workspace = workspace()?;
75
76 let mut plugins = Self::create_plugins();
78 for plugin in &mut plugins
79 {
80 plugin.initialize( &workspace )?;
81 println!( " initialized plugin: {}", plugin.name() );
82 }
83
84 let environments = Self::create_environments();
86
87 Self::setup_advanced_structure( &workspace )?;
89
90 println!( " ā
advanced manager initialized with {} plugins", plugins.len() );
91
92 Ok( Self { workspace, plugins, environments } )
93 }
94
95 fn demonstrate_patterns( &self ) -> Result< (), Box< dyn core::error::Error > >
96 {
97 println!( "\n2ļøā£ demonstrating advanced patterns:" );
98
99 self.demonstrate_plugin_system();
100 self.demonstrate_environment_overlays()?;
101 self.demonstrate_workspace_templates()?;
102 self.demonstrate_tool_integration()?;
103 self.demonstrate_multi_workspace_composition()?;
104
105 Ok( () )
106 }
107
108 fn demonstrate_plugin_system( &self )
109 {
110 println!( " š plugin system demonstration:" );
111
112 for plugin in &self.plugins
113 {
114 match plugin.process( &self.workspace )
115 {
116 Ok( result ) =>
117 {
118 println!( " {} -> {} ({})",
119 plugin.name(),
120 if result.success { "ā
" } else { "ā" },
121 result.message
122 );
123
124 for ( key, value ) in result.data
125 {
126 println!( " {key}: {value}" );
127 }
128 }
129 Err( e ) => println!( " {} -> error: {}", plugin.name(), e ),
130 }
131 }
132 }
133
134 fn demonstrate_environment_overlays( &self ) -> Result< (), Box< dyn core::error::Error > >
135 {
136 println!( "\n šļø environment overlay system:" );
137
138 for ( env_name, env_config ) in &self.environments
139 {
140 println!( " environment: {env_name}" );
141
142 let env_dir = self.workspace.config_dir().join( "environments" ).join( env_name );
144 fs::create_dir_all( &env_dir )?;
145
146 let base_config = format!( r#"# base configuration for {}
148debug = {}
149log_level = "{}"
150cache_enabled = {}
151"#,
152 env_name,
153 env_name == "development",
154 env_config.variables.get( "LOG_LEVEL" ).unwrap_or( &"info".to_string() ),
155 env_name != "testing"
156 );
157
158 fs::write( env_dir.join( "base.toml" ), base_config )?;
159
160 for feature in &env_config.features
162 {
163 let feature_config = format!( r#"# {feature} feature configuration
164[{feature}]
165enabled = true
166config_file = "config/features/{feature}.toml"
167"# );
168
169 fs::write( env_dir.join( format!( "{feature}.toml" ) ), feature_config )?;
170 println!( " created overlay: {env_name}/{feature}.toml" );
171 }
172
173 for ( key, value ) in &env_config.variables
175 {
176 println!( " env {key}: {value}" );
177 }
178
179 for ( path_name, path_value ) in &env_config.paths
181 {
182 let resolved_path = self.workspace.join( path_value );
183 println!( " path {}: {}", path_name, resolved_path.display() );
184 }
185 }
186
187 Ok( () )
188 }
189
190 fn demonstrate_workspace_templates( &self ) -> Result< (), Box< dyn core::error::Error > >
191 {
192 println!( "\n š workspace template system:" );
193
194 let templates = vec!
195 [
196 ( "rust-cli", Self::create_cli_template() ),
197 ( "web-service", Self::create_web_template() ),
198 ( "data-pipeline", Self::create_pipeline_template() ),
199 ( "desktop-app", Self::create_desktop_template() ),
200 ];
201
202 let templates_dir = self.workspace.join( "templates" );
203 fs::create_dir_all( &templates_dir )?;
204
205 for ( template_name, template_config ) in templates
206 {
207 let template_path = templates_dir.join( template_name );
208 fs::create_dir_all( &template_path )?;
209
210 let metadata = format!( r#"# workspace template: {}
212name = "{}"
213description = "{}"
214version = "1.0.0"
215author = "workspace_tools"
216
217[directories]
218{}
219
220[files]
221{}
222"#,
223 template_name,
224 template_name,
225 template_config.description,
226 template_config.directories.join( "\n" ),
227 template_config.files.iter()
228 .map( | ( name, _ ) | format!( r#""{name}" = "template""# ) )
229 .collect::< Vec< _ > >()
230 .join( "\n" )
231 );
232
233 fs::write( template_path.join( "template.toml" ), metadata )?;
234
235 let file_count = template_config.files.len();
237 for ( filename, content ) in &template_config.files
238 {
239 let file_path = template_path.join( filename );
240 if let Some( parent ) = file_path.parent()
241 {
242 fs::create_dir_all( parent )?;
243 }
244 fs::write( file_path, content )?;
245 }
246
247 println!( " created template: {template_name}" );
248 println!( " directories: {}", template_config.directories.len() );
249 println!( " files: {file_count}" );
250 }
251
252 Ok( () )
253 }
254
255 fn demonstrate_tool_integration( &self ) -> Result< (), Box< dyn core::error::Error > >
256 {
257 println!( "\n š§ rust ecosystem tool integration:" );
258
259 let cargo_config = format!( r#"# cargo configuration with workspace_tools
261[env]
262WORKSPACE_PATH = {{ value = ".", relative = true }}
263
264[build]
265target-dir = "{}/target"
266
267[install]
268root = "{}/bin"
269"#,
270 self.workspace.data_dir().display(),
271 self.workspace.join( "tools" ).display()
272 );
273
274 let cargo_dir = self.workspace.join( ".cargo" );
275 fs::create_dir_all( &cargo_dir )?;
276 fs::write( cargo_dir.join( "config.toml" ), cargo_config )?;
277 println!( " ā
cargo integration configured" );
278
279 let justfile = format!( r#"# justfile with workspace_tools integration
281# set workspace for all recipes
282export WORKSPACE_PATH := justfile_directory()
283
284# default recipe
285default:
286 @just --list
287
288# development tasks
289dev:
290 cargo run --example hello_workspace
291
292test:
293 cargo test --workspace
294
295# build tasks
296build:
297 cargo build --release
298
299# deployment tasks
300deploy env="staging":
301 echo "deploying to {{{{env}}}}"
302 echo "workspace: $WORKSPACE_PATH"
303
304# cleanup tasks
305clean:
306 cargo clean
307 rm -rf {}/target
308 rm -rf {}/logs/*
309"#,
310 self.workspace.data_dir().display(),
311 self.workspace.logs_dir().display()
312 );
313
314 fs::write( self.workspace.join( "justfile" ), justfile )?;
315 println!( " ā
just integration configured" );
316
317 let serde_example = r#"// serde integration with workspace_tools
319use serde::{Deserialize, Serialize};
320use workspace_tools::workspace;
321
322#[derive(Serialize, Deserialize)]
323struct AppConfig {
324 name: String,
325 version: String,
326 database_url: String,
327}
328
329fn load_config() -> Result<AppConfig, Box<dyn std::error::Error>> {
330 let ws = workspace()?;
331 let config_path = ws.find_config("app")?;
332 let config_str = std::fs::read_to_string(config_path)?;
333 let config: AppConfig = toml::from_str(&config_str)?;
334 Ok(config)
335}
336"#;
337
338 let examples_dir = self.workspace.join( "integration_examples" );
339 fs::create_dir_all( &examples_dir )?;
340 fs::write( examples_dir.join( "serde_integration.rs" ), serde_example )?;
341 println!( " ā
serde integration example created" );
342
343 let tracing_example = r#"// tracing integration with workspace_tools
345use tracing::{info, warn, error};
346use tracing_appender::rolling::{RollingFileAppender, Rotation};
347use workspace_tools::workspace;
348
349fn setup_logging() -> Result<(), Box<dyn std::error::Error>> {
350 let ws = workspace()?;
351 let log_dir = ws.logs_dir();
352 std::fs::create_dir_all(&log_dir)?;
353
354 let file_appender = RollingFileAppender::new(
355 Rotation::DAILY,
356 log_dir,
357 "app.log"
358 );
359
360 // configure tracing subscriber with workspace-aware file output
361 // tracing_subscriber setup would go here...
362
363 info!("logging initialized with workspace: {}", ws.root().display());
364 Ok(())
365}
366"#;
367
368 fs::write( examples_dir.join( "tracing_integration.rs" ), tracing_example )?;
369 println!( " ā
tracing integration example created" );
370
371 Ok( () )
372 }
373
374 fn demonstrate_multi_workspace_composition( &self ) -> Result< (), Box< dyn core::error::Error > >
375 {
376 println!( "\n šļø multi-workspace composition:" );
377
378 let sub_workspaces = vec!
380 [
381 ( "frontend", "web frontend components" ),
382 ( "backend", "api and business logic" ),
383 ( "shared", "shared libraries and utilities" ),
384 ( "tools", "development and deployment tools" ),
385 ];
386
387 for ( workspace_name, description ) in sub_workspaces
388 {
389 let sub_ws_dir = self.workspace.join( "workspaces" ).join( workspace_name );
390 fs::create_dir_all( &sub_ws_dir )?;
391
392 let sub_cargo_dir = sub_ws_dir.join( ".cargo" );
394 fs::create_dir_all( &sub_cargo_dir )?;
395
396 let sub_cargo_config = r#"[env]
397WORKSPACE_PATH = { value = ".", relative = true }
398PARENT_WORKSPACE = { value = "../..", relative = true }
399
400[alias]
401parent-test = "test --manifest-path ../../Cargo.toml"
402"#.to_string();
403
404 fs::write( sub_cargo_dir.join( "config.toml" ), sub_cargo_config )?;
405
406 let composition_manifest = format!( r#"# workspace composition manifest
408name = "{workspace_name}"
409description = "{description}"
410parent_workspace = "../.."
411
412[dependencies.internal]
413shared = {{ path = "../shared" }}
414
415[dependencies.external]
416# external dependencies specific to this workspace
417
418[directories]
419config = "config"
420data = "data"
421logs = "logs"
422src = "src"
423
424[integration]
425parent_config = true
426parent_secrets = true
427isolated_data = true
428"# );
429
430 fs::write( sub_ws_dir.join( "workspace.toml" ), composition_manifest )?;
431
432 for dir in &[ "config", "data", "logs", "src" ]
434 {
435 fs::create_dir_all( sub_ws_dir.join( dir ) )?;
436 }
437
438 println!( " created sub-workspace: {workspace_name} ({description})" );
439 }
440
441 let orchestration_script = r#"#!/bin/bash
443# workspace orchestration script
444set -e
445
446PARENT_WS="$WORKSPACE_PATH"
447echo "orchestrating multi-workspace build..."
448echo "parent workspace: $PARENT_WS"
449
450# build shared components first
451echo "building shared workspace..."
452cd workspaces/shared
453export WORKSPACE_PATH="$(pwd)"
454cargo build
455
456# build backend
457echo "building backend workspace..."
458cd ../backend
459export WORKSPACE_PATH="$(pwd)"
460cargo build
461
462# build frontend
463echo "building frontend workspace..."
464cd ../frontend
465export WORKSPACE_PATH="$(pwd)"
466cargo build
467
468# build tools
469echo "building tools workspace..."
470cd ../tools
471export WORKSPACE_PATH="$(pwd)"
472cargo build
473
474echo "multi-workspace build completed!"
475"#;
476
477 let scripts_dir = self.workspace.join( "scripts" );
478 fs::create_dir_all( &scripts_dir )?;
479 fs::write( scripts_dir.join( "build-all.sh" ), orchestration_script )?;
480 println!( " ā
orchestration script created" );
481
482 Ok( () )
483 }
484
485 fn cleanup( &self ) -> Result< (), Box< dyn core::error::Error > >
486 {
487 println!( "\n3ļøā£ cleaning up advanced demo..." );
488
489 let cleanup_dirs = vec!
490 [
491 "templates", "workspaces", "scripts", "integration_examples",
492 "tools", "bin", "target", ".cargo"
493 ];
494
495 for dir_name in cleanup_dirs
496 {
497 let dir_path = self.workspace.join( dir_name );
498 if dir_path.exists()
499 {
500 fs::remove_dir_all( &dir_path )?;
501 println!( " removed: {}", dir_path.display() );
502 }
503 }
504
505 let cleanup_files = vec![ "justfile" ];
506 for file_name in cleanup_files
507 {
508 let file_path = self.workspace.join( file_name );
509 if file_path.exists()
510 {
511 fs::remove_file( &file_path )?;
512 println!( " removed: {}", file_path.display() );
513 }
514 }
515
516 let config_cleanup = vec![ "environments", "features" ];
518 for dir_name in config_cleanup
519 {
520 let dir_path = self.workspace.config_dir().join( dir_name );
521 if dir_path.exists()
522 {
523 fs::remove_dir_all( &dir_path )?;
524 println!( " removed: {}", dir_path.display() );
525 }
526 }
527
528 println!( " ā
cleanup completed" );
529
530 Ok( () )
531 }
532
533 fn create_plugins() -> Vec< Box< dyn WorkspacePlugin > >
536 {
537 vec!
538 [
539 Box::new( ConfigValidatorPlugin::new() ),
540 Box::new( AssetOptimizerPlugin::new() ),
541 Box::new( SecurityScannerPlugin::new() ),
542 Box::new( DocumentationGeneratorPlugin::new() ),
543 ]
544 }
545
546 fn create_environments() -> HashMap< String, EnvironmentConfig >
547 {
548 let mut environments = HashMap::new();
549
550 let mut dev_vars = HashMap::new();
552 dev_vars.insert( "LOG_LEVEL".to_string(), "debug".to_string() );
553 dev_vars.insert( "DEBUG".to_string(), "true".to_string() );
554
555 let mut dev_paths = HashMap::new();
556 dev_paths.insert( "temp".to_string(), "data/dev_temp".to_string() );
557 dev_paths.insert( "cache".to_string(), "data/dev_cache".to_string() );
558
559 environments.insert( "development".to_string(), EnvironmentConfig
560 {
561 name : "development".to_string(),
562 variables : dev_vars,
563 paths : dev_paths,
564 features : vec![ "hot_reload".to_string(), "debug_ui".to_string() ],
565 } );
566
567 let mut prod_vars = HashMap::new();
569 prod_vars.insert( "LOG_LEVEL".to_string(), "info".to_string() );
570 prod_vars.insert( "DEBUG".to_string(), "false".to_string() );
571
572 let mut prod_paths = HashMap::new();
573 prod_paths.insert( "temp".to_string(), "data/temp".to_string() );
574 prod_paths.insert( "cache".to_string(), "data/cache".to_string() );
575
576 environments.insert( "production".to_string(), EnvironmentConfig
577 {
578 name : "production".to_string(),
579 variables : prod_vars,
580 paths : prod_paths,
581 features : vec![ "metrics".to_string(), "monitoring".to_string() ],
582 } );
583
584 environments
585 }
586
587 fn setup_advanced_structure( ws : &Workspace ) -> Result< (), Box< dyn core::error::Error > >
588 {
589 let advanced_dirs = vec!
590 [
591 "plugins", "templates", "environments", "scripts", "integration_examples",
592 "config/environments", "config/features", "config/plugins",
593 "data/plugins", "logs/plugins",
594 ];
595
596 for dir in advanced_dirs
597 {
598 let dir_path = ws.join( dir );
599 fs::create_dir_all( dir_path )?;
600 }
601
602 Ok( () )
603 }
604
605 fn create_cli_template() -> WorkspaceTemplate
606 {
607 WorkspaceTemplate
608 {
609 description : "command-line interface application".to_string(),
610 directories : vec!
611 [
612 "src".to_string(), "tests".to_string(), "config".to_string(),
613 "data".to_string(), "logs".to_string(), "docs".to_string()
614 ],
615 files : vec!
616 [
617 ( "src/main.rs".to_string(), "// cli application main".to_string() ),
618 ( "src/cli.rs".to_string(), "// command line interface".to_string() ),
619 ( "config/app.toml".to_string(), "# cli configuration".to_string() ),
620 ( "Cargo.toml".to_string(), "# cargo manifest".to_string() ),
621 ],
622 }
623 }
624
625 fn create_web_template() -> WorkspaceTemplate
626 {
627 WorkspaceTemplate
628 {
629 description : "web service application".to_string(),
630 directories : vec!
631 [
632 "src".to_string(), "templates".to_string(), "static".to_string(),
633 "uploads".to_string(), "config".to_string(), "data".to_string()
634 ],
635 files : vec!
636 [
637 ( "src/main.rs".to_string(), "// web service main".to_string() ),
638 ( "src/handlers.rs".to_string(), "// request handlers".to_string() ),
639 ( "templates/base.html".to_string(), "<!-- base template -->".to_string() ),
640 ( "static/css/main.css".to_string(), "/* main styles */".to_string() ),
641 ],
642 }
643 }
644
645 fn create_pipeline_template() -> WorkspaceTemplate
646 {
647 WorkspaceTemplate
648 {
649 description : "data processing pipeline".to_string(),
650 directories : vec!
651 [
652 "src".to_string(), "pipelines".to_string(), "data/input".to_string(),
653 "data/output".to_string(), "data/temp".to_string(), "config".to_string()
654 ],
655 files : vec!
656 [
657 ( "src/main.rs".to_string(), "// pipeline runner".to_string() ),
658 ( "src/processors.rs".to_string(), "// data processors".to_string() ),
659 ( "pipelines/etl.toml".to_string(), "# etl pipeline config".to_string() ),
660 ],
661 }
662 }
663
664 fn create_desktop_template() -> WorkspaceTemplate
665 {
666 WorkspaceTemplate
667 {
668 description : "desktop gui application".to_string(),
669 directories : vec!
670 [
671 "src".to_string(), "assets".to_string(), "resources".to_string(),
672 "config".to_string(), "data".to_string(), "plugins".to_string()
673 ],
674 files : vec!
675 [
676 ( "src/main.rs".to_string(), "// desktop app main".to_string() ),
677 ( "src/ui.rs".to_string(), "// user interface".to_string() ),
678 ( "assets/icon.png".to_string(), "// app icon data".to_string() ),
679 ],
680 }
681 }
682}
683
684struct WorkspaceTemplate
685{
686 description : String,
687 directories : Vec< String >,
688 files : Vec< ( String, String ) >,
689}
690
691struct ConfigValidatorPlugin
694{
695 initialized : bool,
696}
697
698impl ConfigValidatorPlugin
699{
700 fn new() -> Self
701 {
702 Self { initialized : false }
703 }
704}
705
706impl WorkspacePlugin for ConfigValidatorPlugin
707{
708 fn name( &self ) -> &'static str { "config-validator" }
709
710 fn initialize( &mut self, _workspace : &Workspace ) -> Result< (), Box< dyn core::error::Error > >
711 {
712 self.initialized = true;
713 Ok( () )
714 }
715
716 fn process( &self, workspace : &Workspace ) -> Result< PluginResult, Box< dyn core::error::Error > >
717 {
718 let config_dir = workspace.config_dir();
719 let config_count = if config_dir.exists()
720 {
721 fs::read_dir( &config_dir )?.count()
722 }
723 else { 0 };
724
725 let mut data = HashMap::new();
726 data.insert( "config_files".to_string(), config_count.to_string() );
727 data.insert( "config_dir".to_string(), config_dir.display().to_string() );
728
729 Ok( PluginResult
730 {
731 success : config_count > 0,
732 message : format!( "found {config_count} config files" ),
733 data,
734 } )
735 }
736}
737
738struct AssetOptimizerPlugin;
739impl AssetOptimizerPlugin { fn new() -> Self { Self } }
740impl WorkspacePlugin for AssetOptimizerPlugin
741{
742 fn name( &self ) -> &'static str { "asset-optimizer" }
743 fn initialize( &mut self, _workspace : &Workspace ) -> Result< (), Box< dyn core::error::Error > > { Ok( () ) }
744 fn process( &self, workspace : &Workspace ) -> Result< PluginResult, Box< dyn core::error::Error > >
745 {
746 let static_dir = workspace.join( "static" );
747 let asset_count = if static_dir.exists() { fs::read_dir( static_dir )?.count() } else { 0 };
748
749 let mut data = HashMap::new();
750 data.insert( "assets_found".to_string(), asset_count.to_string() );
751
752 Ok( PluginResult
753 {
754 success : true,
755 message : format!( "optimized {asset_count} assets" ),
756 data,
757 } )
758 }
759}
760
761struct SecurityScannerPlugin;
762impl SecurityScannerPlugin { fn new() -> Self { Self } }
763impl WorkspacePlugin for SecurityScannerPlugin
764{
765 fn name( &self ) -> &'static str { "security-scanner" }
766 fn initialize( &mut self, _workspace : &Workspace ) -> Result< (), Box< dyn core::error::Error > > { Ok( () ) }
767 fn process( &self, workspace : &Workspace ) -> Result< PluginResult, Box< dyn core::error::Error > >
768 {
769 let mut issues = 0;
770 let mut data = HashMap::new();
771
772 #[ cfg( feature = "secrets" ) ]
774 {
775 let secret_dir = workspace.secret_dir();
776 if secret_dir.exists()
777 {
778 data.insert( "secret_dir_secure".to_string(), "true".to_string() );
780 }
781 else
782 {
783 issues += 1;
784 data.insert( "secret_dir_missing".to_string(), "true".to_string() );
785 }
786 }
787
788 data.insert( "security_issues".to_string(), issues.to_string() );
789
790 Ok( PluginResult
791 {
792 success : issues == 0,
793 message : format!( "security scan: {issues} issues found" ),
794 data,
795 } )
796 }
797}
798
799struct DocumentationGeneratorPlugin;
800impl DocumentationGeneratorPlugin { fn new() -> Self { Self } }
801impl WorkspacePlugin for DocumentationGeneratorPlugin
802{
803 fn name( &self ) -> &'static str { "doc-generator" }
804 fn initialize( &mut self, _workspace : &Workspace ) -> Result< (), Box< dyn core::error::Error > > { Ok( () ) }
805 fn process( &self, workspace : &Workspace ) -> Result< PluginResult, Box< dyn core::error::Error > >
806 {
807 let docs_dir = workspace.docs_dir();
808 fs::create_dir_all( &docs_dir )?;
809
810 let workspace_doc = format!( r"# workspace documentation
812
813generated by workspace_tools documentation plugin
814
815## workspace information
816- root: {}
817- config: {}
818- data: {}
819- logs: {}
820
821## structure
822this workspace follows the standard workspace_tools layout for consistent development.
823",
824 workspace.root().display(),
825 workspace.config_dir().display(),
826 workspace.data_dir().display(),
827 workspace.logs_dir().display()
828 );
829
830 fs::write( docs_dir.join( "workspace.md" ), workspace_doc )?;
831
832 let mut data = HashMap::new();
833 data.insert( "docs_generated".to_string(), "1".to_string() );
834 data.insert( "docs_path".to_string(), docs_dir.display().to_string() );
835
836 Ok( PluginResult
837 {
838 success : true,
839 message : "generated workspace documentation".to_string(),
840 data,
841 } )
842 }
843}