Passed
Push — master ( b7b35d...140698 )
by Alexander
02:25
created

Config::getSectionIndex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 1
eloc 2
c 2
b 0
f 1
nc 1
nop 0
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Vasoft\VersionIncrement;
6
7
use Vasoft\VersionIncrement\Commits\CommitCollection;
8
use Vasoft\VersionIncrement\Commits\CommitCollectionFactory;
9
use Vasoft\VersionIncrement\Contract\ChangelogFormatterInterface;
10
use Vasoft\VersionIncrement\Contract\CommitParserInterface;
11
use Vasoft\VersionIncrement\Contract\SectionRuleInterface;
12
use Vasoft\VersionIncrement\Contract\TagFormatterInterface;
13
use Vasoft\VersionIncrement\Contract\VcsExecutorInterface;
14
use Vasoft\VersionIncrement\Core\Sections;
15
use Vasoft\VersionIncrement\Exceptions\UnknownPropertyException;
16
use Vasoft\VersionIncrement\SectionRules\DefaultRule;
17
18
/**
19
 * Class Config.
20
 *
21
 * Represents the configuration for the version increment tool. This class provides methods to configure various
22
 * aspects of the tool, such as sections, rules, version control settings, and formatters. It also manages default
23
 * configurations and ensures consistency across the application.
24
 */
25
final class Config
26
{
27
    private ?CommitCollection $commitCollection = null;
28
    private array $scopes = [];
29
    private array $props = [];
30
    private string $squashedCommitMessage = 'Squashed commit of the following:';
31
    private bool $processDefaultSquashedCommit = false;
32
    private array $minorTypes = [
33
        'feat',
34
    ];
35
    private array $majorTypes = [];
36
    private array $sectionRules = [];
37
    public const DEFAULT_SECTION = 'other';
38
    private string $masterBranch = 'master';
39
    private string $releaseSection = 'chore';
40
    private string $releaseScope = 'release';
41
    private string $aggregateSection = '';
42
    private bool $enabledComposerVersioning = true;
43
    private ?ChangelogFormatterInterface $changelogFormatter = null;
44
    private ?CommitParserInterface $commitParser = null;
45
    private ?VcsExecutorInterface $vcsExecutor = null;
46
    private ?TagFormatterInterface $tagFormatter = null;
47
    private bool $hideDoubles = false;
48
    private bool $ignoreUntrackedFiles = false;
49
    private readonly Sections $sections;
50
51 51
    public function __construct()
52
    {
53 51
        $this->sections = new Sections();
0 ignored issues
show
Bug introduced by
The property sections is declared read-only in Vasoft\VersionIncrement\Config.
Loading history...
54
    }
55
56
    /**
57
     * Sets the sections configuration for the tool.
58
     *
59
     * This method allows you to define a custom set of sections, each with its own title, order, and visibility settings.
60
     * Existing sections are cleared before applying the new configuration. The order of sections is reset, and the default
61
     * section (`other`) will be added automatically if not explicitly defined.
62
     *
63
     * Each section can be configured with the following optional parameters:
64
     * - `title`: The display name of the section in the CHANGELOG (defaults to the section key).
65
     * - `order`: The sorting priority of the section (auto-incremented if not provided).
66
     * - `hidden`: Whether the section should be hidden in the CHANGELOG (defaults to `false`).
67
     *
68
     * @param array $sections an associative array where keys are section codes and values are arrays containing
69
     *                        the section's configuration (`title`, `order`, and `hidden`)
70
     *
71
     * @return $this this Config instance for method chaining
72
     */
73 6
    public function setSections(array $sections): self
74
    {
75 6
        $this->sections->setSections($sections);
76
77 6
        return $this;
78
    }
79
80
    /**
81
     * Sets or updates the configuration for a specific section.
82
     *
83
     * This method allows you to define or modify the settings of a section, such as its title, order, and visibility.
84
     * If the section already exists, its configuration is updated; otherwise, a new section is created. Existing values
85
     * for `order` and `hidden` are preserved unless explicitly overridden.
86
     *
87
     * @param string    $key    the unique identifier (code) of the section
88
     * @param string    $title  the display name of the section in the CHANGELOG
89
     * @param int       $order  The sorting priority of the section. Defaults to `-1`, which preserves the existing order
90
     *                          or assigns a new auto-incremented value if the section does not exist.
91
     * @param null|bool $hidden Whether the section should be hidden in the CHANGELOG. Defaults to `null`, which
92
     *                          preserves the existing visibility setting or sets it to `false` if the section
93
     *                          does not exist.
94
     *
95
     * @return $this this Config instance for method chaining
96
     */
97 3
    public function setSection(
98
        string $key,
99
        string $title,
100
        int $order = -1,
101
        ?bool $hidden = null,
102
    ): self {
103 3
        $this->sections->setSection($key, $title, $order, $hidden);
104
105 3
        return $this;
106
    }
107
108 7
    public function getSections(): Sections
109
    {
110 7
        return $this->sections;
111
    }
112
113
    /**
114
     * Retrieves the commit collection based on the configured sections.
115
     *
116
     * This method creates a `CommitCollection` object by converting the configured sections into `Section` objects.
117
     * It ensures that all sections are sorted and that a default section exists if not explicitly defined.
118
     *
119
     * @return CommitCollection a collection of commits grouped by sections
120
     */
121 24
    public function getCommitCollection(): CommitCollection
122
    {
123 24
        if (null === $this->commitCollection) {
124 24
            $this->commitCollection = (new CommitCollectionFactory(
125 24
                $this,
126 24
                $this->majorTypes,
127 24
                $this->minorTypes,
128 24
                self::DEFAULT_SECTION,
129 24
            ))->getCollection($this->sections->getSortedSections());
130
        }
131
132 24
        return $this->commitCollection;
133
    }
134
135
    /**
136
     * Sets the section to be used for release commits.
137
     *
138
     * This method defines the section that will be associated with release-related commits. The specified section will be
139
     * used when generating release commit messages or determining the scope of a release.
140
     *
141
     * @param string $section The key of the section to be used for releases (e.g., 'release').
142
     *
143
     * @return $this this Config instance for method chaining
144
     */
145 1
    public function setReleaseSection(string $section): self
146
    {
147 1
        $this->releaseSection = $section;
148
149 1
        return $this;
150
    }
151
152
    /**
153
     * Retrieves the key of the section configured for release commits.
154
     *
155
     * This method returns the key of the section that is associated with release-related commits. If the configured
156
     * release section does not exist in the sections list, the default section (`other`) is returned instead.
157
     *
158
     * @return string the key of the release section or the default section if the configured release section is invalid
159
     */
160 22
    public function getReleaseSection(): string
161
    {
162 22
        return $this->sections->exits($this->releaseSection) ? $this->releaseSection : self::DEFAULT_SECTION;
163
    }
164
165
    /**
166
     * Retrieves the name of the main branch in the repository.
167
     *
168
     * By default, it is set to "master".
169
     *
170
     * @return string The name of the main branch (e.g., "main" or "master").
171
     */
172 30
    public function getMasterBranch(): string
173
    {
174 30
        return $this->masterBranch;
175
    }
176
177
    /**
178
     * Sets the name of the main branch in the repository.
179
     *
180
     * This method allows you to configure the name of the main branch (e.g., "main" or "master") used by the tool.
181
     *
182
     * @param string $masterBranch the name of the main branch
183
     *
184
     * @return $this this Config instance for method chaining
185
     */
186 4
    public function setMasterBranch(string $masterBranch): self
187
    {
188 4
        $this->masterBranch = $masterBranch;
189
190 4
        return $this;
191
    }
192
193
    /**
194
     * Sets the types of changes that trigger a minor version increment.
195
     *
196
     * This method configures the list of commit types that, when present, will cause the minor version to be
197
     * incremented during version updates.
198
     *
199
     * @param array $minorTypes An array of commit type codes (e.g., ['feat', 'fix']).
200
     *
201
     * @return $this this Config instance for method chaining
202
     */
203 1
    public function setMinorTypes(array $minorTypes): self
204
    {
205 1
        $this->minorTypes = $minorTypes;
206
207 1
        return $this;
208
    }
209
210
    /**
211
     * Sets the types of changes that trigger a major version increment.
212
     *
213
     * This method configures the list of commit types that, when present, will cause the major version to be
214
     * incremented during version updates.
215
     *
216
     * @param array $majorTypes An array of commit type codes (e.g., ['breaking']).
217
     *
218
     * @return $this this Config instance for method chaining
219
     */
220 2
    public function setMajorTypes(array $majorTypes): self
221
    {
222 2
        $this->majorTypes = $majorTypes;
223
224 2
        return $this;
225
    }
226
227
    /**
228
     * Sets the scope to be used for release commit messages.
229
     *
230
     * This method defines the scope that will be included in the description of release-related commits.
231
     * If an empty string is provided, no scope will be added to the release commit message.
232
     *
233
     * @param string $releaseScope The scope to be used for release commits (e.g., 'rel').
234
     *                             Use an empty string to omit the scope from the commit message.
235
     *
236
     * @return $this this Config instance for method chaining
237
     */
238 3
    public function setReleaseScope(string $releaseScope): self
239
    {
240 3
        $this->releaseScope = $releaseScope;
241
242 3
        return $this;
243
    }
244
245
    /**
246
     * Retrieves the scope configured for release commit messages.
247
     *
248
     * This method returns the scope that will be included in the description of release-related commits.
249
     * If no scope is configured, an empty string is returned, indicating that no scope will be added to the commit message.
250
     *
251
     * @return string the scope for release commit messages, or an empty string if no scope is configured
252
     */
253 21
    public function getReleaseScope(): string
254
    {
255 21
        return $this->releaseScope;
256
    }
257
258
    /**
259
     * Enables or disables ignoring untracked files in the repository.
260
     *
261
     * This method configures whether untracked files should be ignored when running the version increment tool.
262
     * By default, untracked files are not ignored, and their presence may cause the tool to fail.
263
     *
264
     * @param bool $ignoreUntrackedFiles Whether to ignore untracked files:
265
     *                                   - `true`: Ignore untracked files.
266
     *                                   - `false`: Do not ignore untracked files (default behavior).
267
     *
268
     * @return $this this Config instance for method chaining
269
     */
270 1
    public function setIgnoreUntrackedFiles(bool $ignoreUntrackedFiles): self
271
    {
272 1
        $this->ignoreUntrackedFiles = $ignoreUntrackedFiles;
273
274 1
        return $this;
275
    }
276
277
    /**
278
     * Checks whether untracked files are ignored in the repository.
279
     *
280
     * This method retrieves the current configuration for ignoring untracked files. If enabled, the tool will not
281
     * consider untracked files when performing operations.
282
     *
283
     * @return bool returns `true` if untracked files are ignored, `false` otherwise
284
     */
285 29
    public function mastIgnoreUntrackedFiles(): bool
286
    {
287 29
        return $this->ignoreUntrackedFiles;
288
    }
289
290
    /**
291
     * Adds a rule for determining whether a commit belongs to a specific section.
292
     *
293
     * This method associates a rule with a specific section. The rule is used to evaluate whether a commit should be
294
     * included in the specified section.
295
     *
296
     * @param string               $key  the key of the section
297
     * @param SectionRuleInterface $rule the rule to be added
298
     *
299
     * @return $this this Config instance for method chaining
300
     */
301 3
    public function addSectionRule(string $key, SectionRuleInterface $rule): self
302
    {
303 3
        $this->sectionRules[$key][] = $rule;
304
305 3
        return $this;
306
    }
307
308
    /**
309
     * Retrieves the rules associated with a specific section.
310
     *
311
     * This method returns an array of rules for the specified section. If no custom rules are defined, a default rule
312
     * is automatically added and returned.
313
     *
314
     * @param string $key the key of the section
315
     *
316
     * @return SectionRuleInterface[] an array of rules for the specified section
317
     */
318 24
    public function getSectionRules(string $key): array
319
    {
320 24
        $this->sectionRules[$key]['default'] = new DefaultRule($key);
321
322 24
        return $this->sectionRules[$key];
323
    }
324
325
    /**
326
     * Sets the section to be used for identifying squashed (aggregate) commits.
327
     *
328
     * This method configures the section that will be treated as a marker for squashed commits. When a commit is
329
     * associated with this section, it will be processed as a squashed commit. Squashed commits typically contain
330
     * a summary of multiple commits and are parsed accordingly to extract individual changes.
331
     *
332
     * @param string $aggregateSection The key of the section to be used for identifying squashed commits
333
     *                                 (e.g., 'aggregate').
334
     *
335
     * @return $this this Config instance for method chaining
336
     */
337 1
    public function setAggregateSection(string $aggregateSection): self
338
    {
339 1
        $this->aggregateSection = $aggregateSection;
340
341 1
        return $this;
342
    }
343
344
    /**
345
     * Retrieves the section configured for identifying squashed (aggregate) commits.
346
     *
347
     * This method returns the key of the section that is used to identify squashed commits. If no section has been
348
     * explicitly configured, an empty string is returned, indicating that no specific section is assigned for
349
     * identifying squashed commits.
350
     *
351
     * @return string the key of the section used for identifying squashed commits, or an empty string if no section
352
     *                is configured
353
     */
354 24
    public function getAggregateSection(): string
355
    {
356 24
        return $this->aggregateSection;
357
    }
358
359
    /**
360
     * Sets the commit message template for squashed commits.
361
     *
362
     * This method allows you to customize the message used to identify squashed commits in the repository.
363
     *
364
     * @param string $squashedCommitMessage the custom message template for squashed commits
365
     *
366
     * @return $this this Config instance for method chaining
367
     */
368 1
    public function setSquashedCommitMessage(string $squashedCommitMessage): self
369
    {
370 1
        $this->squashedCommitMessage = $squashedCommitMessage;
371
372 1
        return $this;
373
    }
374
375
    /**
376
     * Retrieves the commit message template for squashed commits.
377
     *
378
     * @return string the message template for squashed commits
379
     */
380 24
    public function getSquashedCommitMessage(): string
381
    {
382 24
        return $this->squashedCommitMessage;
383
    }
384
385
    /**
386
     * Enables or disables processing of default squashed commits.
387
     *
388
     * This method configures whether the tool should process default squashed commits (those matching the default
389
     * message template).
390
     *
391
     * @param bool $processDefaultSquashedCommit whether to enable processing of default squashed commits
392
     *
393
     * @return $this this Config instance for method chaining
394
     */
395 2
    public function setProcessDefaultSquashedCommit(bool $processDefaultSquashedCommit): self
396
    {
397 2
        $this->processDefaultSquashedCommit = $processDefaultSquashedCommit;
398
399 2
        return $this;
400
    }
401
402
    /**
403
     * Checks whether processing of default squashed commits is enabled.
404
     *
405
     * @return bool returns `true` if processing of default squashed commits is enabled, `false` otherwise
406
     */
407 24
    public function shouldProcessDefaultSquashedCommit(): bool
408
    {
409 24
        return $this->processDefaultSquashedCommit;
410
    }
411
412
    /**
413
     * Retrieves the changelog formatter instance.
414
     *
415
     * If no custom changelog formatter is set, a default instance of `DefaultFormatter` is created and configured.
416
     * The formatter's configuration is automatically updated to use the current `Config` instance.
417
     *
418
     * @return ChangelogFormatterInterface the changelog formatter instance
419
     */
420 24
    public function getChangelogFormatter(): ChangelogFormatterInterface
421
    {
422 24
        if (null === $this->changelogFormatter) {
423 22
            $this->changelogFormatter = new Changelog\DefaultFormatter();
424 22
            $this->changelogFormatter->setConfig($this);
425
        }
426
427 24
        return $this->changelogFormatter;
428
    }
429
430
    /**
431
     * Sets a custom changelog formatter for the configuration.
432
     *
433
     * The provided formatter will be used for all changelog-related operations. The formatter's configuration
434
     * is automatically updated to use the current `Config` instance.
435
     *
436
     * @param ChangelogFormatterInterface $changelogFormatter the custom changelog formatter to set
437
     *
438
     * @return $this this Config instance for method chaining
439
     */
440 2
    public function setChangelogFormatter(ChangelogFormatterInterface $changelogFormatter): self
441
    {
442 2
        $this->changelogFormatter = $changelogFormatter;
443 2
        $this->changelogFormatter->setConfig($this);
444
445 2
        return $this;
446
    }
447
448
    /**
449
     * Retrieves the VCS executor instance.
450
     *
451
     * If no custom VCS executor is set, a default instance of `GitExecutor` is created and used.
452
     *
453
     * @return VcsExecutorInterface the VCS executor instance
454
     */
455 31
    public function getVcsExecutor(): VcsExecutorInterface
456
    {
457 31
        if (null === $this->vcsExecutor) {
458 1
            $this->vcsExecutor = new GitExecutor();
459
        }
460
461 31
        return $this->vcsExecutor;
462
    }
463
464
    /**
465
     * Sets a custom VCS executor for the configuration.
466
     *
467
     * The provided executor will be used for all version control system operations.
468
     *
469
     * @param VcsExecutorInterface $vcsExecutor the custom VCS executor to set
470
     *
471
     * @return $this this Config instance for method chaining
472
     */
473 31
    public function setVcsExecutor(VcsExecutorInterface $vcsExecutor): self
474
    {
475 31
        $this->vcsExecutor = $vcsExecutor;
476
477 31
        return $this;
478
    }
479
480
    /**
481
     * Retrieves the commit parser instance.
482
     *
483
     * If no custom commit parser is set, a default instance of `ShortParser` is created and configured.
484
     * The parser's configuration is automatically updated to use the current `Config` instance.
485
     *
486
     * @return CommitParserInterface the commit parser instance
487
     */
488 27
    public function getCommitParser(): CommitParserInterface
489
    {
490 27
        if (null === $this->commitParser) {
491 26
            $this->commitParser = new Commits\ShortParser();
492 26
            $this->commitParser->setConfig($this);
493
        }
494
495 27
        return $this->commitParser;
496
    }
497
498
    /**
499
     * Sets a custom commit parser for the configuration.
500
     *
501
     * The provided parser will be used for all commit parsing operations. The parser's configuration is automatically
502
     * updated to use the current `Config` instance.
503
     *
504
     * @return $this this Config instance for method chaining
505
     */
506 1
    public function setCommitParser(CommitParserInterface $changelogFormatter): self
507
    {
508 1
        $this->commitParser = $changelogFormatter;
509 1
        $this->commitParser->setConfig($this);
510
511 1
        return $this;
512
    }
513
514
    /**
515
     * Retrieves the tag formatter instance.
516
     *
517
     * If no custom tag formatter is set, a default instance of `DefaultFormatter` is created and configured.
518
     *
519
     * @return TagFormatterInterface the tag formatter instance
520
     */
521 6
    public function getTagFormatter(): TagFormatterInterface
522
    {
523 6
        if (null === $this->tagFormatter) {
524 5
            $this->tagFormatter = new Tag\DefaultFormatter();
525 5
            $this->tagFormatter->setConfig($this);
526
        }
527
528 6
        return $this->tagFormatter;
529
    }
530
531
    /**
532
     * Sets a custom tag formatter for the configuration.
533
     *
534
     * The provided formatter will be used for all tag-related operations. The formatter's configuration
535
     * is automatically updated to use the current `Config` instance.
536
     *
537
     * @param TagFormatterInterface $tagFormatter the custom tag formatter to set
538
     *
539
     * @return $this this Config instance for method chaining
540
     */
541 1
    public function setTagFormatter(TagFormatterInterface $tagFormatter): self
542
    {
543 1
        $this->tagFormatter = $tagFormatter;
544 1
        $this->tagFormatter->setConfig($this);
545
546 1
        return $this;
547
    }
548
549
    /**
550
     * Enables or disables version management in the `composer.json` file.
551
     *
552
     * When disabled, version management will rely solely on Git tags instead of updating `composer.json`.
553
     *
554
     * @param bool $enabledComposerVersioning Whether to enable version management in `composer.json`.
555
     *                                        - `true`: Enable version management in `composer.json`.
556
     *                                        - `false`: Disable version management in `composer.json`.
557
     *
558
     * @return $this this Config instance for method chaining
559
     */
560 8
    public function setEnabledComposerVersioning(bool $enabledComposerVersioning): self
561
    {
562 8
        $this->enabledComposerVersioning = $enabledComposerVersioning;
563
564 8
        return $this;
565
    }
566
567
    /**
568
     * Checks whether version management in the `composer.json` file is enabled.
569
     *
570
     * @return bool Returns `true` if version management in `composer.json` is enabled, `false` otherwise.
571
     */
572 29
    public function isEnabledComposerVersioning(): bool
573
    {
574 29
        return $this->enabledComposerVersioning;
575
    }
576
577
    /**
578
     * Sets a custom property in the configuration.
579
     *
580
     * This method allows you to store custom key-value pairs in the configuration. These properties can be used to pass
581
     * additional parameters required by custom implementations (e.g., formatters, VCS executors, parsers, etc.).
582
     *
583
     * @param string $key   The name of the property to set. This should be a unique identifier for the property.
584
     * @param mixed  $value The value to associate with the property. This can be any type of data required by the custom
585
     *                      implementation.
586
     *
587
     * @return $this this Config instance for method chaining
588
     *
589
     * @example
590
     * ```php
591
     * return (new \Vasoft\VersionIncrement\Config())
592
     *     ->set('customParam', 'customValue');
593
     * ```
594
     */
595 5
    public function set(string $key, mixed $value): self
596
    {
597 5
        $this->props[$key] = $value;
598
599 5
        return $this;
600
    }
601
602
    /**
603
     * Retrieves the value of a custom property from the configuration.
604
     *
605
     * This method retrieves the value associated with the specified property key. If the property does not exist,
606
     * an exception is thrown to indicate that the property is unknown.
607
     *
608
     * @param string $key The name of the property to retrieve. This should match the key used when setting the property.
609
     *
610
     * @return mixed the value associated with the property
611
     *
612
     * @throws UnknownPropertyException if the specified property does not exist in the configuration
613
     */
614 6
    public function get(string $key): mixed
615
    {
616 6
        if (!isset($this->props[$key])) {
617 1
            throw new UnknownPropertyException($key);
618
        }
619
620 5
        return $this->props[$key];
621
    }
622
623
    /**
624
     * Enables or disables hiding of duplicate entries within the same section in the CHANGELOG.
625
     *
626
     * This method configures whether duplicate entries (lines with identical content) should be hidden in the generated
627
     * CHANGELOG. When enabled, only the first occurrence of a duplicate entry will be displayed within each section.
628
     *
629
     * @param bool $hideDoubles Whether to hide duplicate entries:
630
     *                          - `true`: Hide duplicate entries within the same section.
631
     *                          - `false`: Display all entries, including duplicates (default behavior).
632
     *
633
     * @return $this this Config instance for method chaining
634
     */
635 1
    public function setHideDoubles(bool $hideDoubles): self
636
    {
637 1
        $this->hideDoubles = $hideDoubles;
638
639 1
        return $this;
640
    }
641
642
    /**
643
     * Checks whether hiding of duplicate entries within the same section is enabled.
644
     *
645
     * This method retrieves the current configuration for hiding duplicate entries in the CHANGELOG. If enabled, duplicate
646
     * entries within the same section will be hidden during the generation of the CHANGELOG.
647
     *
648
     * @return bool returns `true` if hiding of duplicate entries is enabled, `false` otherwise
649
     */
650 24
    public function isHideDoubles(): bool
651
    {
652 24
        return $this->hideDoubles;
653
    }
654
655
    /**
656
     * Adds a human-readable title for a specific scope.
657
     *
658
     * This method associates a scope key with a human-readable title. The title will be used in the CHANGELOG
659
     * instead of the raw scope name when generating commit messages. If the scope already exists, its title
660
     * will be updated.
661
     *
662
     * @param string $key   The scope key (e.g., 'dev', 'deprecated').
663
     * @param string $title The human-readable title for the scope (e.g., 'Development', 'Deprecated Features').
664
     */
665 2
    public function addScope(string $key, string $title): self
666
    {
667 2
        $this->scopes[$key] = $title;
668
669 2
        return $this;
670
    }
671
672
    /**
673
     * Retrieves all configured scopes and their human-readable titles.
674
     *
675
     * This method returns an associative array where keys are scope codes and values are their corresponding
676
     * human-readable titles. These titles are used in the CHANGELOG to replace raw scope names for better readability.
677
     *
678
     * @return array an associative array of scopes, where keys are scope codes and values are their titles
679
     */
680 6
    public function getScopes(): array
681
    {
682 6
        return $this->scopes;
683
    }
684
}
685