Skip to content

Commit 649514c

Browse files
committed
Add the companienv
1 parent 93eeaec commit 649514c

9 files changed

+403
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/vendor
2+
/composer.lock

bin/companienv

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
if (is_file($autoload = getcwd() . '/vendor/autoload.php')) {
5+
require $autoload;
6+
}
7+
8+
if (!class_exists('Companienv\\Application', true)) {
9+
if (is_file($autoload = __DIR__ . '/../vendor/autoload.php')) {
10+
require($autoload);
11+
} elseif (is_file($autoload = __DIR__ . '/../../../autoload.php')) {
12+
require($autoload);
13+
} else {
14+
fwrite(STDERR,
15+
'You must set up the project dependencies, run the following commands:'.PHP_EOL.
16+
'curl -s http://getcomposer.org/installer | php'.PHP_EOL.
17+
'php composer.phar install'.PHP_EOL
18+
);
19+
exit(1);
20+
}
21+
}
22+
23+
$rootFolder = getcwd();
24+
25+
$application = new \Companienv\Application($rootFolder);
26+
$application->run();

composer.json

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "sroze/companienv",
3+
"description": "Companion for .env files",
4+
"keywords": [
5+
"dotenv",
6+
".env",
7+
"configuration"
8+
],
9+
"type": "library",
10+
"license": "MIT",
11+
"authors": [
12+
{
13+
"name": "Samuel ROZE",
14+
"email": "[email protected]"
15+
}
16+
],
17+
"require": {
18+
"php": ">=7.0",
19+
"symfony/console": "~4.0",
20+
"symfony/dotenv": "~4.0",
21+
"jackiedo/dotenv-editor": "~1.0"
22+
},
23+
"autoload": {
24+
"psr-0": {
25+
"Companienv": "src/"
26+
}
27+
},
28+
"bin": [
29+
"bin/companienv"
30+
]
31+
}

src/Companienv/Application.php

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Companienv;
4+
5+
use Companienv\DotEnv\Parser;
6+
use Symfony\Component\Console\Input\ArgvInput;
7+
use Symfony\Component\Console\Output\ConsoleOutput;
8+
9+
class Application
10+
{
11+
private $rootDirectory;
12+
13+
public function __construct(string $rootDirectory)
14+
{
15+
$this->rootDirectory = $rootDirectory;
16+
}
17+
18+
public function run()
19+
{
20+
$referenceFile = $this->rootDirectory.'/.env.dist';
21+
$configurationFile = $this->rootDirectory.'/.env';
22+
23+
$reference = (new Parser())->parse($referenceFile);
24+
25+
$companion = new Companion(new ArgvInput(), new ConsoleOutput(), $reference, $configurationFile);
26+
$companion->fillGaps();
27+
}
28+
}

src/Companienv/Companion.php

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
namespace Companienv;
4+
5+
use Companienv\DotEnv\Block;
6+
use Companienv\DotEnv\File;
7+
use Jackiedo\DotenvEditor\DotenvFormatter;
8+
use Jackiedo\DotenvEditor\DotenvWriter;
9+
use Symfony\Component\Console\Helper\QuestionHelper;
10+
use Symfony\Component\Console\Input\InputInterface;
11+
use Symfony\Component\Console\Output\OutputInterface;
12+
use Symfony\Component\Console\Question\Question;
13+
14+
class Companion
15+
{
16+
private $input;
17+
private $output;
18+
19+
private $reference;
20+
private $path;
21+
22+
public function __construct(InputInterface $input, OutputInterface $output, File $reference, string $path)
23+
{
24+
$this->input = $input;
25+
$this->output = $output;
26+
27+
$this->reference = $reference;
28+
$this->path = $path;
29+
}
30+
31+
public function fillGaps()
32+
{
33+
$missingVariables = $this->getMissingVariables();
34+
if (count($missingVariables) == 0) {
35+
return;
36+
}
37+
38+
$this->output->writeln(sprintf(
39+
'It looks like you are missing some configuration (%d variables). I will help you to sort this out.',
40+
count($missingVariables)
41+
));
42+
43+
if (!$this->askConfirmation('<info>Let\'s fix this? (y) </info>')) {
44+
$this->output->writeln([
45+
'',
46+
'<comment>I let you think about it then. Re-run the command to get started again.</comment>',
47+
''
48+
]);
49+
50+
return;
51+
}
52+
53+
$definedVariablesHash = $this->getDefinedVariablesHash();
54+
foreach ($this->reference->getBlocks() as $block) {
55+
$this->fillBlockGaps($block, $missingVariables, $definedVariablesHash);
56+
}
57+
}
58+
59+
private function fillBlockGaps(Block $block, array $missingVariables, array $definedVariablesHash)
60+
{
61+
$variablesInBlock = $block->getVariablesInBlock($missingVariables);
62+
if (count($variablesInBlock) == 0) {
63+
return;
64+
}
65+
66+
$this->output->writeln([
67+
'',
68+
'<info>'.$block->getTitle().'</info>',
69+
$block->getDescription(),
70+
''
71+
]);
72+
73+
foreach ($block->getVariables() as $variable) {
74+
$defaultValue = isset($definedVariablesHash[$variable->getName()]) ? $definedVariablesHash[$variable->getName()] : $variable->getValue();
75+
$question = sprintf('<comment>%s</comment> ? ', $variable->getName());
76+
77+
if ($defaultValue) {
78+
$question .= '('.$defaultValue.') ';
79+
}
80+
81+
$value = $this->ask($question, $defaultValue);
82+
$this->writeVariable($variable->getName(), $value);
83+
}
84+
}
85+
86+
private function writeVariable(string $name, string $value)
87+
{
88+
if (!file_exists($this->path)) {
89+
file_put_contents($this->path, '');
90+
}
91+
92+
$variablesInFileHash = $this->getDefinedVariablesHash();
93+
94+
$writer = new DotenvWriter(new DotenvFormatter());
95+
$writer->setBuffer(file_get_contents($this->path));
96+
97+
if (isset($variablesInFileHash[$name])) {
98+
$writer->updateSetter($name, $value);
99+
} else {
100+
$writer->appendSetter($name, $value);
101+
}
102+
103+
$writer->save($this->path);
104+
}
105+
106+
private function getMissingVariables()
107+
{
108+
$variablesInFile = $this->getDefinedVariablesHash();
109+
110+
$variablesInReference = $this->reference->getAllVariables();
111+
$missingVariables = [];
112+
113+
foreach ($variablesInReference as $variable) {
114+
if ($variable->hasValue() && !isset($variablesInFile[$variable->getName()])) {
115+
$missingVariables[] = $variable;
116+
}
117+
}
118+
119+
return $missingVariables;
120+
}
121+
122+
private function getDefinedVariablesHash()
123+
{
124+
$variablesInFile = [];
125+
if (file_exists($this->path)) {
126+
$dotEnv = new \Symfony\Component\Dotenv\Dotenv();
127+
$variablesInFile = $dotEnv->parse(file_get_contents($this->path), $this->path);
128+
}
129+
130+
return $variablesInFile;
131+
}
132+
133+
private function askConfirmation(string $question) : bool
134+
{
135+
return in_array(strtolower($this->ask($question, 'y')), ['y', 'yes']);
136+
}
137+
138+
private function ask(string $question, string $default = null) : string
139+
{
140+
return (new QuestionHelper())->ask($this->input, $this->output, new Question($question, $default));
141+
}
142+
}

src/Companienv/DotEnv/Block.php

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace Companienv\DotEnv;
4+
5+
class Block
6+
{
7+
private $title;
8+
private $description;
9+
private $variables;
10+
private $attributes;
11+
12+
public function __construct(string $title, string $description = '', array $variables = [], array $attributes = [])
13+
{
14+
$this->title = $title;
15+
$this->description = $description;
16+
$this->variables = $variables;
17+
$this->attributes = $attributes;
18+
}
19+
20+
public function appendToDescription(string $string)
21+
{
22+
$this->description .= $string;
23+
}
24+
25+
public function addVariable(Variable $variable)
26+
{
27+
$this->variables[] = $variable;
28+
}
29+
30+
public function addAttribute(string $attribute)
31+
{
32+
$this->attributes[] = $attribute;
33+
}
34+
35+
public function getTitle(): string
36+
{
37+
return $this->title;
38+
}
39+
40+
public function getDescription(): string
41+
{
42+
return $this->description;
43+
}
44+
45+
/**
46+
* @return Variable[]
47+
*/
48+
public function getVariables(): array
49+
{
50+
return $this->variables;
51+
}
52+
53+
public function getVariablesInBlock(array $variables)
54+
{
55+
$blockVariableNames = array_map(function (Variable $variable) {
56+
return $variable->getName();
57+
}, $this->variables);
58+
59+
return array_filter($variables, function (Variable $variable) use ($blockVariableNames) {
60+
return in_array($variable->getName(), $blockVariableNames);
61+
});
62+
}
63+
}

src/Companienv/DotEnv/File.php

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Companienv\DotEnv;
4+
5+
class File
6+
{
7+
private $header;
8+
private $blocks;
9+
10+
public function __construct(string $header = '', array $blocks = [])
11+
{
12+
$this->header = $header;
13+
$this->blocks = $blocks;
14+
}
15+
16+
public function getBlocks(): array
17+
{
18+
return $this->blocks;
19+
}
20+
21+
/**
22+
* @return Variable[]
23+
*/
24+
public function getAllVariables() : array
25+
{
26+
return array_reduce($this->blocks, function (array $carry, Block $block) {
27+
return array_merge($carry, $block->getVariables());
28+
}, []);
29+
}
30+
}

src/Companienv/DotEnv/Parser.php

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace Companienv\DotEnv;
4+
5+
class Parser
6+
{
7+
public function parse(string $path) : File
8+
{
9+
$blocks = [];
10+
11+
/** @var Block|null $block */
12+
$block = null;
13+
foreach (file($path) as $number => $line) {
14+
$line = trim($line);
15+
if (empty($line)) {
16+
continue;
17+
}
18+
19+
if (strpos($line, '#') === 0) {
20+
// We see a title
21+
if (substr($line, 0, 2) == '##') {
22+
$block = new Block(trim($line, '# '));
23+
$blocks[] = $block;
24+
} elseif (substr($line, 0, 2) == '#~') {
25+
// Ignore this comment.
26+
} elseif ($block !== null) {
27+
if (substr($line, 0, 2) == '#+') {
28+
$block->addAttribute(substr($line, 2));
29+
} else {
30+
$block->appendToDescription(trim($line, '# '));
31+
}
32+
}
33+
} else {
34+
// This is a variable
35+
$sides = explode('=', $line);
36+
if (count($sides) != 2) {
37+
throw new \InvalidArgumentException(sprintf(
38+
'The line %d of the file %s is invalid: %s',
39+
$number,
40+
$path,
41+
$line
42+
));
43+
}
44+
45+
$block->addVariable(new Variable($sides[0], $sides[1]));
46+
}
47+
}
48+
49+
return new File('', $blocks);
50+
}
51+
}

0 commit comments

Comments
 (0)