Skip to content

Commit 75f1bc6

Browse files
committed
feat: add config manager
- Add `ConfigManager` to load config from `.ai-commit.json` file.
1 parent ccd3225 commit 75f1bc6

File tree

11 files changed

+384
-468
lines changed

11 files changed

+384
-468
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ package-lock.json
2222
mutation-report.html
2323
storage/logs/laravel.log
2424
.env
25-
.ai-commit.php
25+
/.ai-commit.*

app/Commands/CommitCommand.php

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use App\Contracts\OutputAwareContract;
1717
use App\Exceptions\TaskException;
1818
use App\GeneratorManager;
19-
use App\Mark;
2019
use Composer\Console\Input\InputOption;
2120
use Illuminate\Console\Scheduling\Schedule;
2221
use Illuminate\Contracts\Config\Repository;
@@ -40,7 +39,7 @@ class CommitCommand extends Command
4039
// {--diff-options=* : Append options for the `git diff` command <comment>[default: ":!*.lock"]</comment>
4140
// {--generator=openai : Specify generator
4241
// {--num=3 : Specify number of generated messages
43-
// {--template= : Specify template of messages generated
42+
// {--prompt= : Specify prompt name of messages generated
4443
';
4544

4645
protected $description = 'Automagically generate commit messages with AI.';
@@ -52,14 +51,15 @@ class CommitCommand extends Command
5251
*/
5352
protected function configure()
5453
{
55-
$config = resolve(Repository::class);
54+
/** @var \App\ConfigManager $config */
55+
$config = resolve(Repository::class)->get('ai-commit');
5656

5757
$this->setDefinition([
58-
new InputOption('commit-options', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Append options for the `git commit` command', $config->get('ai-commit.commit-options')),
59-
new InputOption('diff-options', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Append options for the `git diff` command', $config->get('ai-commit.diff-options')),
60-
new InputOption('generator', '', InputOption::VALUE_REQUIRED, 'Specify generator', $config->get('ai-commit.generator')),
61-
new InputOption('num', '', InputOption::VALUE_REQUIRED, 'Specify number of generated messages', $config->get('ai-commit.num')),
62-
new InputOption('template', '', InputOption::VALUE_REQUIRED, 'Specify template of messages generated', $config->get('ai-commit.template')),
58+
new InputOption('commit-options', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Append options for the `git commit` command', $config->get('commit_options')),
59+
new InputOption('diff-options', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Append options for the `git diff` command', $config->get('diff_options')),
60+
new InputOption('generator', '', InputOption::VALUE_REQUIRED, 'Specify generator name', $config->get('generator')),
61+
new InputOption('num', '', InputOption::VALUE_REQUIRED, 'Specify number of generated messages', $config->get('num')),
62+
new InputOption('prompt', '', InputOption::VALUE_REQUIRED, 'Specify prompt name of messages generated', $config->get('prompt')),
6363
]);
6464
}
6565

@@ -124,17 +124,12 @@ public function handle()
124124

125125
protected function getDiffCommand(): array
126126
{
127-
return array_merge(
128-
['git', 'diff', '--staged'],
129-
$this->option('diff-options') ?: $this->laravel->get('config')->get('ai-commit.diff-options')
130-
);
127+
return array_merge(['git', 'diff', '--staged'], $this->option('diff-options'));
131128
}
132129

133130
protected function getGenerator(): GeneratorContract
134131
{
135-
$generator = $this->laravel->get(GeneratorManager::class)->driver(
136-
$this->option('generator') ?: $this->laravel->get('config')->get('ai-commit.generator')
137-
);
132+
$generator = $this->laravel->get(GeneratorManager::class)->driver($this->option('generator'));
138133

139134
return tap($generator, function (GeneratorContract $generator) {
140135
$generator instanceof OutputAwareContract and $generator->setOutput($this->output);
@@ -153,17 +148,20 @@ protected function getCommitCommand(array $commitMessage): array
153148
->pipe(function (Collection $collection): array {
154149
return array_merge(
155150
['git', 'commit', '--message', $collection->implode(str_repeat(PHP_EOL, 2))],
156-
$this->option('commit-options') ?: $this->laravel->get('config')->get('ai-commit.commit-options')
151+
$this->option('commit-options')
157152
);
158153
});
159154
}
160155

161156
protected function getPromptOfAI(string $stagedDiff): string
162157
{
163-
return str($this->option('template') ?: $this->laravel->get('config')->get('ai-commit.template'))
158+
/** @var \App\ConfigManager $config */
159+
$config = $this->laravel->get('config')->get('ai-commit');
160+
161+
return str($config->get("prompts.{$this->option('prompt')}"))
164162
->replace(
165-
[Mark::DIFF, Mark::NUM],
166-
[$stagedDiff, $this->option('num') ?: $this->laravel->get('config')->get('ai-commit.num')]
163+
[$config->get('diff_mark'), $config->get('num_mark')],
164+
[$stagedDiff, $this->option('num')]
167165
)
168166
->when($this->option('verbose'), function (Stringable $diff) {
169167
$this->line('');

app/Config.php

Lines changed: 0 additions & 17 deletions
This file was deleted.

app/ConfigFinder.php

Lines changed: 0 additions & 17 deletions
This file was deleted.

app/ConfigManager.php

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of the guanguans/ai-commit.
7+
*
8+
* (c) guanguans <ityaozm@gmail.com>
9+
*
10+
* This source file is subject to the MIT license that is bundled.
11+
*/
12+
13+
namespace App;
14+
15+
use App\Exceptions\InvalidJsonFileException;
16+
use Illuminate\Config\Repository;
17+
use Illuminate\Contracts\Support\Arrayable;
18+
use Illuminate\Contracts\Support\Jsonable;
19+
use Illuminate\Support\Collection;
20+
use InvalidArgumentException;
21+
use Iterator;
22+
use JsonSerializable;
23+
use Stringable;
24+
25+
/**
26+
* @template TKey of array-key
27+
* @template TValue
28+
*
29+
* @see https://github.com/hassankhan/config
30+
*/
31+
class ConfigManager extends Repository implements Arrayable, Jsonable, JsonSerializable, Stringable, Iterator
32+
{
33+
final public static function load(): void
34+
{
35+
resolve('config')->set('ai-commit', self::create());
36+
}
37+
38+
final public static function create(): self
39+
{
40+
$files = [
41+
config_path('ai-commit.php'),
42+
windows_os() ? sprintf('C:\\Users\\%s\\.ai-commit.json', get_current_user()) : sprintf('%s/.ai-commit.json', exec('cd ~; pwd')),
43+
getcwd().DIRECTORY_SEPARATOR.'.ai-commit.json',
44+
];
45+
46+
return static::createFrom(...array_filter($files, 'file_exists'));
47+
}
48+
49+
public static function createFrom(...$files): self
50+
{
51+
$config = array_reduce($files, function (array $items, string $file): array {
52+
$ext = str(pathinfo($file, PATHINFO_EXTENSION));
53+
54+
if ($ext->is('php')) {
55+
$items[] = require $file;
56+
57+
return $items;
58+
}
59+
60+
if ($ext->is('json')) {
61+
$config = json_decode(file_get_contents($file), true);
62+
if (JSON_ERROR_NONE !== json_last_error()) {
63+
throw InvalidJsonFileException::make($file);
64+
}
65+
66+
$items[] = $config;
67+
68+
return $items;
69+
}
70+
71+
throw new InvalidArgumentException("Invalid argument type: `$ext`.");
72+
}, []);
73+
74+
return new static(array_replace_recursive(...$config));
75+
}
76+
77+
/**
78+
* @return $this
79+
*/
80+
public function merge(array $items): self
81+
{
82+
$this->items = array_replace_recursive($this->items, $items);
83+
84+
return $this;
85+
}
86+
87+
/**
88+
* Collect the values into a collection.
89+
*
90+
* @return \Illuminate\Support\Collection<TKey, TValue>
91+
*/
92+
public function collect(): Collection
93+
{
94+
return new Collection($this->all());
95+
}
96+
97+
/**
98+
* Get the collection of items as a plain array.
99+
*
100+
* @return array
101+
*/
102+
public function toArray()
103+
{
104+
return array_map(function ($value) {
105+
return $value instanceof Arrayable ? $value->toArray() : $value;
106+
}, $this->all());
107+
}
108+
109+
/**
110+
* Convert the object into something JSON serializable.
111+
*
112+
* @return array<TKey, mixed>
113+
*/
114+
public function jsonSerialize(): array
115+
{
116+
return array_map(function ($value) {
117+
if ($value instanceof JsonSerializable) {
118+
return $value->jsonSerialize();
119+
} elseif ($value instanceof Jsonable) {
120+
return json_decode($value->toJson(), true);
121+
} elseif ($value instanceof Arrayable) {
122+
return $value->toArray();
123+
}
124+
125+
return $value;
126+
}, $this->all());
127+
}
128+
129+
/**
130+
* Get the collection of items as JSON.
131+
*
132+
* @param int $options
133+
*
134+
* @return string
135+
*/
136+
public function toJson($options = 0)
137+
{
138+
return json_encode($this->jsonSerialize(), $options);
139+
}
140+
141+
/**
142+
* Convert the collection to its string representation.
143+
*
144+
* @noinspection DebugFunctionUsageInspection
145+
*/
146+
public function toString(string $type = 'json'): string
147+
{
148+
if (str($type)->is('json')) {
149+
return $this->toJson(JSON_PRETTY_PRINT);
150+
}
151+
152+
if (str($type)->is('php')) {
153+
return var_export($this->toArray(), true);
154+
}
155+
156+
throw new InvalidArgumentException("Invalid argument type: `$type`.");
157+
}
158+
159+
public function toCwd()
160+
{
161+
$file = getcwd().DIRECTORY_SEPARATOR.'.ai-commit.json';
162+
163+
return $this->toFile($file);
164+
}
165+
166+
public function toGlobal()
167+
{
168+
$file = windows_os() ? sprintf('C:\\Users\\%s\\.ai-commit.json', get_current_user()) : sprintf('%s/.ai-commit.json', exec('cd ~; pwd'));
169+
170+
return $this->toFile($file);
171+
}
172+
173+
public function toFile(string $file)
174+
{
175+
$type = pathinfo($file, PATHINFO_EXTENSION);
176+
177+
return file_put_contents($file, $this->toString($type));
178+
}
179+
180+
/**
181+
* Convert the collection to its string representation.
182+
*
183+
* @return string
184+
*/
185+
public function __toString()
186+
{
187+
return $this->toJson();
188+
}
189+
190+
/**
191+
* Iterator Methods.
192+
*/
193+
194+
/**
195+
* Returns the data array element referenced by its internal cursor.
196+
*
197+
* @return mixed The element referenced by the data array's internal cursor.
198+
* If the array is empty or there is no element at the cursor, the
199+
* function returns false. If the array is undefined, the function
200+
* returns null
201+
*/
202+
#[\ReturnTypeWillChange]
203+
public function current()
204+
{
205+
return is_array($this->items) ? current($this->items) : null;
206+
}
207+
208+
/**
209+
* Returns the data array index referenced by its internal cursor.
210+
*
211+
* @return mixed The index referenced by the data array's internal cursor.
212+
* If the array is empty or undefined or there is no element at the
213+
* cursor, the function returns null
214+
*/
215+
#[\ReturnTypeWillChange]
216+
public function key()
217+
{
218+
return is_array($this->items) ? key($this->items) : null;
219+
}
220+
221+
/**
222+
* Moves the data array's internal cursor forward one element.
223+
*
224+
* @return mixed The element referenced by the data array's internal cursor
225+
* after the move is completed. If there are no more elements in the
226+
* array after the move, the function returns false. If the data array
227+
* is undefined, the function returns null
228+
*/
229+
#[\ReturnTypeWillChange]
230+
public function next()
231+
{
232+
return is_array($this->items) ? next($this->items) : null;
233+
}
234+
235+
/**
236+
* Moves the data array's internal cursor to the first element.
237+
*
238+
* @return mixed The element referenced by the data array's internal cursor
239+
* after the move is completed. If the data array is empty, the function
240+
* returns false. If the data array is undefined, the function returns
241+
* null
242+
*/
243+
#[\ReturnTypeWillChange]
244+
public function rewind()
245+
{
246+
return is_array($this->items) ? reset($this->items) : null;
247+
}
248+
249+
/**
250+
* Tests whether the iterator's current index is valid.
251+
*
252+
* @return bool True if the current index is valid; false otherwise
253+
*/
254+
#[\ReturnTypeWillChange]
255+
public function valid()
256+
{
257+
return is_array($this->items) ? null !== key($this->items) : false;
258+
}
259+
260+
/**
261+
* Remove a value using the offset as a key.
262+
*
263+
* @param string $key
264+
*
265+
* @return void
266+
*/
267+
public function remove($key)
268+
{
269+
$this->offsetUnset($key);
270+
}
271+
}

0 commit comments

Comments
 (0)