File Coverage

lib/CSS/Watcher.pm
Criterion Covered Total %
statement 167 192 86.9
branch 38 52 73.0
condition 4 5 80.0
subroutine 23 26 88.4
pod 0 6 0.0
total 232 281 82.5


line stmt bran cond sub pod time code
1             package CSS::Watcher;
2              
3 1     1   42789 use strict;
  1         2  
  1         26  
4 1     1   6 use warnings;
  1         1  
  1         25  
5              
6 1     1   4 use Carp;
  1         2  
  1         59  
7 1     1   964 use Data::Dumper;
  1         8581  
  1         65  
8              
9 1     1   1261 use Log::Log4perl qw(:easy);
  1         53634  
  1         6  
10 1     1   1482 use File::Slurp qw/read_file write_file/;
  1         15727  
  1         67  
11 1     1   7 use Path::Tiny;
  1         2  
  1         47  
12 1     1   5 use Digest::MD5 qw/md5_hex/;
  1         2  
  1         43  
13 1     1   880 use List::MoreUtils qw(any);
  1         11970  
  1         7  
14              
15 1     1   1042 use CSS::Watcher::Parser;
  1         3  
  1         36  
16 1     1   446 use CSS::Watcher::ParserLess;
  1         2  
  1         35  
17 1     1   391 use CSS::Watcher::Monitor;
  1         3  
  1         43  
18              
19             our $VERSION = '0.4.7';
20              
21 1     1   6 use constant DEFAULT_HTML_STUFF_DIR => '~/.emacs.d/ac-html-csswatcher/completion/';
  1         2  
  1         2059  
22              
23             sub new {
24 5     5 0 386274 my $class= shift;
25 5         11 my $options = shift;
26              
27             return bless ({
28 5   100     67 outputdir => $options->{'outputdir'} // DEFAULT_HTML_STUFF_DIR,
29             parser_css => CSS::Watcher::Parser->new(),
30             parser_less => CSS::Watcher::ParserLess->new(),
31             }, $class);
32             }
33              
34             sub update {
35 7     7 0 19 my $self = shift;
36 7         14 my $obj = shift;
37              
38             # check what is the monobj. file? dir?
39 7 100 66     175 if (-f $obj || -d $obj) {
40 4         15 my $proj_dir = $self->get_project_dir ($obj);
41 4 50       15 return unless (defined $proj_dir);
42              
43 4         14 INFO "Update project: $proj_dir";
44              
45 4         64 my $prj = $self->_get_project ($proj_dir);
46 4         20 $prj->{parsed_files} = []; # clean old parsed file list
47              
48 4         7 my $changes = 0;
49              
50 4         7 my (@ignore, @allow, @skip_dirs);
51 4         17 my $cfg = path($proj_dir)->child('.csswatcher');
52              
53             # clear project cache if .csswatcher changed
54 4 100       148 if ($prj->{monitor}->is_changed($cfg)) {
55 3         292 INFO ('.csswatcher changed, resetting');
56 3         28 $prj->{monitor}->make_dirty();
57 3         9 delete $prj->{parsed};
58 3         5 $changes++;
59             }
60              
61 4 100       22 if (-f $cfg) {
62 1 50   1   51 if (open (CFG, '<:encoding(UTF-8)', $cfg)) {
  1         1  
  1         8  
  3         52  
63 3         60420 while () {
64 10         45 chomp;
65 10 100       138 (m/^\s*ignore:\s*(.*?)\s*$/i) ? push @ignore, $1 :
    50          
    100          
66             (m/^\s*skip:\s*(.*?)\s*$/i) ? push @skip_dirs, $1 :
67             (m/^\s*use:\s*(.*?)\s*$/i) ? push @allow, $1 : 1;
68             }
69 3         36 close CFG;
70             }
71             }
72              
73             # scan new or changed files, cache them
74             $prj->{monitor}->scan (
75             sub {
76 13     13   16 my $file = shift;
77              
78 13 50       44 return if (any {$_ eq $file} @{$prj->{parsed_files}});
  12         23  
  13         46  
79              
80 13         37 my $allow = 0;
81 13         27 foreach (@allow) {
82 18 100       234 if ($file =~ m/$_/) {
83 4         6 $allow = 1;
84 4         7 last;
85             }
86             }
87 13 100       33 unless ($allow) {
88 9         19 foreach (@ignore) {
89 10 100       132 if ($file =~ m/$_/) {
90 2         12 INFO " Ignored $file =~\"$_\"";
91 2         18 return;
92             }
93             }
94             }
95 11 50       55 ($file =~ m/\.css$/) ? $changes += 1 && $self->_parse_css ($prj, $file) :
    100          
96             ($file =~ m/\.less$/) ? $changes += 1 && $self->_parse_less_and_imports ($prj, $file) : 1;
97              
98 4         52 }, \@skip_dirs);
99 4         33 INFO "Update done.";
100 4         39 return ($changes, $proj_dir);
101             }
102 3         13 return;
103             }
104              
105             sub _parse_css {
106 4     4   7 my ($self, $project, $file) = @_;
107              
108 4         19 INFO " (Re)parse css: $file";
109 4         44 my $data = read_file ($file);
110 4         317 my ($classes, $ids) = $self->{parser_css}->parse_css ($data);
111 4         19 $project->{parsed}{$file} = {CLASSES => $classes,
112             IDS => $ids};
113 4         7 push @{$project->{parsed_files}}, $file;
  4         12  
114 4         13 return 1;
115             }
116              
117             sub _parse_less {
118 0     0   0 my ($self, $project, $file) = @_;
119              
120 0         0 INFO " (Re)parse less: $file";
121 0         0 my ($classes, $ids, $requiries) = $self->{parser_less}->parse_less ($file);
122 0         0 $project->{parsed}{$file} = {CLASSES => $classes,
123             IDS => $ids};
124 0         0 push @{$project->{parsed_files}}, $file;
  0         0  
125              
126             # normilize path of requiried files, they may have .././
127             # eval {
128             # $project->{imports_less}{$file} =
129             # [ map {path($file)->parent->child($_)->realpath()->stringify} @{$requiries} ];
130             # };
131             # if ($@) {
132             # WARN $@;
133             # }
134             $project->{imports_less}{$file} =
135 0         0 [ map {path($file)->parent->child($_)->realpath()->stringify} @{$requiries} ];
  0         0  
  0         0  
136 0         0 return 1;
137             }
138              
139             sub _parse_less_and_imports {
140 0     0   0 my ($self, $project, $file) = @_;
141              
142 0         0 my $parsed_files = 1; # 1, cause we parse $file for sure., ++ if dependencies parsed too
143              
144 0         0 $self->_parse_less ($project, $file);
145              
146 0         0 while (my ($less_fname, $imports) = each %{$project->{imports_less}}) {
  0         0  
147 0         0 foreach (@{$imports}) {
  0         0  
148 0 0       0 if ($file eq $_) {
149 0 0   0   0 next if (any {$_ eq $less_fname} @{$project->{parsed_files}});
  0         0  
  0         0  
150 0         0 INFO sprintf " %s required by %s, parse them too.", path($file)->basename, path($less_fname)->basename;
151 0         0 $self->_parse_less($project, $less_fname);
152 0         0 $parsed_files++;
153             }
154             }
155             }
156 0         0 return $parsed_files;
157             }
158              
159             sub project_stuff {
160 4     4 0 1290 my $self = shift;
161 4         6 my $proj_dir = shift;
162              
163 4         11 my $prj = $self->_get_project ($proj_dir);
164              
165             # build unique tag - class,id
166 4         17 my (%classes, %ids);
167 4         7 my ($total_classes, $total_ids) = (0, 0);
168 4         7 while ( my ( $file, $completions ) = each %{$prj->{parsed}} ) {
  10         36  
169 6         8 while ( my ( $tag, $classes ) = each %{$completions->{CLASSES}} ) {
  15         59  
170 9         11 foreach (keys %{$classes}) {
  9         22  
171 9         35 $classes{$tag}{$_} .= 'Defined in ' . path( $file )->relative( $proj_dir ) . '\n';
172 9         1007 $total_classes++;
173             }
174             }
175             }
176 4         8 while ( my ( $file, $completions ) = each %{$prj->{parsed}} ) {
  10         30  
177 6         7 while ( my ( $tag, $ids ) = each %{$completions->{IDS}} ) {
  9         34  
178 3         4 foreach (keys %{$ids}) {
  3         8  
179 3         13 $ids{$tag}{$_} .= 'Defined in ' . path( $file )->relative( $proj_dir ) . '\n';
180 3         287 $total_ids++;
181             }
182             }
183             }
184 4         13 INFO "Total for $proj_dir:";
185 4         52 INFO " Classes: $total_classes, ids: $total_ids";
186              
187 4         29 return (\%classes, \%ids);
188             }
189              
190             # clean old output html complete stuff and build new
191             sub build_ac_html_stuff {
192 3     3 0 1968 my $self = shift;
193 3         6 my $proj_dir = shift;
194              
195 3         10 my ($classes, $ids) = $self->project_stuff ($proj_dir);
196              
197 3         11 my $ac_html_stuff_dir = path ($self->{outputdir})->child (md5_hex( ''.$proj_dir ));
198 3         156 my $attrib_dir = path ($ac_html_stuff_dir)->child ('html-attributes-complete');
199              
200 3         114 $attrib_dir->remove_tree({safe => 0});
201 3         114 $attrib_dir->mkpath;
202              
203 3         985 while ( my ( $tag, $class ) = each %{$classes} ) {
  7         872  
204 4         41 my $fname = File::Spec->catfile ($attrib_dir, $tag . '-class');
205 4         48 DEBUG "Write $fname";
206             write_file ($fname, join "\n", map {
207 4         31 $_ . ' ' . $class->{$_} } sort keys %{$class});
  4         24  
  4         13  
208             }
209 3         16 while ( my ( $tag, $id ) = each %${ids} ) {
210 2         20 my $fname = File::Spec->catfile ($attrib_dir, $tag . '-id');
211 2         23 DEBUG "Write $fname";
212             write_file ($fname, join "\n", map {
213 2         17 $_ . ' ' . $id->{$_} } sort keys %{$id});
  2         13  
  2         7  
214             }
215 3         342 DEBUG "Done writing. Reply to client.";
216 3         31 return $ac_html_stuff_dir;
217             }
218              
219             sub get_html_stuff {
220 4     4 0 2379 my $self = shift;
221 4         8 my $obj = shift;
222              
223 4         13 my ($changes, $project_dir) = $self->update ($obj);
224 4 100       17 return unless defined $changes;
225              
226 3         9 my $prj = $self->_get_project ($project_dir);
227              
228 3         12 my $ac_html_stuff_dir;
229              
230 3 100       7 if ($changes) {
231 2         8 $ac_html_stuff_dir = $self->build_ac_html_stuff ($project_dir);
232 2         6 $prj->{'ac_html_stuff'} = $ac_html_stuff_dir;
233             } else {
234 1         3 $ac_html_stuff_dir = $prj->{'ac_html_stuff'};
235             }
236 3         10 return ($project_dir, $ac_html_stuff_dir);
237             }
238              
239             sub _get_project {
240 11     11   16 my $self = shift;
241 11         18 my $dir = shift;
242 11 50       25 return unless defined $dir;
243              
244 11 100       35 unless (exists $self->{PROJECTS}{$dir}) {
245 3         37 $self->{PROJECTS}{$dir} =
246             bless ( {monitor => CSS::Watcher::Monitor->new({dir => $dir})}, 'CSS::Watcher::Project' );
247             }
248 11         62 return $self->{PROJECTS}{$dir};
249             }
250              
251             # Lookup for project dir similar to projectile.el
252             sub get_project_dir {
253 11     11 0 1627 my $self = shift;
254 11         18 my $obj = shift;
255            
256 11 50       175 my $pdir = ! defined ($obj) ? undef:
    100          
    50          
257             (-f $obj) ? path ($obj)->parent :
258             (-d $obj) ? $obj : undef;
259 11 50       305 return unless (defined $pdir);
260              
261 11         33 $pdir = path( $pdir );
262              
263 11         207 foreach (qw/.projectile .csswatcher .git .hg .fslckout .bzr _darcs/) {
264 48 100       1593 if (-e ($pdir->child( $_ ))) {
265 6         247 return $pdir;
266             }
267             }
268 5 50       205 return if ($pdir->is_rootdir());
269             #parent dir
270 5         133 return $self->get_project_dir ($pdir->parent);
271             }
272              
273             1;
274              
275             __END__