File Coverage

blib/lib/App/ArduinoBuilder/Config.pm
Criterion Covered Total %
statement 98 116 84.4
branch 29 54 53.7
condition 14 29 48.2
subroutine 16 21 76.1
pod 0 15 0.0
total 157 235 66.8


line stmt bran cond sub pod time code
1             package App::ArduinoBuilder::Config;
2              
3 1     1   234197 use strict;
  1         3  
  1         30  
4 1     1   4 use warnings;
  1         3  
  1         24  
5 1     1   6 use utf8;
  1         3  
  1         5  
6              
7 1     1   421 use App::ArduinoBuilder::Logger;
  1         3  
  1         75  
8 1     1   10 use Exporter 'import';
  1         4  
  1         1821  
9              
10             our @EXPORT_OK = qw(get_os_name);
11              
12             # Reference for the whole configuration interpretation:
13             # https://arduino.github.io/arduino-cli/0.32/platform-specification
14              
15             sub new {
16 8     8 0 1322 my ($class, %options) = @_;
17 8         33 my $this = bless {config => {}, files => 0, options => {}}, $class;
18 8 50       24 $this->read_file($options{file}, %options) if $options{file};
19 8         12 for my $f (@{$options{files}}) {
  8         26  
20 1         14 $this->read_file($f, %options, no_resolve => 1);
21             }
22 8 50       43 $this->resolve(%options) unless $options{no_resolve};
23 8         29 return $this;
24             }
25              
26             sub read_file {
27 2     2 0 6632 my ($this, $file_name, %options) = @_;
28 2         5 %options = (%{$this->{options}}, %options);
  2         7  
29 2 50 33     10 return if $options{allow_missing} && ! -f $file_name;
30 2 50       93 open my $fh, '<', $file_name or fatal "Can’t open '${file_name}': $!";
31 2         68 while (my $l = <$fh>) {
32 12 100       53 next if $l =~ m/^\s*(?:#.*)?$/; # Only whitespace or comment
33 8 50       54 fatal "Unparsable line in ${file_name}: ${l}" unless $l =~ m/^\s*([-0-9a-z_.]+?)\s*=\s*(.*?)\s*$/i;
34 8 50 33     77 $this->{config}{$1} = $2 if !(exists $this->{config}{$1}) || $options{allow_override};
35             }
36 2 100       12 $this->resolve(%options) unless $options{no_resolve};
37 2         6 $this->{nb_files}++;
38 2         33 return 1;
39             }
40              
41             sub size {
42 0     0 0 0 my ($this) = @_;
43 0         0 return scalar keys %{$this->{config}};
  0         0  
44             }
45              
46             sub empty {
47 0     0 0 0 my ($this) = @_;
48 0         0 return $this->size() == 0;
49             }
50              
51             sub nb_files {
52 0     0 0 0 my ($this) = @_;
53             return $this->{nb_files}
54 0         0 }
55              
56             sub get {
57 27     27 0 71 my ($this, $key, %options) = @_;
58 27         36 %options = (%{$this->{options}}, %options);
  27         77  
59 27 0 33     60 return $options{default} if !$this->exists($key) && exists $options{default};
60 27 50       56 $options{allow_partial} = 1 if $options{no_resolve};
61 27         64 my $v = _resolve_key($key, $this->{config}, %options, allow_partial => 1);
62 27 50       58 fatal "Key '$key' does not exist in the configuration." unless defined $v;
63 27 50 66     105 fatal "Key '$key' has unresolved reference to value '$1'." if $v =~ m/\{([^}]+)\}/ && !$options{allow_partial};
64 27         71 return $v;
65             }
66              
67             sub keys {
68 6     6 0 10 my ($this, %options) = @_;
69 6         10 return keys %{$this->{config}};
  6         31  
70             }
71              
72             sub exists {
73 37     37 0 67 my ($this, $key) = @_;
74 37         112 return exists $this->{config}{$key};
75             }
76              
77             sub set {
78 9     9 0 36 my ($this, $key, $value, %options) = @_;
79 9         10 %options = (%{$this->{options}}, %options);
  9         19  
80 9 50       19 if (exists $this->{config}{$key}) {
81 0 0       0 return if $options{ignore_existing};
82 0 0       0 fatal "Key '$key' already exists." unless $options{allow_override};
83             }
84 9         18 $this->{config}{$key} = $value;
85 9         16 return;
86             }
87              
88             sub append {
89 0     0 0 0 my ($this, $key, $value) = @_;
90 0 0       0 if ($this->{config}{$key}) {
91 0         0 $this->{config}{$key} .= ' '.$value;
92             } else {
93 0         0 $this->{config}{$key} = $value;
94             }
95 0         0 return;
96             }
97              
98             sub _resolve_key {
99 57     57   132 my ($key, $config, %options) = @_;
100 57 50       126 return $options{with}{$key} if exists $options{with}{$key};
101 57 100       112 if (not exists $config->{$key}) {
102 20 100 100     63 return $options{base}->get($key, %options{grep { $_ ne 'base'} CORE::keys %options}) if exists $options{base} && $options{base}->exists($key);
  18         51  
103 14 50       28 fatal "Can’t resolve key '${key}' in the configuration." unless $options{allow_partial};
104 14         32 return;
105             }
106 37         54 my $value = $config->{$key};
107 37 50       67 return $value if $options{no_resolve};
108 37         121 while ($value =~ m/\{([^{}]+)\}/g) {
109 30         69 my $new_key = $1;
110 30         65 my $match_start = $-[0];
111 30         87 my $match_len = $+[0] - $-[0];
112 30         59 my $l = 2 + length($new_key);
113 30         101 my $new_value = _resolve_key($new_key, $config, %options);
114 30 100       135 substr $value, $match_start, $match_len, $new_value if defined $new_value;
115             }
116 37         87 return $value;
117             }
118              
119             # The Arduino OS name, based on the Perl OS name.
120             sub get_os_name {
121             # It is debattable how we want to treat cygwin and msys. For now we assume
122             # that they will be used with a windows native Arduino toolchain.
123 10 50 33 10 0 66 return 'windows' if $^O eq 'MSWin32' || $^O eq 'cygwin' || $^O eq 'msys';
      33        
124 10 50       26 return 'macosx' if $^O eq 'MacOS';
125 10         25 return 'linux';
126             }
127              
128             # This only does the OS resolution, not each key/value interpretation as this
129             # should always be done as late as possible in case some values changes later.
130             #
131             # Should be called manually only if you call set() with OS specific keys (mainly
132             # in tests).
133             sub resolve {
134 11     11 0 31 my ($this, %options) = @_;
135 11         18 %options = (%{$this->{options}}, %options);
  11         37  
136 11         22 my $config = $this->{config};
137 11   66     34 my $os_name = $options{force_os_name} // get_os_name();
138 11         31 for my $k (CORE::keys %$config) {
139 17 100       77 $config->{$1} = $config->{$k} if $k =~ m/^(.*)\.$os_name$/;
140             }
141 11         27 return 1;
142             }
143              
144             # By default, definition from this are kept and not replaced by those from others.
145             sub merge {
146 0     0 0 0 my ($this, $other, %options) = @_;
147 0         0 while (my ($k, $v) = each %{$other->{config}}) {
  0         0  
148 0 0 0     0 $this->{config}{$k} = $v if $options{allow_override} || !exists $this->{config}{$k};
149             }
150             }
151              
152             sub filter {
153 3     3 0 14 my ($this, $prefix) = @_;
154 3         12 my $filtered = App::ArduinoBuilder::Config->new();
155 3         8 $filtered->{options}{base} = $this;
156 3         40 while (my ($k, $v) = each %{$this->{config}}) {
  13         55  
157 10 100       62 if ($k =~ m/^\Q$prefix\E\./) {
158 7         32 $filtered->{config}{substr($k, $+[0])} = $v;
159             }
160             }
161 3         22 return $filtered;
162             }
163              
164             sub dump {
165 6     6 0 14 my ($this, $prefix) = @_;
166 6         10 my $out = '';
167 6   100     20 my $p = $prefix // '';
168 6         16 for my $k (sort($this->keys())) {
169 18         40 my $v = $this->get($k, allow_partial => 1);
170 18         54 $out .= "${p}${k}=${v}\n";
171             }
172 6         29 return $out;
173             }
174              
175             1;