1. my class Instant { ... }
  2. my class IO::Path is Cool {
  3. has IO::Spec $.SPEC;
  4. has Str $.CWD;
  5. has Str $.path;
  6. has Bool $!is-absolute;
  7. has Str $!abspath; # should be native for faster file tests, but segfaults
  8. has %!parts;
  9. multi method ACCEPTS(IO::Path:D: IO::Path:D \other) {
  10. nqp::p6bool(nqp::iseq_s($.abspath, nqp::unbox_s(other.abspath)));
  11. }
  12. multi method ACCEPTS(IO::Path:D: Cool:D \that) {
  13. nqp::p6bool(nqp::iseq_s($.abspath,nqp::unbox_s(IO::Path.new(|that).abspath)));
  14. }
  15. submethod BUILD(:$!path!, :$!SPEC!, :$!CWD! --> Nil) {
  16. nqp::unless($!path,
  17. die "Must specify something as a path: did you mean '.' for the current directory?"
  18. )
  19. }
  20. method new-from-absolute-path($path, :$SPEC = $*SPEC, Str() :$CWD = $*CWD) {
  21. method !set-absolute() {
  22. $!is-absolute = True;
  23. $!abspath := $path;
  24. self;
  25. }
  26. self.bless(:$path, :$SPEC, :$CWD)!set-absolute;
  27. }
  28. proto method new(|) {*}
  29. multi method new(IO::Path: Str $path, :$SPEC = $*SPEC, Str:D :$CWD) {
  30. self.bless(:$path, :$SPEC, :$CWD);
  31. }
  32. multi method new(IO::Path: Str $path, :$SPEC = $*SPEC, :$CWD = $*CWD) {
  33. self.bless(:$path, :$SPEC, :CWD($CWD.Str));
  34. }
  35. multi method new(IO::Path: Cool $path, :$SPEC = $*SPEC, :$CWD = $*CWD) {
  36. self.bless(:path($path.Str), :$SPEC, :CWD($CWD.Str));
  37. }
  38. multi method new(IO::Path:
  39. :$basename!,
  40. :$dirname = '',
  41. :$volume = '',
  42. :$SPEC = $*SPEC,
  43. Str() :$CWD = $*CWD,
  44. ) {
  45. self.bless(:path($SPEC.join($volume,$dirname,$basename)),:$SPEC,:$CWD);
  46. }
  47. multi method new(IO::Path:) {
  48. die "Must specify something as a path: did you mean '.' for the current directory?";
  49. }
  50. method abspath() {
  51. $!abspath //= $!SPEC.rel2abs($!path,$!CWD);
  52. }
  53. method is-absolute() {
  54. $!is-absolute //= $!SPEC.is-absolute($!path);
  55. }
  56. method is-relative() {
  57. !( $!is-absolute //= $!SPEC.is-absolute($!path) );
  58. }
  59. method parts {
  60. %!parts ||= $!SPEC.split($!path);
  61. }
  62. method volume(IO::Path:D:) { %.parts<volume> }
  63. method dirname(IO::Path:D:) { %.parts<dirname> }
  64. method basename(IO::Path:D:) { %.parts<basename> }
  65. method extension(IO::Path:D:) { Rakudo::Internals.MAKE-EXT(self.basename) }
  66. # core can't do 'basename handles <Numeric Int>'
  67. method Numeric(IO::Path:D:) { self.basename.Numeric }
  68. method Int (IO::Path:D:) { self.basename.Int }
  69. multi method Str (IO::Path:D:) { $!path }
  70. multi method gist(IO::Path:D:) {
  71. $!is-absolute
  72. ?? qq|"$.abspath".IO|
  73. !! qq|"$.path".IO|
  74. }
  75. multi method perl(IO::Path:D:) {
  76. $!is-absolute # attribute now set
  77. ?? "{$.abspath.perl}.IO({:$!SPEC.perl})"
  78. !! "{$.path.perl}.IO({:$!SPEC.perl},{:$!CWD.perl})"
  79. }
  80. method succ(IO::Path:D:) {
  81. self.bless(
  82. :path($!SPEC.join($.volume,$.dirname,$.basename.succ)),
  83. :$!SPEC,
  84. :$!CWD,
  85. );
  86. }
  87. method pred(IO::Path:D:) {
  88. self.bless(
  89. :path($!SPEC.join($.volume,$.dirname,$.basename.pred)),
  90. :$!SPEC,
  91. :$!CWD,
  92. );
  93. }
  94. method IO(IO::Path:D: |c) { self }
  95. method open(IO::Path:D: |c) {
  96. my $handle = IO::Handle.new(:path(self));
  97. $handle // $handle.throw;
  98. $handle.open(|c);
  99. }
  100. method watch(IO::Path:D:) {
  101. IO::Notification.watch-path($.abspath);
  102. }
  103. proto method absolute(|) { * }
  104. multi method absolute (IO::Path:D:) { $.abspath }
  105. multi method absolute (IO::Path:D: $CWD) {
  106. self.is-absolute
  107. ?? $.abspath
  108. !! $!SPEC.rel2abs($!path, $CWD);
  109. }
  110. method relative (IO::Path:D: $CWD = $*CWD) {
  111. $!SPEC.abs2rel($.abspath, $CWD);
  112. }
  113. method cleanup (IO::Path:D:) {
  114. self.bless(:path($!SPEC.canonpath($!path)), :$!SPEC, :$!CWD);
  115. }
  116. method resolve (IO::Path:D:) {
  117. # XXXX: Not portable yet; assumes POSIX semantics
  118. my int $max-depth = 256;
  119. my str $sep = $!SPEC.dir-sep;
  120. my str $cur = $!SPEC.curdir;
  121. my str $up = $!SPEC.updir;
  122. my str $empty = '';
  123. my str $resolved = $empty;
  124. my Mu $res-list := nqp::list_s();
  125. my Mu $parts := nqp::split($sep, nqp::unbox_s(self.absolute));
  126. while $parts {
  127. fail "Resolved path too deep!"
  128. if $max-depth < nqp::elems($res-list) + nqp::elems($parts);
  129. # Grab next unprocessed part, check for '', '.', '..'
  130. my str $part = nqp::shift($parts);
  131. next if nqp::iseq_s($part, $empty) || nqp::iseq_s($part, $cur);
  132. if nqp::iseq_s($part, $up) {
  133. next unless $res-list;
  134. nqp::pop_s($res-list);
  135. $resolved = $res-list ?? $sep ~ nqp::join($sep, $res-list)
  136. !! $empty;
  137. next;
  138. }
  139. # Normal part, set as next path to test
  140. my str $next = nqp::concat($resolved, nqp::concat($sep, $part));
  141. # Path part doesn't exist; handle rest in non-resolving mode
  142. if !nqp::stat($next, nqp::const::STAT_EXISTS) {
  143. $resolved = $next;
  144. while $parts {
  145. $part = nqp::shift($parts);
  146. next if nqp::iseq_s($part, $empty) || nqp::iseq_s($part, $cur);
  147. $resolved = nqp::concat($resolved, nqp::concat($sep, $part));
  148. }
  149. }
  150. # Symlink; read it and act on absolute or relative link
  151. elsif nqp::fileislink($next) {
  152. my str $link = nqp::readlink($next);
  153. my Mu $link-parts := nqp::split($sep, $link);
  154. next unless $link-parts;
  155. # Symlink to absolute path
  156. if nqp::iseq_s($link-parts[0], $empty) {
  157. $resolved = nqp::shift($link-parts);
  158. $res-list := nqp::list_s();
  159. }
  160. nqp::unshift($parts, nqp::pop($link-parts))
  161. while $link-parts;
  162. }
  163. # Just a plain old path part, so append it and go on
  164. else {
  165. $resolved = $next;
  166. nqp::push_s($res-list, $part);
  167. }
  168. }
  169. $resolved = $sep unless nqp::chars($resolved);
  170. IO::Path.new-from-absolute-path($resolved,:$!SPEC,:CWD(self));
  171. }
  172. method parent(IO::Path:D:) { # XXX needs work
  173. my $curdir := $!SPEC.curdir;
  174. my $updir := $!SPEC.updir;
  175. if self.is-absolute {
  176. return self.bless(
  177. :path($!SPEC.join($.volume, $.dirname, '')),
  178. :$!SPEC,
  179. :$!CWD,
  180. );
  181. }
  182. elsif $.dirname eq $curdir and $.basename eq $curdir {
  183. return self.bless(
  184. :path($!SPEC.join($.volume,$curdir,$updir)),
  185. :$!SPEC,
  186. :$!CWD,
  187. );
  188. }
  189. elsif $.dirname eq $curdir && $.basename eq $updir
  190. or !grep({$_ ne $updir}, $!SPEC.splitdir($.dirname)) {
  191. return self.bless( # If all updirs, then add one more
  192. :path($!SPEC.join($.volume,$!SPEC.catdir($.dirname,$updir),$.basename)),
  193. :$!SPEC,
  194. :$!CWD,
  195. );
  196. }
  197. else {
  198. return self.bless(
  199. :path($!SPEC.join($.volume, $.dirname, '')),
  200. :$!SPEC,
  201. :$!CWD,
  202. );
  203. }
  204. }
  205. method child (IO::Path:D: \child) {
  206. self.bless(
  207. :path( $!SPEC.join('', $!path,
  208. nqp::if(nqp::istype(child, Str), child, child.Str)
  209. )),
  210. :$!SPEC, :$!CWD
  211. );
  212. }
  213. proto method chdir(|) { * }
  214. multi method chdir(IO::Path:D: Str() $path is copy, :$test = 'r') {
  215. if !$!SPEC.is-absolute($path) {
  216. my ($volume,$dirs) = $!SPEC.splitpath(self.abspath, :nofile);
  217. my @dirs = $!SPEC.splitdir($dirs);
  218. @dirs.shift; # the first is always empty for absolute dirs
  219. for $!SPEC.splitdir($path) -> $dir {
  220. if $dir eq '..' {
  221. @dirs.pop if @dirs;
  222. }
  223. elsif $dir ne '.' {
  224. @dirs.push: $dir;
  225. }
  226. }
  227. @dirs.push('') if !@dirs; # need at least the rootdir
  228. $path = join($!SPEC.dir-sep, $volume, @dirs);
  229. }
  230. my $dir = IO::Path.new-from-absolute-path($path,:$!SPEC,:CWD(self));
  231. # basic sanity
  232. unless $dir.d {
  233. fail X::IO::Chdir.new(
  234. :$path,
  235. :os-error( $dir.e
  236. ?? "is not a directory"
  237. !! "does not exist"),
  238. );
  239. }
  240. if $test eq 'r' {
  241. return $dir if $dir.r;
  242. }
  243. elsif $test eq 'r w' {
  244. return $dir if $dir.r and $dir.w;
  245. }
  246. elsif $test eq 'r w x' {
  247. return $dir if $dir.r and $dir.w and $dir.x;
  248. }
  249. fail X::IO::Chdir.new(
  250. :$path,
  251. :os-error("did not pass 'd $test' test"),
  252. );
  253. }
  254. proto method rename(|) { * }
  255. multi method rename(IO::Path:D: IO::Path:D $to, :$createonly) {
  256. if $createonly and $to.e {
  257. fail X::IO::Rename.new(
  258. :from($.abspath),
  259. :$to,
  260. :os-error(':createonly specified and destination exists'),
  261. );
  262. }
  263. nqp::rename($.abspath, nqp::unbox_s($to.abspath));
  264. CATCH { default {
  265. fail X::IO::Rename.new(
  266. :from($!abspath), :to($to.abspath), :os-error(.Str) );
  267. } }
  268. True;
  269. }
  270. multi method rename(IO::Path:D: $to, :$CWD = $*CWD, |c) {
  271. self.rename($to.IO(:$!SPEC,:$CWD),|c);
  272. }
  273. proto method copy(|) { * }
  274. multi method copy(IO::Path:D: IO::Path:D $to, :$createonly) {
  275. if $createonly and $to.e {
  276. fail X::IO::Copy.new(
  277. :from($.abspath),
  278. :$to,
  279. :os-error(':createonly specified and destination exists'),
  280. );
  281. }
  282. nqp::copy($.abspath, nqp::unbox_s($to.abspath));
  283. CATCH { default {
  284. fail X::IO::Copy.new(
  285. :from($!abspath), :$to, :os-error(.Str) );
  286. } }
  287. True;
  288. }
  289. multi method copy(IO::Path:D: $to, :$CWD = $*CWD, |c) {
  290. self.copy($to.IO(:$!SPEC,:$CWD),|c);
  291. }
  292. method move(IO::Path:D: |c) {
  293. my $result = self.copy(|c);
  294. fail X::IO::Move.new(
  295. :from($result.exception.from),
  296. :to($result.exception.to),
  297. :os-error($result.exception.os-error),
  298. ) unless $result.defined;
  299. $result = self.unlink();
  300. fail X::IO::Move.new(
  301. :from($result.exception.from),
  302. :to($result.exception.to),
  303. :os-error($result.exception.os-error),
  304. ) unless $result.defined;
  305. True
  306. }
  307. method chmod(IO::Path:D: Int() $mode) {
  308. nqp::chmod($.abspath, nqp::unbox_i($mode));
  309. CATCH { default {
  310. fail X::IO::Chmod.new(
  311. :path($!abspath), :$mode, :os-error(.Str) );
  312. } }
  313. True;
  314. }
  315. method unlink(IO::Path:D:) {
  316. nqp::unlink($.abspath);
  317. CATCH { default {
  318. fail X::IO::Unlink.new( :path($!abspath), os-error => .Str );
  319. } }
  320. True;
  321. }
  322. method symlink(IO::Path:D: $name is copy, :$CWD = $*CWD) {
  323. $name = $name.IO(:$!SPEC,:$CWD).path;
  324. nqp::symlink(nqp::unbox_s($name), $.abspath);
  325. CATCH { default {
  326. fail X::IO::Symlink.new(:target($!abspath), :$name, os-error => .Str);
  327. } }
  328. True;
  329. }
  330. method link(IO::Path:D: $name is copy, :$CWD = $*CWD) {
  331. $name = $name.IO(:$!SPEC,:$CWD).path;
  332. nqp::link(nqp::unbox_s($name), $.abspath);
  333. CATCH { default {
  334. fail X::IO::Link.new(:target($!abspath), :$name, os-error => .Str);
  335. } }
  336. True;
  337. }
  338. method mkdir(IO::Path:D: $mode = 0o777) {
  339. nqp::mkdir($.abspath, $mode);
  340. CATCH { default {
  341. fail X::IO::Mkdir.new(:path($!abspath), :$mode, os-error => .Str);
  342. } }
  343. True;
  344. }
  345. method rmdir(IO::Path:D:) {
  346. nqp::rmdir($.abspath);
  347. CATCH { default {
  348. fail X::IO::Rmdir.new(:path($!abspath), os-error => .Str);
  349. } }
  350. True;
  351. }
  352. proto method dir(|) {*} # make it possible to augment with multies from modulespace
  353. multi method dir(IO::Path:D:
  354. Mu :$test = $*SPEC.curupdir,
  355. :$absolute,
  356. :$Str,
  357. :$CWD = $*CWD,
  358. ) {
  359. CATCH { default {
  360. fail X::IO::Dir.new(
  361. :path($.abspath), :os-error(.Str) );
  362. } }
  363. my str $dir-sep = $!SPEC.dir-sep;
  364. my int $relative = !$absolute && !$.is-absolute;
  365. my str $abspath = $.abspath.ends-with($dir-sep)
  366. ?? $.abspath
  367. !! $.abspath ~ $dir-sep;
  368. my str $path = $!path eq '.' || $!path eq $dir-sep
  369. ?? ''
  370. !! $!path.ends-with($dir-sep)
  371. ?? $!path
  372. !! $!path ~ $dir-sep;
  373. my Mu $dirh := nqp::opendir(nqp::unbox_s($.abspath));
  374. gather {
  375. nqp::until(
  376. nqp::isnull_s(my str $str_elem = nqp::nextfiledir($dirh))
  377. || nqp::iseq_i(nqp::chars($str_elem),0),
  378. nqp::if(
  379. $test.ACCEPTS($str_elem),
  380. nqp::if(
  381. $Str,
  382. (take
  383. nqp::concat(nqp::if($relative,$path,$abspath),$str_elem)),
  384. nqp::if(
  385. $relative,
  386. (take IO::Path.new(
  387. nqp::concat($path,$str_elem),:$!SPEC,:$CWD)),
  388. (take IO::Path.new-from-absolute-path(
  389. nqp::concat($abspath,$str_elem),:$!SPEC,:$CWD))
  390. )
  391. )
  392. )
  393. );
  394. nqp::closedir($dirh);
  395. }
  396. }
  397. proto method slurp() { * }
  398. multi method slurp(IO::Path:D:) {
  399. # clean call, try the fast way
  400. if nqp::iseq_i(nqp::elems(nqp::getattr(%_,Map,'$!storage')),0)
  401. && nqp::open(self.abspath,"r") -> $PIO {
  402. LEAVE nqp::closefh(nqp::decont($PIO));
  403. nqp::p6box_s(nqp::readallfh(nqp::decont($PIO)))
  404. }
  405. # need to do the slow way
  406. else {
  407. my $handle = self.open;
  408. $handle // $handle.throw;
  409. LEAVE $handle.close;
  410. my Mu $PIO := nqp::getattr(nqp::decont($handle),IO::Handle,'$!PIO');
  411. if %_<bin> {
  412. my $res;
  413. # normal file
  414. if Rakudo::Internals.FILETEST-S(self.abspath) -> int $size {
  415. $res := nqp::readfh($PIO,buf8.new,$size)
  416. }
  417. # spooky file with zero size?
  418. else {
  419. $res := buf8.new();
  420. loop {
  421. my $buf := nqp::readfh($PIO,buf8.new,0x100000);
  422. last unless nqp::elems($buf);
  423. $res.append($buf);
  424. }
  425. }
  426. $res
  427. }
  428. else {
  429. $handle.encoding($_) with %_<enc>;
  430. nqp::p6box_s(nqp::readallfh($PIO))
  431. }
  432. }
  433. }
  434. method !spurt($contents, :$enc, :$append, :$createonly, :$bin, |c) {
  435. my $mode = $createonly ?? :x !! $append ?? :a !! :w;
  436. my $handle = self.open(:enc($enc // 'utf8'), :$bin, |$mode, |c);
  437. $handle // $handle.throw;
  438. my $spurt := $bin
  439. ?? $handle.write($contents)
  440. !! $handle.print($contents);
  441. $handle.close; # can't use LEAVE in settings :-(
  442. $spurt;
  443. }
  444. proto method spurt(|) { * }
  445. multi method spurt(IO::Path:D: Blob $contents, :$bin, |c) {
  446. self!spurt($contents, :bin, |c );
  447. }
  448. multi method spurt(IO::Path:D: Cool $contents, :$bin, |c) {
  449. self!spurt($contents, :!bin, |c );
  450. }
  451. proto method lines(|) { * }
  452. multi method lines(IO::Path:D: $limit = Whatever, |c) {
  453. my $handle = self.open(|c);
  454. LEAVE $handle.close;
  455. my $buf := nqp::create(IterationBuffer);
  456. nqp::istype($limit,Whatever) || $limit == Inf
  457. ?? $handle.iterator.push-all($buf)
  458. !! $handle.iterator.push-exactly($buf,$limit.Int);
  459. Seq.new(Rakudo::Iterator.ReifiedList($buf))
  460. }
  461. proto method comb(|) { * }
  462. multi method comb(IO::Path:D: Cool:D $comber = "", |c) {
  463. self.open(|c).comb($comber, :close);
  464. }
  465. multi method comb(IO::Path:D: Int:D $size, |c) {
  466. self.open(|c).comb($size, :close);
  467. }
  468. multi method comb(IO::Path:D: Regex:D $comber, |c) {
  469. self.open(|c).comb($comber, :close);
  470. }
  471. multi method split(IO::Path:D: Str:D $splitter = "", |c) {
  472. self.open(|c).split($splitter, :close);
  473. }
  474. multi method split(IO::Path:D: Regex:D $splitter, |c) {
  475. self.open(|c).split($splitter, :close);
  476. }
  477. proto method words(|) { * }
  478. multi method words(IO::Path:D: |c) {
  479. self.open(|c).words(:close);
  480. }
  481. method e(--> Bool:D) {
  482. ?Rakudo::Internals.FILETEST-E($.abspath) # must be $.abspath
  483. }
  484. method d(--> Bool:D) {
  485. $.e
  486. ?? ?Rakudo::Internals.FILETEST-D($!abspath)
  487. !! Failure.new(X::IO::DoesNotExist.new(:path(~self),:trying<d>))
  488. }
  489. method f(--> Bool:D) {
  490. $.e
  491. ?? ?Rakudo::Internals.FILETEST-F($!abspath)
  492. !! Failure.new(X::IO::DoesNotExist.new(:path(~self),:trying<f>))
  493. }
  494. method s(--> Int:D) {
  495. $.e
  496. ?? Rakudo::Internals.FILETEST-S($!abspath)
  497. !! Failure.new(X::IO::DoesNotExist.new(:path(~self),:trying<s>))
  498. }
  499. method l(--> Bool:D) {
  500. ?Rakudo::Internals.FILETEST-LE($.abspath)
  501. ?? ?Rakudo::Internals.FILETEST-L($!abspath)
  502. !! Failure.new(X::IO::DoesNotExist.new(:path(~self),:trying<l>))
  503. }
  504. method r(--> Bool:D) {
  505. $.e
  506. ?? ?Rakudo::Internals.FILETEST-R($!abspath)
  507. !! Failure.new(X::IO::DoesNotExist.new(:path(~self),:trying<r>))
  508. }
  509. method w(--> Bool:D) {
  510. $.e
  511. ?? ?Rakudo::Internals.FILETEST-W($!abspath)
  512. !! Failure.new(X::IO::DoesNotExist.new(:path(~self),:trying<w>))
  513. }
  514. method rw(--> Bool:D) {
  515. $.e
  516. ?? ?Rakudo::Internals.FILETEST-RW($!abspath)
  517. !! Failure.new(X::IO::DoesNotExist.new(:path(~self),:trying<rw>))
  518. }
  519. method x(--> Bool:D) {
  520. $.e
  521. ?? ?Rakudo::Internals.FILETEST-X($!abspath)
  522. !! Failure.new(X::IO::DoesNotExist.new(:path(~self),:trying<x>))
  523. }
  524. method rwx(--> Bool:D) {
  525. $.e
  526. ?? ?Rakudo::Internals.FILETEST-RWX($!abspath)
  527. !! Failure.new(X::IO::DoesNotExist.new(:path(~self),:trying<rwx>))
  528. }
  529. method z(--> Bool:D) {
  530. $.e
  531. ?? $.f
  532. ?? ?Rakudo::Internals.FILETEST-Z($!abspath)
  533. !! Failure.new( X::IO::NotAFile.new(:path(~self),:trying<z>))
  534. !! Failure.new(X::IO::DoesNotExist.new(:path(~self),:trying<z>))
  535. }
  536. method modified(--> Instant:D) {
  537. $.e
  538. ?? Instant.from-posix(Rakudo::Internals.FILETEST-MODIFIED($!abspath))
  539. !! Failure.new(X::IO::DoesNotExist.new(:path(~self),:trying<modified>))
  540. }
  541. method accessed(--> Instant:D) {
  542. $.e
  543. ?? Instant.from-posix(Rakudo::Internals.FILETEST-ACCESSED($!abspath))
  544. !! Failure.new(X::IO::DoesNotExist.new(:path(~self),:trying<accessed>))
  545. }
  546. method changed(--> Instant:D) {
  547. $.e
  548. ?? Instant.from-posix(Rakudo::Internals.FILETEST-CHANGED($!abspath))
  549. !! Failure.new(X::IO::DoesNotExist.new(:path(~self),:trying<changed>))
  550. }
  551. method mode(--> IntStr:D) {
  552. $.e
  553. ?? nqp::stmts(
  554. (my int $mode = nqp::stat($!abspath, nqp::const::STAT_PLATFORM_MODE) +& 0o7777),
  555. IntStr.new($mode, sprintf('%04o', $mode))
  556. )
  557. !! Failure.new(X::IO::DoesNotExist.new(:path(~self),:trying<mode>))
  558. }
  559. }
  560. my class IO::Path::Cygwin is IO::Path {
  561. method new(|c) { IO::Path.new(|c, :SPEC(IO::Spec::Cygwin) ) }
  562. }
  563. my class IO::Path::QNX is IO::Path {
  564. method new(|c) { IO::Path.new(|c, :SPEC(IO::Spec::QNX) ) }
  565. }
  566. my class IO::Path::Unix is IO::Path {
  567. method new(|c) { IO::Path.new(|c, :SPEC(IO::Spec::Unix) ) }
  568. }
  569. my class IO::Path::Win32 is IO::Path {
  570. method new(|c) { IO::Path.new(|c, :SPEC(IO::Spec::Win32) ) }
  571. }