composerize_np/
lib.rs

1pub mod mappings;
2pub mod parser;
3
4use indexmap::IndexMap;
5use serde_yaml::Value;
6use std::fs;
7use std::path::Path;
8
9pub fn composerize(
10    input: &str,
11    _existing_compose: &str,
12    format: &str,
13    _indent: usize,
14) -> Result<String, String> {
15    let (image, command, args) = parser::parse_docker_command(input)?;
16    
17    let network = args.get("network")
18        .or_else(|| args.get("net"))
19        .and_then(|v| v.first())
20        .map(|s| s.as_str())
21        .unwrap_or("default");
22    
23    let mut service_value = parser::build_compose_value(&args, network)?;
24    
25    // Add image
26    if let Value::Mapping(ref mut map) = service_value {
27        map.insert(
28            Value::String("image".to_string()),
29            Value::String(image.clone())
30        );
31        
32        // Add command if present
33        if !command.is_empty() {
34            map.insert(
35                Value::String("command".to_string()),
36                Value::String(command.join(" "))
37            );
38        }
39    }
40    
41    let service_name = get_service_name(&image);
42    
43    let mut services = IndexMap::new();
44    services.insert(service_name, service_value);
45    
46    let version = match format {
47        "v2x" => Some("2".to_string()),
48        "v3x" => Some("3".to_string()),
49        "latest" => None,
50        _ => return Err(format!("Unknown format: {}", format)),
51    };
52    
53    let mut compose = IndexMap::new();
54    
55    if let Some(v) = version {
56        compose.insert(
57            Value::String("version".to_string()),
58            Value::String(v)
59        );
60    }
61    
62    compose.insert(
63        Value::String("services".to_string()),
64        Value::Mapping(services.into_iter()
65            .map(|(k, v)| (Value::String(k), v))
66            .collect())
67    );
68    
69    // Collect used networks and volumes
70    let (networks, volumes) = collect_resources(&args);
71    
72    // Add networks section if present
73    if !networks.is_empty() {
74        let mut networks_map = IndexMap::new();
75        for net in networks {
76            if net != "default" && net != "bridge" && net != "host" && net != "none" {
77                let mut net_config = IndexMap::new();
78                net_config.insert(
79                    Value::String("external".to_string()),
80                    Value::Bool(true)
81                );
82                networks_map.insert(net.to_string(), Value::Mapping(net_config.into_iter().collect()));
83            }
84        }
85        if !networks_map.is_empty() {
86            compose.insert(
87                Value::String("networks".to_string()),
88                Value::Mapping(networks_map.into_iter()
89                    .map(|(k, v)| (Value::String(k), v))
90                    .collect())
91            );
92        }
93    }
94    
95    // Add volumes section if there are named volumes
96    if !volumes.is_empty() {
97        let mut volumes_map = IndexMap::new();
98        for vol in volumes {
99            volumes_map.insert(vol.to_string(), Value::Null);
100        }
101        compose.insert(
102            Value::String("volumes".to_string()),
103            Value::Mapping(volumes_map.into_iter()
104                .map(|(k, v)| (Value::String(k), v))
105                .collect())
106        );
107    }
108    
109    let compose_value = Value::Mapping(compose.into_iter().collect());
110    
111    serde_yaml::to_string(&compose_value)
112        .map_err(|e| format!("Failed to serialize: {}", e))
113}
114
115/// Collects used networks and named volumes from arguments
116fn collect_resources(args: &IndexMap<String, Vec<String>>) -> (Vec<String>, Vec<String>) {
117    let mut networks = Vec::new();
118    let mut volumes = Vec::new();
119    
120    // Collect networks
121    if let Some(nets) = args.get("network").or_else(|| args.get("net")) {
122        for net in nets {
123            if !networks.contains(net) {
124                networks.push(net.clone());
125            }
126        }
127    }
128    
129    // Collect named volumes (not bind mounts)
130    if let Some(vols) = args.get("volume").or_else(|| args.get("v")) {
131        for vol in vols {
132            // Named volume if it doesn't start with / or . or ~
133            if !vol.starts_with('/') && !vol.starts_with('.') && !vol.starts_with('~') {
134                if let Some(vol_name) = vol.split(':').next() {
135                    if !volumes.contains(&vol_name.to_string()) {
136                        volumes.push(vol_name.to_string());
137                    }
138                }
139            }
140        }
141    }
142    
143    (networks, volumes)
144}
145
146pub fn get_service_name(image: &str) -> String {
147    let name = if image.contains('/') {
148        image.split('/').last().unwrap_or(image)
149    } else {
150        image
151    };
152    
153    let name = if name.contains(':') {
154        name.split(':').next().unwrap_or(name)
155    } else {
156        name
157    };
158    
159    name.to_string()
160}
161
162/// Converts docker run command to JSON
163pub fn composerize_to_json(
164    input: &str,
165    _existing_compose: &str,
166    format: &str,
167    indent: usize,
168) -> Result<String, String> {
169    let (image, command, args) = parser::parse_docker_command(input)?;
170
171    let network = args
172        .get("network")
173        .or_else(|| args.get("net"))
174        .and_then(|v| v.first())
175        .map(|s| s.as_str())
176        .unwrap_or("default");
177
178    let mut service_value = parser::build_compose_value(&args, network)?;
179
180    if let Value::Mapping(ref mut map) = service_value {
181        map.insert(
182            Value::String("image".to_string()),
183            Value::String(image.clone()),
184        );
185
186        if !command.is_empty() {
187            map.insert(
188                Value::String("command".to_string()),
189                Value::String(command.join(" ")),
190            );
191        }
192    }
193
194    let service_name = get_service_name(&image);
195
196    let mut services = IndexMap::new();
197    services.insert(service_name, service_value);
198
199    let version = match format {
200        "v2x" => Some("2".to_string()),
201        "v3x" => Some("3".to_string()),
202        "latest" => None,
203        _ => return Err(format!("Unknown format: {}", format)),
204    };
205
206    let mut compose = IndexMap::new();
207
208    if let Some(v) = version {
209        compose.insert(Value::String("version".to_string()), Value::String(v));
210    }
211
212    compose.insert(
213        Value::String("services".to_string()),
214        Value::Mapping(
215            services
216                .into_iter()
217                .map(|(k, v)| (Value::String(k), v))
218                .collect(),
219        ),
220    );
221
222    // Collect used networks and volumes
223    let (networks, volumes) = collect_resources(&args);
224    
225    // Add networks section if present
226    if !networks.is_empty() {
227        let mut networks_map = IndexMap::new();
228        for net in networks {
229            if net != "default" && net != "bridge" && net != "host" && net != "none" {
230                let mut net_config = IndexMap::new();
231                net_config.insert(
232                    Value::String("external".to_string()),
233                    Value::Bool(true)
234                );
235                networks_map.insert(net.to_string(), Value::Mapping(net_config.into_iter().collect()));
236            }
237        }
238        if !networks_map.is_empty() {
239            compose.insert(
240                Value::String("networks".to_string()),
241                Value::Mapping(networks_map.into_iter()
242                    .map(|(k, v)| (Value::String(k), v))
243                    .collect())
244            );
245        }
246    }
247    
248    // Add volumes section if there are named volumes
249    if !volumes.is_empty() {
250        let mut volumes_map = IndexMap::new();
251        for vol in volumes {
252            volumes_map.insert(vol.to_string(), Value::Null);
253        }
254        compose.insert(
255            Value::String("volumes".to_string()),
256            Value::Mapping(volumes_map.into_iter()
257                .map(|(k, v)| (Value::String(k), v))
258                .collect())
259        );
260    }
261
262    let compose_value = Value::Mapping(compose.into_iter().collect());
263
264    // Convert to JSON
265    let json_value: serde_json::Value = serde_yaml::from_value(compose_value)
266        .map_err(|e| format!("Failed to convert to JSON: {}", e))?;
267
268    if indent > 0 {
269        serde_json::to_string_pretty(&json_value)
270            .map_err(|e| format!("Failed to serialize JSON: {}", e))
271    } else {
272        serde_json::to_string(&json_value)
273            .map_err(|e| format!("Failed to serialize JSON: {}", e))
274    }
275}
276
277/// Converts YAML to JSON
278pub fn yaml_to_json(yaml_content: &str, pretty: bool) -> Result<String, String> {
279    let yaml_value: serde_yaml::Value = serde_yaml::from_str(yaml_content)
280        .map_err(|e| format!("Failed to parse YAML: {}", e))?;
281
282    let json_value: serde_json::Value = serde_yaml::from_value(yaml_value)
283        .map_err(|e| format!("Failed to convert to JSON: {}", e))?;
284
285    if pretty {
286        serde_json::to_string_pretty(&json_value)
287            .map_err(|e| format!("Failed to serialize JSON: {}", e))
288    } else {
289        serde_json::to_string(&json_value)
290            .map_err(|e| format!("Failed to serialize JSON: {}", e))
291    }
292}
293
294/// Converts JSON to YAML
295pub fn json_to_yaml(json_content: &str) -> Result<String, String> {
296    let json_value: serde_json::Value = serde_json::from_str(json_content)
297        .map_err(|e| format!("Failed to parse JSON: {}", e))?;
298
299    let yaml_value: serde_yaml::Value = serde_json::from_value(json_value)
300        .map_err(|e| format!("Failed to convert to YAML: {}", e))?;
301
302    serde_yaml::to_string(&yaml_value).map_err(|e| format!("Failed to serialize YAML: {}", e))
303}
304
305/// Converts file from one format to another
306pub fn convert_file(
307    input_path: &Path,
308    output_path: &Path,
309    output_format: &str,
310) -> Result<(), String> {
311    let content =
312        fs::read_to_string(input_path).map_err(|e| format!("Failed to read file: {}", e))?;
313
314    let input_ext = input_path
315        .extension()
316        .and_then(|s| s.to_str())
317        .unwrap_or("");
318
319    let result = match (input_ext, output_format) {
320        ("yml" | "yaml", "json") => yaml_to_json(&content, true)?,
321        ("json", "yml" | "yaml") => json_to_yaml(&content)?,
322        _ => {
323            return Err(format!(
324                "Unsupported conversion: {} to {}",
325                input_ext, output_format
326            ))
327        }
328    };
329
330    fs::write(output_path, result).map_err(|e| format!("Failed to write file: {}", e))?;
331
332    Ok(())
333}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338
339    #[test]
340    fn test_simple_nginx() {
341        let result = composerize("docker run nginx", "", "latest", 2);
342        assert!(result.is_ok());
343        let yaml = result.unwrap();
344        assert!(yaml.contains("nginx"));
345        assert!(yaml.contains("image: nginx"));
346    }
347
348    #[test]
349    fn test_with_ports() {
350        let result = composerize("docker run -p 80:80 nginx", "", "latest", 2);
351        assert!(result.is_ok());
352        let yaml = result.unwrap();
353        assert!(yaml.contains("ports:"));
354        assert!(yaml.contains("80:80"));
355    }
356
357    #[test]
358    fn test_with_environment() {
359        let result = composerize("docker run -e NODE_ENV=production nginx", "", "latest", 2);
360        assert!(result.is_ok());
361        let yaml = result.unwrap();
362        assert!(yaml.contains("environment:"));
363        assert!(yaml.contains("NODE_ENV=production"));
364    }
365
366    #[test]
367    fn test_with_volumes() {
368        let result = composerize("docker run -v /data:/app nginx", "", "latest", 2);
369        assert!(result.is_ok());
370        let yaml = result.unwrap();
371        assert!(yaml.contains("volumes:"));
372        assert!(yaml.contains("/data:/app"));
373    }
374
375    #[test]
376    fn test_with_name() {
377        let result = composerize("docker run --name my-app nginx", "", "latest", 2);
378        assert!(result.is_ok());
379        let yaml = result.unwrap();
380        assert!(yaml.contains("container_name: my-app"));
381    }
382
383    #[test]
384    fn test_with_restart() {
385        let result = composerize("docker run --restart always nginx", "", "latest", 2);
386        assert!(result.is_ok());
387        let yaml = result.unwrap();
388        assert!(yaml.contains("restart: always"));
389    }
390
391    #[test]
392    fn test_privileged() {
393        let result = composerize("docker run --privileged nginx", "", "latest", 2);
394        if let Err(e) = &result {
395            eprintln!("Error: {}", e);
396        }
397        assert!(result.is_ok());
398        let yaml = result.unwrap();
399        assert!(yaml.contains("privileged: true"));
400    }
401
402    #[test]
403    fn test_interactive_tty() {
404        let result = composerize("docker run -it ubuntu bash", "", "latest", 2);
405        assert!(result.is_ok());
406        let yaml = result.unwrap();
407        assert!(yaml.contains("stdin_open: true"));
408        assert!(yaml.contains("tty: true"));
409        assert!(yaml.contains("command: bash"));
410    }
411
412    #[test]
413    fn test_memory_limit() {
414        let result = composerize("docker run --memory 512m nginx", "", "latest", 2);
415        assert!(result.is_ok());
416        let yaml = result.unwrap();
417        assert!(yaml.contains("memory: 512m"));
418    }
419
420    #[test]
421    fn test_cpu_limit() {
422        let result = composerize("docker run --cpus 2.5 nginx", "", "latest", 2);
423        assert!(result.is_ok());
424        let yaml = result.unwrap();
425        assert!(yaml.contains("cpus: 2.5"));
426    }
427
428    #[test]
429    fn test_multiple_ports() {
430        let result = composerize("docker run -p 80:80 -p 443:443 nginx", "", "latest", 2);
431        assert!(result.is_ok());
432        let yaml = result.unwrap();
433        assert!(yaml.contains("80:80"));
434        assert!(yaml.contains("443:443"));
435    }
436
437    #[test]
438    fn test_multiple_env_vars() {
439        let result = composerize("docker run -e VAR1=value1 -e VAR2=value2 nginx", "", "latest", 2);
440        assert!(result.is_ok());
441        let yaml = result.unwrap();
442        assert!(yaml.contains("VAR1=value1"));
443        assert!(yaml.contains("VAR2=value2"));
444    }
445
446    #[test]
447    fn test_complex_command() {
448        let result = composerize(
449            "docker run -d -p 8080:80 --name web -e NODE_ENV=production --restart always nginx:alpine",
450            "",
451            "latest",
452            2
453        );
454        assert!(result.is_ok());
455        let yaml = result.unwrap();
456        assert!(yaml.contains("8080:80"));
457        assert!(yaml.contains("container_name: web"));
458        assert!(yaml.contains("NODE_ENV=production"));
459        assert!(yaml.contains("restart: always"));
460        assert!(yaml.contains("image: nginx:alpine"));
461    }
462
463    #[test]
464    fn test_version_v2x() {
465        let result = composerize("docker run nginx", "", "v2x", 2);
466        assert!(result.is_ok());
467        let yaml = result.unwrap();
468        assert!(yaml.contains("version: '2'") || yaml.contains("version: \"2\""));
469    }
470
471    #[test]
472    fn test_version_v3x() {
473        let result = composerize("docker run nginx", "", "v3x", 2);
474        assert!(result.is_ok());
475        let yaml = result.unwrap();
476        assert!(yaml.contains("version: '3'") || yaml.contains("version: \"3\""));
477    }
478
479    #[test]
480    fn test_version_latest_no_version() {
481        let result = composerize("docker run nginx", "", "latest", 2);
482        assert!(result.is_ok());
483        let yaml = result.unwrap();
484        assert!(!yaml.contains("version:"));
485    }
486
487    #[test]
488    fn test_get_service_name_simple() {
489        assert_eq!(get_service_name("nginx"), "nginx");
490    }
491
492    #[test]
493    fn test_get_service_name_with_tag() {
494        assert_eq!(get_service_name("nginx:alpine"), "nginx");
495    }
496
497    #[test]
498    fn test_get_service_name_with_registry() {
499        assert_eq!(get_service_name("docker.io/library/nginx"), "nginx");
500    }
501
502    #[test]
503    fn test_get_service_name_with_registry_and_tag() {
504        assert_eq!(get_service_name("docker.io/library/nginx:1.21"), "nginx");
505    }
506
507    #[test]
508    fn test_healthcheck() {
509        let result = composerize(
510            "docker run --health-cmd 'curl -f http://localhost' --health-interval 30s nginx",
511            "",
512            "latest",
513            2
514        );
515        assert!(result.is_ok());
516        let yaml = result.unwrap();
517        assert!(yaml.contains("healthcheck:"));
518        assert!(yaml.contains("test:"));
519        assert!(yaml.contains("interval: 30s"));
520    }
521
522    #[test]
523    fn test_labels() {
524        let result = composerize("docker run -l app=web -l env=prod nginx", "", "latest", 2);
525        assert!(result.is_ok());
526        let yaml = result.unwrap();
527        assert!(yaml.contains("labels:"));
528        assert!(yaml.contains("app=web"));
529        assert!(yaml.contains("env=prod"));
530    }
531
532    #[test]
533    fn test_hostname() {
534        let result = composerize("docker run --hostname myhost nginx", "", "latest", 2);
535        assert!(result.is_ok());
536        let yaml = result.unwrap();
537        assert!(yaml.contains("hostname: myhost"));
538    }
539
540    #[test]
541    fn test_user() {
542        let result = composerize("docker run --user 1000:1000 nginx", "", "latest", 2);
543        assert!(result.is_ok());
544        let yaml = result.unwrap();
545        assert!(yaml.contains("user: 1000:1000"));
546    }
547
548    #[test]
549    fn test_workdir() {
550        let result = composerize("docker run --workdir /app nginx", "", "latest", 2);
551        assert!(result.is_ok());
552        let yaml = result.unwrap();
553        assert!(yaml.contains("working_dir: /app"));
554    }
555
556    #[test]
557    fn test_entrypoint() {
558        let result = composerize("docker run --entrypoint /bin/sh nginx", "", "latest", 2);
559        assert!(result.is_ok());
560        let yaml = result.unwrap();
561        assert!(yaml.contains("entrypoint:"));
562        assert!(yaml.contains("/bin/sh"));
563    }
564
565    #[test]
566    fn test_cap_add() {
567        let result = composerize("docker run --cap-add NET_ADMIN nginx", "", "latest", 2);
568        assert!(result.is_ok());
569        let yaml = result.unwrap();
570        assert!(yaml.contains("cap_add:"));
571        assert!(yaml.contains("NET_ADMIN"));
572    }
573
574    #[test]
575    fn test_dns() {
576        let result = composerize("docker run --dns 8.8.8.8 nginx", "", "latest", 2);
577        assert!(result.is_ok());
578        let yaml = result.unwrap();
579        assert!(yaml.contains("dns:"));
580        assert!(yaml.contains("8.8.8.8"));
581    }
582
583    #[test]
584    fn test_no_image_error() {
585        let result = composerize("docker run -d", "", "latest", 2);
586        assert!(result.is_err());
587        assert!(result.unwrap_err().contains("No image specified"));
588    }
589
590    #[test]
591    fn test_invalid_format() {
592        let result = composerize("docker run nginx", "", "invalid", 2);
593        assert!(result.is_err());
594        assert!(result.unwrap_err().contains("Unknown format"));
595    }
596
597    #[test]
598    fn test_composerize_to_json() {
599        let result = composerize_to_json("docker run -p 80:80 nginx", "", "latest", 2);
600        assert!(result.is_ok());
601        let json = result.unwrap();
602        assert!(json.contains("\"nginx\""));
603        assert!(json.contains("\"80:80\""));
604        assert!(json.contains("\"services\""));
605    }
606
607    #[test]
608    fn test_yaml_to_json() {
609        let yaml = r#"
610services:
611  nginx:
612    image: nginx
613    ports:
614      - 80:80
615"#;
616        let result = yaml_to_json(yaml, true);
617        assert!(result.is_ok());
618        let json = result.unwrap();
619        assert!(json.contains("\"nginx\""));
620        assert!(json.contains("\"80:80\""));
621    }
622
623    #[test]
624    fn test_json_to_yaml() {
625        let json = r#"{
626  "services": {
627    "nginx": {
628      "image": "nginx",
629      "ports": ["80:80"]
630    }
631  }
632}"#;
633        let result = json_to_yaml(json);
634        assert!(result.is_ok());
635        let yaml = result.unwrap();
636        assert!(yaml.contains("nginx"));
637        assert!(yaml.contains("80:80"));
638    }
639
640    #[test]
641    fn test_yaml_to_json_to_yaml() {
642        let original_yaml = r#"
643services:
644  nginx:
645    image: nginx
646    ports:
647      - 80:80
648"#;
649        let json = yaml_to_json(original_yaml, false).unwrap();
650        let yaml = json_to_yaml(&json).unwrap();
651        assert!(yaml.contains("nginx"));
652        assert!(yaml.contains("80:80"));
653    }
654
655    #[test]
656    fn test_json_with_complex_structure() {
657        let result = composerize_to_json(
658            "docker run -d -p 8080:80 -e NODE_ENV=production --restart always nginx",
659            "",
660            "v3x",
661            2,
662        );
663        assert!(result.is_ok());
664        let json = result.unwrap();
665        assert!(json.contains("\"version\""));
666        assert!(json.contains("\"3\""));
667        assert!(json.contains("\"NODE_ENV=production\""));
668        assert!(json.contains("\"restart\""));
669        assert!(json.contains("\"always\""));
670    }
671
672    #[test]
673    fn test_networks_section() {
674        let result = composerize("docker run --network ml-net nginx", "", "latest", 2);
675        assert!(result.is_ok());
676        let yaml = result.unwrap();
677        assert!(yaml.contains("networks:"));
678        assert!(yaml.contains("ml-net:"));
679        assert!(yaml.contains("external: true"));
680    }
681
682    #[test]
683    fn test_volumes_section() {
684        let result = composerize("docker run -v data:/data -v cache:/cache nginx", "", "latest", 2);
685        assert!(result.is_ok());
686        let yaml = result.unwrap();
687        assert!(yaml.contains("volumes:"));
688        assert!(yaml.contains("data:"));
689        assert!(yaml.contains("cache:"));
690    }
691
692    #[test]
693    fn test_no_volumes_for_bind_mounts() {
694        let result = composerize("docker run -v /host:/container nginx", "", "latest", 2);
695        assert!(result.is_ok());
696        let yaml = result.unwrap();
697        // Should not have volumes section for bind mounts
698        let lines: Vec<&str> = yaml.lines().collect();
699        let volumes_line = lines.iter().position(|&l| l.starts_with("volumes:"));
700        assert!(volumes_line.is_none());
701    }
702
703    #[test]
704    fn test_mixed_volumes() {
705        let result = composerize("docker run -v data:/data -v /host:/host nginx", "", "latest", 2);
706        assert!(result.is_ok());
707        let yaml = result.unwrap();
708        // Should have volumes section only for named volume
709        assert!(yaml.contains("volumes:"));
710        assert!(yaml.contains("data:"));
711        // But service volumes should have both
712        assert!(yaml.contains("- data:/data"));
713        assert!(yaml.contains("- /host:/host"));
714    }
715
716    #[test]
717    fn test_default_network_not_in_section() {
718        let result = composerize("docker run nginx", "", "latest", 2);
719        assert!(result.is_ok());
720        let yaml = result.unwrap();
721        // Should not have networks section for default network
722        let lines: Vec<&str> = yaml.lines().collect();
723        let networks_line = lines.iter().position(|&l| l.starts_with("networks:"));
724        assert!(networks_line.is_none());
725    }
726
727    #[test]
728    fn test_full_compose_with_resources() {
729        let result = composerize(
730            "docker run -d --name ml-service --network ml-net -v ml-models:/models -v ml-cache:/cache nginx",
731            "",
732            "latest",
733            2
734        );
735        assert!(result.is_ok());
736        let yaml = result.unwrap();
737        // Check all sections
738        assert!(yaml.contains("services:"));
739        assert!(yaml.contains("networks:"));
740        assert!(yaml.contains("volumes:"));
741        assert!(yaml.contains("ml-net:"));
742        assert!(yaml.contains("ml-models:"));
743        assert!(yaml.contains("ml-cache:"));
744    }
745
746    #[test]
747    fn test_mount_to_volume_conversion() {
748        let result = composerize(
749            "docker run --mount=type=bind,source=/host/data,target=/container/data,readonly nginx",
750            "",
751            "latest",
752            2
753        );
754        assert!(result.is_ok());
755        let yaml = result.unwrap();
756        // Check that mount is converted to short syntax
757        assert!(yaml.contains("/host/data:/container/data:ro"));
758        // Should not have raw mount string
759        assert!(!yaml.contains("type=bind"));
760    }
761
762    #[test]
763    fn test_mount_without_readonly() {
764        let result = composerize(
765            "docker run --mount=type=bind,source=/src,target=/dst nginx",
766            "",
767            "latest",
768            2
769        );
770        assert!(result.is_ok());
771        let yaml = result.unwrap();
772        assert!(yaml.contains("/src:/dst"));
773        assert!(!yaml.contains(":ro"));
774    }
775}