1use crate::dependencies::{
2 compiledb::parse_compile_database,
3 cparse::{all_sources_and_includes, extract_includes, SourceWithIncludes},
4 gn::load_gn_targets,
5 graph::GraphBuilder,
6 path_mapper::{PathMapper, PathMapping},
7};
8use color_eyre::Result;
9use color_eyre::{eyre::WrapErr, Report};
10use nom::{
11 branch::alt,
12 bytes::complete::{is_not, tag_no_case},
13 character::complete::{char as parsed_char, multispace1},
14 combinator::{opt, value},
15 multi::{many0, many1, separated_list0},
16 sequence::{pair, separated_pair, tuple},
17 IResult, Parser,
18};
19use nom_supreme::ParserExt;
20
21use std::{
22 collections::{HashMap, HashSet},
23 path::PathBuf,
24};
25
26use tracing::{debug, error, info, warn};
27
28use super::{error::Error, graph::Graph};
29
30#[derive(Debug, PartialEq, Clone)]
32pub enum MapInstruction {
33 DisplayMap { from: String, to: String },
34 Keep(String),
35 Drop(String),
36}
37
38#[derive(Debug, Clone, PartialEq)]
39pub enum GroupInstruction {
40 GroupSourceHeader,
41 GroupFromGn {
42 gn_root: String,
43 target: String,
44 source_root: String,
45 ignore_targets: HashSet<String>,
46 },
47 ManualGroup {
48 name: String,
49 color: Option<String>,
50 items: Vec<String>,
51 },
52}
53
54#[derive(Debug, PartialEq, Clone)]
55pub struct ZoomItem {
56 name: String,
57 focused: bool,
58}
59
60#[derive(Debug, PartialEq, Clone)]
61pub enum GroupEdgeEnd {
62 From(String),
63 To(String),
64}
65
66#[derive(Debug, PartialEq, Clone)]
67pub enum EdgeColor {
68 Regular(String),
69 Bold(String),
70}
71
72impl EdgeColor {
73 pub fn color_name(&self) -> &str {
74 match self {
75 EdgeColor::Regular(c) => c,
76 EdgeColor::Bold(c) => c,
77 }
78 }
79
80 pub fn is_bold(&self) -> bool {
81 match self {
82 EdgeColor::Regular(_) => false,
83 EdgeColor::Bold(_) => true,
84 }
85 }
86}
87
88#[derive(Debug, PartialEq, Clone)]
89pub struct ColorInstruction {
90 end: GroupEdgeEnd,
91 color: EdgeColor,
92}
93
94#[derive(Debug, PartialEq, Clone)]
96enum InputCommand {
97 LoadCompileDb {
98 path: String,
99 load_include_directories: bool,
100 load_sources: bool,
101 },
102 IncludeDirectory(String),
103 Glob(String),
104}
105
106#[derive(Debug, PartialEq, Clone)]
107struct VariableAssignment {
108 name: String,
109 value: String,
110}
111
112#[derive(Debug, PartialEq, Default, Clone)]
113struct GraphInstructions {
114 map_instructions: Vec<MapInstruction>,
115 group_instructions: Vec<GroupInstruction>,
116 color_instructions: Vec<ColorInstruction>,
117 zoom_items: Vec<ZoomItem>,
118}
119
120#[derive(Debug, PartialEq, Clone, Default)]
123struct ConfigurationFile {
124 variable_map: HashMap<String, String>,
126
127 input_commands: Vec<InputCommand>,
129
130 graph: GraphInstructions,
132}
133
134fn expand_variable(value: &str, variable_map: &HashMap<String, String>) -> String {
135 let mut value = value.to_string();
137
138 loop {
139 let replacements = variable_map
140 .iter()
141 .map(|(k, v)| (format!("${{{}}}", k), v))
142 .filter(|(k, _v)| value.contains(k))
143 .collect::<Vec<_>>();
144
145 if replacements.is_empty() {
146 break;
147 }
148
149 for (k, v) in replacements {
150 value = value.replace(&k, v);
151 }
152 }
153
154 value
155}
156
157trait Expanded {
162 fn expanded_from(self, variable_map: &HashMap<String, String>) -> Self;
163}
164
165trait ResolveVariables<O> {
166 fn resolve_variables(self) -> O;
167}
168
169impl ResolveVariables<HashMap<String, String>> for Vec<VariableAssignment> {
170 fn resolve_variables(self) -> HashMap<String, String> {
171 let mut variable_map = HashMap::new();
172 for VariableAssignment { name, value } in self {
173 variable_map.insert(name, value.expanded_from(&variable_map));
174 }
175 variable_map
176 }
177}
178
179impl Expanded for ZoomItem {
180 fn expanded_from(self, variable_map: &HashMap<String, String>) -> Self {
181 Self {
182 name: self.name.expanded_from(variable_map),
183 ..self
184 }
185 }
186}
187
188impl Expanded for GroupEdgeEnd {
189 fn expanded_from(self, variable_map: &HashMap<String, String>) -> Self {
190 match self {
191 GroupEdgeEnd::From(v) => GroupEdgeEnd::From(v.expanded_from(variable_map)),
192 GroupEdgeEnd::To(v) => GroupEdgeEnd::To(v.expanded_from(variable_map)),
193 }
194 }
195}
196
197impl Expanded for EdgeColor {
198 fn expanded_from(self, variable_map: &HashMap<String, String>) -> Self {
199 match self {
200 EdgeColor::Regular(c) => EdgeColor::Regular(c.expanded_from(variable_map)),
201 EdgeColor::Bold(c) => EdgeColor::Bold(c.expanded_from(variable_map)),
202 }
203 }
204}
205
206impl Expanded for ColorInstruction {
207 fn expanded_from(self, variable_map: &HashMap<String, String>) -> Self {
208 Self {
209 end: self.end.expanded_from(variable_map),
210 color: self.color.expanded_from(variable_map),
211 }
212 }
213}
214
215impl Expanded for InputCommand {
216 fn expanded_from(self, variable_map: &HashMap<String, String>) -> Self {
217 match self {
218 InputCommand::LoadCompileDb {
219 path,
220 load_include_directories,
221 load_sources,
222 } => InputCommand::LoadCompileDb {
223 path: path.expanded_from(variable_map),
224 load_include_directories,
225 load_sources,
226 },
227 InputCommand::IncludeDirectory(p) => {
228 InputCommand::IncludeDirectory(p.expanded_from(variable_map))
229 }
230 InputCommand::Glob(p) => InputCommand::Glob(p.expanded_from(variable_map)),
231 }
232 }
233}
234
235#[derive(Debug, Default)]
236struct DependencyData {
237 includes: HashSet<PathBuf>,
238 files: Vec<SourceWithIncludes>,
239}
240
241struct FullFileList<'a> {
245 dependencies: &'a DependencyData,
246}
247
248impl<'a> FullFileList<'a> {
249 pub fn new(dependencies: &'a DependencyData) -> Self {
250 Self { dependencies }
251 }
252}
253
254impl<'a> std::fmt::Display for FullFileList<'a> {
255 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 fmt.write_str("Processed files:\n")?;
257 for f in self.dependencies.files.iter() {
258 fmt.write_fmt(format_args!(" {:?}\n", f.path))?;
259 }
260 Ok(())
261 }
262}
263
264pub fn parse_comment(input: &str) -> IResult<&str, &str> {
277 pair(parsed_char('#'), opt(is_not("\n\r")))
278 .map(|(_, r)| r.unwrap_or_default())
279 .parse(input)
280}
281
282pub fn parse_whitespace(input: &str) -> IResult<&str, ()> {
300 value((), many1(alt((multispace1, parse_comment)))).parse(input)
301}
302
303fn parse_variable_name(input: &str) -> IResult<&str, &str> {
304 is_not("= \t\r\n{}[]()#").parse(input)
305}
306
307pub fn parse_until_whitespace(input: &str) -> IResult<&str, &str> {
322 is_not("#\n\r \t").parse(input)
323}
324
325fn parse_compiledb(input: &str) -> IResult<&str, InputCommand> {
326 #[derive(Clone, Copy, PartialEq)]
327 enum Type {
328 Includes,
329 Sources,
330 }
331 tuple((
332 parse_until_whitespace
333 .preceded_by(tuple((
334 opt(parse_whitespace),
335 tag_no_case("from"),
336 parse_whitespace,
337 tag_no_case("compiledb"),
338 parse_whitespace,
339 )))
340 .terminated(parse_whitespace),
341 separated_list0(
342 tuple((
343 opt(parse_whitespace),
344 tag_no_case(","),
345 opt(parse_whitespace),
346 )),
347 alt((
348 value(Type::Includes, tag_no_case("include_dirs")),
349 value(Type::Sources, tag_no_case("sources")),
350 )),
351 )
352 .preceded_by(tuple((tag_no_case("load"), opt(parse_whitespace))))
353 .terminated(opt(parse_whitespace)),
354 ))
355 .map(|(path, selections)| InputCommand::LoadCompileDb {
356 path: path.into(),
357 load_include_directories: selections.contains(&Type::Includes),
358 load_sources: selections.contains(&Type::Sources),
359 })
360 .parse(input)
361}
362
363fn parse_input_command(input: &str) -> IResult<&str, InputCommand> {
364 alt((
365 parse_compiledb,
366 parse_until_whitespace
367 .preceded_by(tuple((tag_no_case("glob"), parse_whitespace)))
368 .terminated(opt(parse_whitespace))
369 .map(|s| InputCommand::Glob(s.into())),
370 parse_until_whitespace
371 .preceded_by(tuple((tag_no_case("include_dir"), parse_whitespace)))
372 .terminated(opt(parse_whitespace))
373 .map(|s| InputCommand::IncludeDirectory(s.into())),
374 ))
375 .parse(input)
376}
377
378fn parse_input(input: &str) -> IResult<&str, Vec<InputCommand>> {
379 many0(parse_input_command)
380 .preceded_by(tuple((
381 tag_no_case("input"),
382 parse_whitespace,
383 tag_no_case("{"),
384 opt(parse_whitespace),
385 )))
386 .terminated(tuple((tag_no_case("}"), opt(parse_whitespace))))
387 .parse(input)
388}
389
390impl<T> Expanded for Vec<T>
391where
392 T: Expanded,
393{
394 fn expanded_from(self, variable_map: &HashMap<String, String>) -> Self {
395 self.into_iter()
396 .map(|v| v.expanded_from(variable_map))
397 .collect()
398 }
399}
400
401impl Expanded for String {
402 fn expanded_from(self, variable_map: &HashMap<String, String>) -> Self {
403 expand_variable(&self, variable_map)
404 }
405}
406
407impl Expanded for MapInstruction {
408 fn expanded_from(self, variable_map: &HashMap<String, String>) -> Self {
409 match self {
410 MapInstruction::DisplayMap { from, to } => MapInstruction::DisplayMap {
411 from: from.expanded_from(variable_map),
412 to: to.expanded_from(variable_map),
413 },
414 MapInstruction::Keep(v) => MapInstruction::Keep(v.expanded_from(variable_map)),
415 MapInstruction::Drop(v) => MapInstruction::Drop(v.expanded_from(variable_map)),
416 }
417 }
418}
419
420impl Expanded for GroupInstruction {
421 fn expanded_from(self, variable_map: &HashMap<String, String>) -> Self {
422 match self {
423 GroupInstruction::GroupSourceHeader => self,
424 GroupInstruction::GroupFromGn {
425 gn_root,
426 target,
427 source_root,
428 ignore_targets,
429 } => GroupInstruction::GroupFromGn {
430 gn_root: gn_root.expanded_from(variable_map),
431 target,
432 source_root: source_root.expanded_from(variable_map),
433 ignore_targets,
434 },
435 GroupInstruction::ManualGroup { name, color, items } => {
436 GroupInstruction::ManualGroup { name, color, items }
437 }
438 }
439 }
440}
441
442impl Expanded for GraphInstructions {
443 fn expanded_from(self, variable_map: &HashMap<String, String>) -> Self {
444 Self {
445 map_instructions: self.map_instructions.expanded_from(variable_map),
446 group_instructions: self.group_instructions.expanded_from(variable_map),
447 color_instructions: self.color_instructions.expanded_from(variable_map),
448 zoom_items: self.zoom_items.expanded_from(variable_map),
449 }
450 }
451}
452
453fn parse_map_instructions(input: &str) -> IResult<&str, Vec<MapInstruction>> {
454 many0(
455 alt((
456 separated_pair(
457 parse_until_whitespace,
458 tuple((parse_whitespace, tag_no_case("=>"), parse_whitespace)),
459 parse_until_whitespace,
460 )
461 .map(|(from, to)| MapInstruction::DisplayMap {
462 from: from.into(),
463 to: to.into(),
464 }),
465 parse_until_whitespace
466 .preceded_by(tuple((
467 opt(parse_whitespace),
468 tag_no_case("keep"),
469 parse_whitespace,
470 )))
471 .map(|s| MapInstruction::Keep(s.into())),
472 parse_until_whitespace
473 .preceded_by(tuple((
474 opt(parse_whitespace),
475 tag_no_case("drop"),
476 parse_whitespace,
477 )))
478 .map(|s| MapInstruction::Drop(s.into())),
479 ))
480 .terminated(parse_whitespace),
481 )
482 .preceded_by(tuple((
483 tag_no_case("map"),
484 parse_whitespace,
485 tag_no_case("{"),
486 parse_whitespace,
487 )))
488 .terminated(tuple((
489 opt(parse_whitespace),
490 tag_no_case("}"),
491 opt(parse_whitespace),
492 )))
493 .parse(input)
494}
495
496fn parse_gn_target(input: &str) -> IResult<&str, GroupInstruction> {
497 tuple((
498 parse_until_whitespace.preceded_by(tuple((
499 tag_no_case("gn"),
500 parse_whitespace,
501 tag_no_case("root"),
502 parse_whitespace,
503 ))),
504 parse_until_whitespace.preceded_by(tuple((
505 parse_whitespace,
506 tag_no_case("target"),
507 parse_whitespace,
508 ))),
509 parse_until_whitespace.preceded_by(tuple((
510 parse_whitespace,
511 tag_no_case("sources"),
512 parse_whitespace,
513 ))),
514 opt(parse_target_list
515 .preceded_by(tuple((
516 parse_whitespace,
517 tag_no_case("ignore"),
518 parse_whitespace,
519 tag_no_case("targets"),
520 opt(parse_whitespace),
521 tag_no_case("{"),
522 )))
523 .terminated(tuple((
524 opt(parse_whitespace),
525 tag_no_case("}"),
526 opt(parse_whitespace),
527 )))),
528 ))
529 .terminated(opt(parse_whitespace))
530 .map(
531 |(gn_root, target, source_root, ignore_targets)| GroupInstruction::GroupFromGn {
532 gn_root: gn_root.into(),
533 target: target.into(),
534 source_root: source_root.into(),
535 ignore_targets: ignore_targets
536 .map(|v| v.into_iter().map(|s| s.into()).collect())
537 .unwrap_or_default(),
538 },
539 )
540 .parse(input)
541}
542
543fn parse_manual_group(input: &str) -> IResult<&str, GroupInstruction> {
544 tuple((
545 tuple((
546 parse_until_whitespace,
547 opt(parse_until_whitespace.preceded_by(tuple((
548 parse_whitespace,
549 tag_no_case("color"),
550 opt(parse_whitespace),
551 )))),
552 ))
553 .preceded_by(tuple((
554 opt(parse_whitespace),
555 tag_no_case("manual"),
556 opt(parse_whitespace),
557 )))
558 .terminated(tuple((opt(parse_whitespace), tag_no_case("{")))),
559 many0(
560 is_not("\n\r \t#}")
561 .preceded_by(opt(parse_whitespace))
562 .map(String::from),
563 ),
564 ))
565 .map(|((name, color), items)| GroupInstruction::ManualGroup {
566 name: name.into(),
567 color: color.map(|c| c.into()),
568 items,
569 })
570 .terminated(tuple((
571 opt(parse_whitespace),
572 tag_no_case("}"),
573 opt(parse_whitespace),
574 )))
575 .parse(input)
576}
577
578fn parse_target_list(input: &str) -> IResult<&str, Vec<&str>> {
579 many0(is_not("\n\r \t#}").preceded_by(opt(parse_whitespace)))
580 .terminated(opt(parse_whitespace))
581 .parse(input)
582}
583
584fn parse_group_by_extension(input: &str) -> IResult<&str, GroupInstruction> {
585 value(
591 GroupInstruction::GroupSourceHeader,
592 tag_no_case("group_source_header"),
593 )
594 .terminated(opt(parse_whitespace))
595 .parse(input)
596}
597
598fn parse_group(input: &str) -> IResult<&str, Vec<GroupInstruction>> {
599 many0(alt((
600 parse_group_by_extension,
601 parse_gn_target,
602 parse_manual_group,
603 )))
604 .preceded_by(tuple((
605 opt(parse_whitespace),
606 tag_no_case("group"),
607 opt(parse_whitespace),
608 tag_no_case("{"),
609 opt(parse_whitespace),
610 )))
611 .terminated(tuple((
612 opt(parse_whitespace),
613 tag_no_case("}"),
614 opt(parse_whitespace),
615 )))
616 .parse(input)
617}
618
619fn parse_color_instruction(input: &str) -> IResult<&str, ColorInstruction> {
620 #[derive(Copy, Clone, PartialEq)]
621 enum Direction {
622 From,
623 To,
624 }
625
626 tuple((
627 alt((
628 value(Direction::From, tag_no_case("from")),
629 value(Direction::To, tag_no_case("to")),
630 ))
631 .preceded_by(opt(parse_whitespace))
632 .terminated(parse_whitespace),
633 parse_until_whitespace.terminated(parse_whitespace),
634 opt(tag_no_case("bold").terminated(parse_whitespace)).map(|v| v.is_some()),
635 parse_until_whitespace,
636 ))
637 .map(|(direction, name, bold, color)| ColorInstruction {
638 end: match direction {
639 Direction::From => GroupEdgeEnd::From(name.into()),
640 Direction::To => GroupEdgeEnd::To(name.into()),
641 },
642 color: if bold {
643 EdgeColor::Bold(color.into())
644 } else {
645 EdgeColor::Regular(color.into())
646 },
647 })
648 .parse(input)
649}
650
651fn parse_color_instructions(input: &str) -> IResult<&str, Vec<ColorInstruction>> {
652 separated_list0(parse_whitespace, parse_color_instruction)
653 .preceded_by(tuple((
654 opt(parse_whitespace),
655 tag_no_case("color"),
656 parse_whitespace,
657 tag_no_case("edges"),
658 opt(parse_whitespace),
659 tag_no_case("{"),
660 opt(parse_whitespace),
661 )))
662 .terminated(tuple((
663 opt(parse_whitespace),
664 tag_no_case("}"),
665 opt(parse_whitespace),
666 )))
667 .parse(input)
668}
669
670fn parse_zoom(input: &str) -> IResult<&str, Vec<ZoomItem>> {
671 many0(
672 tuple((
673 opt(tag_no_case("focus:").terminated(parse_whitespace)),
674 is_not("\n\r \t#}").terminated(opt(parse_whitespace)),
675 ))
676 .map(|(focus, name)| ZoomItem {
677 name: name.into(),
678 focused: focus.is_some(),
679 }),
680 )
681 .preceded_by(tuple((
682 opt(parse_whitespace),
683 tag_no_case("zoom"),
684 opt(parse_whitespace),
685 tag_no_case("{"),
686 opt(parse_whitespace),
687 )))
688 .terminated(tuple((
689 opt(parse_whitespace),
690 tag_no_case("}"),
691 opt(parse_whitespace),
692 )))
693 .parse(input)
694}
695
696fn parse_graph(input: &str) -> IResult<&str, GraphInstructions> {
697 tuple((
698 parse_map_instructions,
699 parse_group,
700 opt(parse_color_instructions),
701 opt(parse_zoom),
702 ))
703 .preceded_by(tuple((
704 opt(parse_whitespace),
705 tag_no_case("graph"),
706 parse_whitespace,
707 tag_no_case("{"),
708 opt(parse_whitespace),
709 )))
710 .terminated(tuple((
711 opt(parse_whitespace),
712 tag_no_case("}"),
713 opt(parse_whitespace),
714 )))
715 .map(
716 |(map_instructions, group_instructions, color_instructions, zoom)| GraphInstructions {
717 map_instructions,
718 group_instructions,
719 color_instructions: color_instructions.unwrap_or_default(),
720 zoom_items: zoom.unwrap_or_default(),
721 },
722 )
723 .parse(input)
724}
725
726fn parse_variable_assignment(input: &str) -> IResult<&str, VariableAssignment> {
727 separated_pair(
728 parse_variable_name,
729 tag_no_case("=")
730 .preceded_by(opt(parse_whitespace))
731 .terminated(opt(parse_whitespace)),
732 parse_until_whitespace,
733 )
734 .map(|(name, value)| VariableAssignment {
735 name: name.into(),
736 value: value.into(),
737 })
738 .parse(input)
739}
740
741fn parse_variable_assignments(input: &str) -> IResult<&str, HashMap<String, String>> {
742 separated_list0(parse_whitespace, parse_variable_assignment)
743 .preceded_by(opt(parse_whitespace))
744 .terminated(opt(parse_whitespace))
745 .map(|v| v.resolve_variables())
746 .parse(input)
747}
748
749fn parse_config(input: &str) -> IResult<&str, ConfigurationFile> {
750 tuple((parse_variable_assignments, parse_input, parse_graph))
751 .map(|(variable_map, input_commands, graph)| ConfigurationFile {
752 input_commands: input_commands.expanded_from(&variable_map),
753 graph: graph.expanded_from(&variable_map),
754 variable_map,
755 })
756 .parse(input)
757}
758
759pub async fn build_graph(input: &str) -> Result<Graph, Report> {
760 let (input, config) = parse_config(input)
761 .map_err(|e| Error::ConfigParseError {
762 message: format!("Nom error: {:?}", e),
763 })
764 .wrap_err("Failed to parse with nom")?;
765
766 if !input.is_empty() {
767 return Err(Error::ConfigParseError {
768 message: format!("Not all input was consumed: {:?}", input),
769 }
770 .into());
771 }
772
773 debug!("Variables: {:#?}", config.variable_map);
774 debug!("Input: {:#?}", config.input_commands);
775 debug!("Graph: {:#?}", config.graph);
776
777 let mut dependency_data = DependencyData::default();
778
779 for i in config.input_commands {
780 match i {
781 InputCommand::LoadCompileDb {
782 path,
783 load_include_directories,
784 load_sources,
785 } => {
786 let entries = match parse_compile_database(&path).await {
787 Ok(entries) => entries,
788 Err(err) => {
789 error!("Error parsing compile database {}: {:?}", path, err);
790 continue;
791 }
792 };
793 if entries.is_empty() {
794 warn!(target: "compile-db", "No entries loaded from {}", path);
795 warn!(target: "compile-db",
796 "This may happen if directory/source paths are not correct (e.g. compilation on a different system)."
797 );
798 warn!(target: "compile-db",
799 "Consider debugging by setting RUST_LOG=\"compile-db=debug\" to debug entry parsing."
800 );
801 }
802 info!(target: "compile-db", "Loaded {} compile entries from {}", entries.len(), &path);
803 if load_include_directories {
804 let compile_db_includes = entries
805 .iter()
806 .flat_map(|e| e.include_directories.clone())
807 .collect::<HashSet<_>>();
808 info!(target: "compile-db",
809 "Include directories from {}: {:#?}", &path, compile_db_includes);
810
811 dependency_data.includes.extend(compile_db_includes);
812 }
813 if load_sources {
814 let includes_array = dependency_data
815 .includes
816 .clone()
817 .into_iter()
818 .collect::<Vec<_>>();
819 for entry in entries {
820 match extract_includes(&entry.file_path, &includes_array).await {
821 Ok(includes) => {
822 info!(target: "compile-db", "Loaded {:?} with includes {:#?}", &entry.file_path, includes);
823 dependency_data.files.push(SourceWithIncludes {
824 path: entry.file_path,
825 includes,
826 });
827 }
828 Err(e) => {
829 error!(
830 "Includee extraction for {:?} failed: {:?}",
831 &entry.file_path, e
832 );
833 }
834 };
835 }
836 }
837 }
838 InputCommand::IncludeDirectory(path) => {
839 dependency_data.includes.insert(PathBuf::from(path));
840 }
841 InputCommand::Glob(g) => {
842 let glob = match glob::glob(&g) {
843 Ok(value) => value,
844 Err(e) => {
845 error!("Glob error for {}: {:?}", g, e);
846 continue;
847 }
848 };
849 let includes_array = dependency_data
850 .includes
851 .clone()
852 .into_iter()
853 .collect::<Vec<_>>();
854 match all_sources_and_includes(glob, &includes_array).await {
855 Ok(data) => {
856 if data.is_empty() {
857 error!("GLOB {:?} resulted in EMPTY file list!", g);
858 }
859 dependency_data.files.extend(data)
860 }
861 Err(e) => {
862 error!("Include prodcessing for {} failed: {:?}", g, e);
863 continue;
864 }
865 }
866 }
867 }
868 }
869
870 let mut mapper = PathMapper::default();
872 for i in config.graph.map_instructions.iter() {
873 if let MapInstruction::DisplayMap { from, to } = i {
874 mapper.add_mapping(PathMapping {
875 from: PathBuf::from(from),
876 to: to.clone(),
877 });
878 }
879 }
880 let keep = config
881 .graph
882 .map_instructions
883 .iter()
884 .filter_map(|i| match i {
885 MapInstruction::Keep(v) => Some(v),
886 _ => None,
887 })
888 .collect::<HashSet<_>>();
889
890 let drop = config
891 .graph
892 .map_instructions
893 .iter()
894 .filter_map(|i| match i {
895 MapInstruction::Drop(v) => Some(v),
896 _ => None,
897 })
898 .collect::<HashSet<_>>();
899
900 info!(target: "full-file-list", "Procesed files: {}", FullFileList::new(&dependency_data));
901
902 let mut g = GraphBuilder::new(
904 dependency_data
905 .files
906 .iter()
907 .flat_map(|f| f.includes.iter().chain(std::iter::once(&f.path)))
908 .filter_map(|path| {
909 mapper.try_map(path).map(|to| PathMapping {
910 from: path.clone(),
911 to,
912 })
913 })
914 .filter(|m| keep.iter().any(|prefix| m.to.starts_with(*prefix)))
915 .filter(|m| drop.iter().all(|prefix| !m.to.starts_with(*prefix))),
916 );
917
918 for group_instruction in config.graph.group_instructions {
920 match group_instruction {
921 GroupInstruction::GroupSourceHeader => {
922 g.group_extensions(&["h", "cpp", "hpp", "c", "cxx"]);
923 }
924 GroupInstruction::GroupFromGn {
925 gn_root,
926 target,
927 source_root,
928 ignore_targets,
929 } => match load_gn_targets(
930 &PathBuf::from(gn_root),
931 &PathBuf::from(source_root),
932 &target,
933 )
934 .await
935 {
936 Ok(targets) => g.add_groups_from_gn(targets, ignore_targets),
937 Err(e) => error!("Failed to load GN targets: {:?}", e),
938 },
939 GroupInstruction::ManualGroup { name, color, items } => {
940 g.define_group(
943 &name,
944 color.as_deref().unwrap_or("orange"),
945 items.into_iter().filter_map(|n| mapper.try_invert(&n)),
946 );
947 }
948 }
949 }
950
951 for item in config.graph.zoom_items {
953 g.zoom_in(&item.name, item.focused)
954 }
955
956 for dep in dependency_data.files {
957 if !g.known_path(&dep.path) {
958 continue;
959 }
960 for dest in dep.includes {
961 if !g.known_path(&dest) {
962 continue;
963 }
964 g.add_link(&dep.path, &dest);
965 }
966 }
967
968 for i in config.graph.color_instructions {
969 match i.end {
970 GroupEdgeEnd::From(name) => {
971 g.color_from(&name, i.color.color_name(), i.color.is_bold())
972 }
973 GroupEdgeEnd::To(name) => g.color_to(&name, i.color.color_name(), i.color.is_bold()),
974 }
975 }
976
977 debug!("Final builder: {:#?}", g);
978
979 Ok(g.build())
980}
981
982#[cfg(test)]
983mod tests {
984 use super::*;
985
986 #[test]
987 fn test_comment_parsing() {
988 assert_eq!(parse_comment("#abc\r\nhello"), Ok(("\r\nhello", "abc")));
989 assert!(parse_comment("not a comment").is_err());
990 assert!(parse_comment("comment later # like here").is_err());
991 }
992
993 #[test]
994 fn test_gn_target() {
995 assert_eq!(
996 parse_gn_target("gn root test1 target //my/target/* sources srcs1"),
997 Ok((
998 "",
999 GroupInstruction::GroupFromGn {
1000 gn_root: "test1".into(),
1001 target: "//my/target/*".into(),
1002 source_root: "srcs1".into(),
1003 ignore_targets: HashSet::new(),
1004 },
1005 ))
1006 );
1007
1008 assert_eq!(
1009 parse_gn_target("gn root test1 target //my/target/* sources srcs1 ignore targets {}"),
1010 Ok((
1011 "",
1012 GroupInstruction::GroupFromGn {
1013 gn_root: "test1".into(),
1014 target: "//my/target/*".into(),
1015 source_root: "srcs1".into(),
1016 ignore_targets: HashSet::new(),
1017 },
1018 ))
1019 );
1020
1021 assert_eq!(
1022 parse_gn_target(
1023 "gn root test1 target //my/target/* sources srcs1 ignore targets{
1024 }"
1025 ),
1026 Ok((
1027 "",
1028 GroupInstruction::GroupFromGn {
1029 gn_root: "test1".into(),
1030 target: "//my/target/*".into(),
1031 source_root: "srcs1".into(),
1032 ignore_targets: HashSet::new(),
1033 },
1034 ))
1035 );
1036
1037 assert_eq!(
1038 parse_gn_target(
1039 "gn root test1 target //my/target/* sources srcs1 ignore targets{
1040 a b
1041 c
1042 d
1043 }"
1044 ),
1045 Ok((
1046 "",
1047 GroupInstruction::GroupFromGn {
1048 gn_root: "test1".into(),
1049 target: "//my/target/*".into(),
1050 source_root: "srcs1".into(),
1051 ignore_targets: vec!["a", "b", "c", "d"]
1052 .into_iter()
1053 .map(String::from)
1054 .collect(),
1055 },
1056 ))
1057 );
1058 }
1059
1060 #[test]
1061 fn test_manual_group() {
1062 assert_eq!(
1063 parse_manual_group(
1064 "
1065 manual some/name::special {
1066 file1
1067 file2
1068 another/file::test
1069 }
1070 "
1071 ),
1072 Ok((
1073 "",
1074 GroupInstruction::ManualGroup {
1075 name: "some/name::special".into(),
1076 color: None,
1077 items: vec!["file1".into(), "file2".into(), "another/file::test".into(),]
1078 }
1079 ))
1080 );
1081
1082 assert_eq!(
1083 parse_manual_group(
1084 "
1085 manual some/name::special color red {
1086 file1
1087 file2
1088 another/file::test
1089 }
1090 "
1091 ),
1092 Ok((
1093 "",
1094 GroupInstruction::ManualGroup {
1095 name: "some/name::special".into(),
1096 color: Some("red".into()),
1097 items: vec!["file1".into(), "file2".into(), "another/file::test".into(),]
1098 }
1099 ))
1100 );
1101 }
1102
1103 #[test]
1104 fn test_gn_instruction() {
1105 let mut variable_map = HashMap::new();
1106 variable_map.insert("Foo".into(), "Bar".into());
1107
1108 assert_eq!(
1109 parse_graph(
1110 "
1111 graph {
1112 map {
1113 }
1114
1115 group {
1116 gn root test1 target //my/target/* sources srcs1
1117 gn root test/${Foo}/blah target //* sources ${Foo} ignore targets {
1118 //ignore1
1119 //ignore:other
1120 }
1121 }
1122 }
1123 ",
1124 )
1125 .map(|(r, g)| { (r, g.expanded_from(&variable_map)) }),
1126 Ok((
1127 "",
1128 GraphInstructions {
1129 map_instructions: Vec::default(),
1130 group_instructions: vec![
1131 GroupInstruction::GroupFromGn {
1132 gn_root: "test1".into(),
1133 target: "//my/target/*".into(),
1134 source_root: "srcs1".into(),
1135 ignore_targets: HashSet::new(),
1136 },
1137 GroupInstruction::GroupFromGn {
1138 gn_root: "test/Bar/blah".into(),
1139 target: "//*".into(),
1140 source_root: "Bar".into(),
1141 ignore_targets: {
1142 let mut h = HashSet::new();
1143 h.insert("//ignore1".into());
1144 h.insert("//ignore:other".into());
1145 h
1146 }
1147 },
1148 ],
1149 ..Default::default()
1150 }
1151 ))
1152 );
1153 }
1154
1155 #[test]
1156 fn test_color_instruction_parsing() {
1157 assert_eq!(
1158 parse_color_instruction("from source color"),
1159 Ok((
1160 "",
1161 ColorInstruction {
1162 end: GroupEdgeEnd::From("source".into()),
1163 color: EdgeColor::Regular("color".into())
1164 }
1165 ))
1166 );
1167
1168 assert_eq!(
1169 parse_color_instruction("to destination color"),
1170 Ok((
1171 "",
1172 ColorInstruction {
1173 end: GroupEdgeEnd::To("destination".into()),
1174 color: EdgeColor::Regular("color".into())
1175 }
1176 ))
1177 );
1178
1179 assert_eq!(
1180 parse_color_instruction("From x y"),
1181 Ok((
1182 "",
1183 ColorInstruction {
1184 end: GroupEdgeEnd::From("x".into()),
1185 color: EdgeColor::Regular("y".into())
1186 }
1187 ))
1188 );
1189
1190 assert_eq!(
1191 parse_color_instruction("#comment\n TO a bold b"),
1192 Ok((
1193 "",
1194 ColorInstruction {
1195 end: GroupEdgeEnd::To("a".into()),
1196 color: EdgeColor::Bold("b".into())
1197 }
1198 ))
1199 );
1200 }
1201
1202 #[test]
1203 fn test_color_instructions_parsing() {
1204 assert_eq!(
1205 parse_color_instructions("color edges {}"),
1206 Ok(("", Vec::default()))
1207 );
1208 assert_eq!(
1209 parse_color_instructions(" #comment\ncolor edges { \n }\n#more comments\n \n"),
1210 Ok(("", Vec::default()))
1211 );
1212
1213 assert_eq!(
1214 parse_color_instructions(
1215 "
1216 #comment
1217 color edges {
1218 from x y
1219 to q r
1220 from a bold b
1221 }"
1222 ),
1223 Ok((
1224 "",
1225 vec![
1226 ColorInstruction {
1227 end: GroupEdgeEnd::From("x".into()),
1228 color: EdgeColor::Regular("y".into()),
1229 },
1230 ColorInstruction {
1231 end: GroupEdgeEnd::To("q".into()),
1232 color: EdgeColor::Regular("r".into()),
1233 },
1234 ColorInstruction {
1235 end: GroupEdgeEnd::From("a".into()),
1236 color: EdgeColor::Bold("b".into()),
1237 },
1238 ]
1239 ))
1240 );
1241
1242 assert_eq!(
1243 parse_zoom(
1244 "
1245 #comment
1246 zoom{
1247 normal
1248 focus: thisone
1249 not this
1250 }"
1251 ),
1252 Ok((
1253 "",
1254 vec![
1255 ZoomItem {
1256 name: "normal".to_string(),
1257 focused: false
1258 },
1259 ZoomItem {
1260 name: "thisone".to_string(),
1261 focused: true
1262 },
1263 ZoomItem {
1264 name: "not".to_string(),
1265 focused: false
1266 },
1267 ZoomItem {
1268 name: "this".to_string(),
1269 focused: false
1270 },
1271 ]
1272 ))
1273 );
1274
1275 assert!(parse_zoom("blah").is_err());
1276 }
1277
1278 #[test]
1279 fn test_zoom_parsing() {
1280 assert_eq!(parse_zoom("zoom{}"), Ok(("", Vec::default())));
1281 assert_eq!(
1282 parse_zoom(" #comment\nzoom { \n }\n#more comments\n \n"),
1283 Ok(("", Vec::default()))
1284 );
1285
1286 assert_eq!(
1287 parse_zoom(
1288 "
1289 #comment
1290 zoom{
1291 this
1292 is some #notice that whitespace matters and NOT newlines
1293 test
1294 }"
1295 ),
1296 Ok((
1297 "",
1298 vec![
1299 ZoomItem {
1300 name: "this".to_string(),
1301 focused: false
1302 },
1303 ZoomItem {
1304 name: "is".to_string(),
1305 focused: false
1306 },
1307 ZoomItem {
1308 name: "some".to_string(),
1309 focused: false
1310 },
1311 ZoomItem {
1312 name: "test".to_string(),
1313 focused: false
1314 },
1315 ]
1316 ))
1317 );
1318
1319 assert_eq!(
1320 parse_zoom(
1321 "
1322 #comment
1323 zoom{
1324 normal
1325 focus: thisone
1326 not this
1327 }"
1328 ),
1329 Ok((
1330 "",
1331 vec![
1332 ZoomItem {
1333 name: "normal".to_string(),
1334 focused: false
1335 },
1336 ZoomItem {
1337 name: "thisone".to_string(),
1338 focused: true
1339 },
1340 ZoomItem {
1341 name: "not".to_string(),
1342 focused: false
1343 },
1344 ZoomItem {
1345 name: "this".to_string(),
1346 focused: false
1347 },
1348 ]
1349 ))
1350 );
1351
1352 assert!(parse_zoom("blah").is_err());
1353 }
1354
1355 #[test]
1356 fn test_parse_target_list() {
1357 assert_eq!(parse_target_list(""), Ok(("", vec![])));
1358 assert_eq!(parse_target_list(" "), Ok(("", vec![])));
1359 assert_eq!(parse_target_list("a b c"), Ok(("", vec!["a", "b", "c"])));
1360 assert_eq!(
1361 parse_target_list(" a \n\n b\n c\n\n"),
1362 Ok(("", vec!["a", "b", "c"]))
1363 );
1364 assert_eq!(parse_target_list("}"), Ok(("}", vec![])));
1366 assert_eq!(parse_target_list("a b c }"), Ok(("}", vec!["a", "b", "c"])));
1367 }
1368
1369 #[test]
1370 fn test_parse_glob() {
1371 assert_eq!(
1372 parse_input_command("glob a/b/**/*"),
1373 Ok(("", InputCommand::Glob("a/b/**/*".into())))
1374 );
1375
1376 assert_eq!(
1377 parse_input_command("glob a/x/**/*.h # should consume whitespace\n\n \n\t\n "),
1378 Ok(("", InputCommand::Glob("a/x/**/*.h".into())))
1379 );
1380 }
1381
1382 #[test]
1383 fn test_parse_include_dir() {
1384 assert_eq!(
1385 parse_input_command("include_dir a/b/**/*"),
1386 Ok(("", InputCommand::IncludeDirectory("a/b/**/*".into())))
1387 );
1388
1389 assert_eq!(
1390 parse_input_command("include_dir a/x/**/*.h # should consume whitespace\n\n \n\t\n "),
1391 Ok(("", InputCommand::IncludeDirectory("a/x/**/*.h".into())))
1392 );
1393 }
1394
1395 #[test]
1396 fn test_parse_compiledb() {
1397 assert_eq!(
1398 parse_compiledb("from compiledb foo load include_dirs"),
1399 Ok((
1400 "",
1401 InputCommand::LoadCompileDb {
1402 path: "foo".into(),
1403 load_include_directories: true,
1404 load_sources: false
1405 }
1406 ))
1407 );
1408
1409 assert_eq!(
1410 parse_compiledb("from compiledb bar load sources"),
1411 Ok((
1412 "",
1413 InputCommand::LoadCompileDb {
1414 path: "bar".into(),
1415 load_include_directories: false,
1416 load_sources: true
1417 }
1418 ))
1419 );
1420
1421 assert_eq!(
1422 parse_compiledb("from compiledb bar load sources, include_dirs, sources"),
1423 Ok((
1424 "",
1425 InputCommand::LoadCompileDb {
1426 path: "bar".into(),
1427 load_include_directories: true,
1428 load_sources: true
1429 }
1430 ))
1431 );
1432
1433 assert_eq!(
1434 parse_compiledb(
1435 "
1436 # This is a comment (and whitespace) prefix
1437 from compiledb x/y/z load sources, sources\n# space/comment suffix\n"
1438 ),
1439 Ok((
1440 "",
1441 InputCommand::LoadCompileDb {
1442 path: "x/y/z".into(),
1443 load_include_directories: false,
1444 load_sources: true
1445 }
1446 ))
1447 );
1448
1449 assert_eq!(
1450 parse_compiledb(
1451 "
1452 from compiledb x/y/z load sources, sources\nremaining"
1453 ),
1454 Ok((
1455 "remaining",
1456 InputCommand::LoadCompileDb {
1457 path: "x/y/z".into(),
1458 load_include_directories: false,
1459 load_sources: true
1460 }
1461 ))
1462 );
1463 }
1464
1465 #[test]
1466 fn test_parse_input() {
1467 assert_eq!(
1468 parse_input(
1469 "input {
1470 from compiledb some_compile_db.json load include_dirs
1471 include_dir foo
1472 from compiledb some_compile_db.json load sources
1473
1474 glob xyz/**/*
1475
1476 include_dir bar
1477 from compiledb another.json load sources, include_dirs
1478
1479 glob final/**/*
1480 glob blah/**/*
1481 }"
1482 ),
1483 Ok((
1484 "",
1485 vec![
1486 InputCommand::LoadCompileDb {
1487 path: "some_compile_db.json".into(),
1488 load_include_directories: true,
1489 load_sources: false
1490 },
1491 InputCommand::IncludeDirectory("foo".into()),
1492 InputCommand::LoadCompileDb {
1493 path: "some_compile_db.json".into(),
1494 load_include_directories: false,
1495 load_sources: true
1496 },
1497 InputCommand::Glob("xyz/**/*".into()),
1498 InputCommand::IncludeDirectory("bar".into()),
1499 InputCommand::LoadCompileDb {
1500 path: "another.json".into(),
1501 load_include_directories: true,
1502 load_sources: true
1503 },
1504 InputCommand::Glob("final/**/*".into()),
1505 InputCommand::Glob("blah/**/*".into()),
1506 ]
1507 ))
1508 );
1509 }
1510
1511 #[test]
1512 fn test_variable_assignments() {
1513 assert_eq!(
1514 parse_variable_assignments(
1515 "
1516 a = b
1517 x=y
1518 z=${a}${x}
1519 ab=test
1520 other=${a${a}}ing
1521 "
1522 ),
1523 {
1524 let mut expected = HashMap::new();
1525 expected.insert("a".into(), "b".into());
1526 expected.insert("x".into(), "y".into());
1527 expected.insert("z".into(), "by".into());
1528 expected.insert("ab".into(), "test".into());
1529 expected.insert("other".into(), "testing".into());
1530 Ok(("", expected))
1531 }
1532 );
1533 }
1534
1535 #[test]
1536 fn test_expand_var() {
1537 let mut vars = HashMap::new();
1538 vars.insert("foo".into(), "bar".into());
1539 vars.insert("another".into(), "one".into());
1540 vars.insert("test".into(), "1234".into());
1541 vars.insert("theone".into(), "final".into());
1542
1543 assert_eq!(expand_variable("xyz", &vars), "xyz");
1544 assert_eq!(expand_variable("${foo}", &vars), "bar");
1545 assert_eq!(expand_variable("${another}", &vars), "one");
1546 assert_eq!(
1547 expand_variable("${foo}/${another}/${foo}", &vars),
1548 "bar/one/bar"
1549 );
1550 assert_eq!(expand_variable("${the${another}}", &vars), "final");
1551 }
1552}