diff --git a/src/CLIPrompt.php b/src/CLIPrompt.php index a885a3b..a576571 100644 --- a/src/CLIPrompt.php +++ b/src/CLIPrompt.php @@ -26,7 +26,7 @@ class CLIPrompt { /** * CLIPrompt constructor. * - * @param $file string [optional] Input file path. + * @param string $file [optional] Input file path. * * @return void */ @@ -38,18 +38,88 @@ public function __construct($file = 'php://stdin') { /** * Prompts the user for an answer to the provided question. * - * @param $question string Question to prompt the user. + * @param string $question Question to prompt the user. * * @return string */ - public function ask($question) { - while (empty($response = readline($question))) { + public function ask($question, $style = NULL) { + if ($style) { + $question = $this->applyStyle($question, $style); + } + + echo $question; + while (empty($response = readline())) { $this->error('Please provide a response'); + echo $question; } return $response; } + protected function applyStyle($str, $style) { + switch ($style) { + case 'warning': + $str = $this->colored->str($str, 'yellow'); + break; + case 'danger': + case 'error': + $str = $this->colored->str($str, 'red'); + break; + case 'info': + $str = $this->colored->str($str, 'blue'); + break; + } + + return $str; + } + + /** + * Ask a yes or no question. + * + * @param string $question the question. + * + * @return bool + */ + public function askBool($question, $style = NULL) { + $answer = strtolower($this->ask($question . ' [Y/n]:', $style)); + while (!in_array($answer, ['y', 'n', 'yes', 'no'])) { + $this->error('Please answer with "yes", "no", "y" or "n"'); + } + + if (substr($answer, 0, 1) === 'y') { + return TRUE; + } + + return FALSE; + } + + /** + * Ask multiple choice questions. + * + * @param string $question The question. + * @param array $options A list of options. + * + * @return int The index of the selected option. + */ + public function askMultipleChoice($question, $options, $style = NULL) { + if($style) { + $question = $this->applyStyle($question, $style); + } + + $this->line($question); + $len = count($options); + + for ($i = 1; $i <= $len; $i++) { + $this->line("[$i] {$options[$i - 1]}"); + } + + do { + $answer = $this->ask('Please enter an option number: '); + } while ($answer < 1 || $answer > $len); + + return intval($answer) - 1; + } + /** * Prints a line to the file. * @@ -62,7 +132,7 @@ public function line($message) { /** * Prints a line with a blue background. * - * @param $message + * @param string $message */ public function info($message) { echo $this->colored->str($message, 'blue') . PHP_EOL; @@ -71,7 +141,7 @@ public function info($message) { /** * Prints a line with a red background. * - * @param $message + * @param string $message */ public function error($message) { echo $this->colored->str($message, 'red') . PHP_EOL; @@ -80,7 +150,7 @@ public function error($message) { /** * Prints a line with yellow background. * - * @param $message + * @param string $message */ public function warn($message) { echo $this->colored->str($message, 'yellow') . PHP_EOL; @@ -89,7 +159,7 @@ public function warn($message) { /** * Prints a line with a green background. * - * @param $message + * @param string $message */ public function success($message) { echo $this->colored->str($message, 'black', 'green') . PHP_EOL; diff --git a/src/Colors.php b/src/Colors.php index cd1b445..46f7d52 100644 --- a/src/Colors.php +++ b/src/Colors.php @@ -60,7 +60,7 @@ public function __construct() { public function str($string, $foreground_color = NULL, $background_color = NULL) { $colored_string = ''; $colored_padding = ''; - $string = ' ' . $string . ' '; + //$string = ' ' . $string . ' '; $padding = str_pad('', strlen($string), ' ');; // Check if given foreground color found @@ -77,9 +77,9 @@ public function str($string, $foreground_color = NULL, $background_color = NULL) // Add string and end coloring $colored_string .= $string . "\033[0m"; - $full_string = ' ' . $colored_padding . PHP_EOL; - $full_string .= ' ' . $colored_string . PHP_EOL; - $full_string .= ' ' . $colored_padding; + //$full_string = $colored_padding . PHP_EOL; + $full_string = $colored_string;// . PHP_EOL; + //$full_string .= $colored_padding; } else { $full_string = $colored_string; diff --git a/src/DB.php b/src/DB.php index e175492..01a3823 100644 --- a/src/DB.php +++ b/src/DB.php @@ -28,6 +28,7 @@ class DB { /** * P + * * @var array */ protected $parameters; @@ -134,11 +135,20 @@ public function query($sql, $parameters = []) { public function get() { $execute = $this->prepared->execute($this->parameters); - if(!$execute) { + if (!$execute) { $error = $this->prepared->errorInfo(); - throw new Exception("Couldn't execute query. $this->sql. " . $error[2]); + throw new Exception("Couldn't execute query. $this->sql. " . PHP_EOL . implode(' ', $error)); } return $this->prepared->fetchAll(); } + + /** + * Get the count directly. + * + * @return mixed + */ + public function count() { + return intval($this->get()[0]['count']); + } } \ No newline at end of file diff --git a/src/Generator.php b/src/Generator.php index 0556e39..bfebefd 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -107,6 +107,11 @@ class Generator { */ protected $options; + /** + * Database connection. + * + * @var null|\StatonLab\FieldGenerator\DB + */ protected $db = NULL; /** @@ -140,7 +145,7 @@ public function run() { $this->printIntro(); foreach ($this->questions as $question => $field) { - $this->{$field} = $this->prompt->ask($question); + $this->{$field} = trim($this->prompt->ask($question)); } // Auto construct field name @@ -151,7 +156,7 @@ public function run() { $files = $this->generate(); - // TODO: CHECK TERMS AGAINST DB HERE + $this->validateTerms(); try { return $this->make($files); @@ -174,6 +179,146 @@ protected function validateOptions() { } } + /** + * Validate provided terms in DB. + */ + protected function validateTerms() { + $this->prompt->info('Performing DB checks to validate entries ...'); + $failed = FALSE; + + // Validate DB + $count = $this->count('chado.db', 'name', $this->db_name); + if ($count <= 0) { + $failed = TRUE; + $answer = $this->prompt->askBool("The DB \"{$this->db_name}\" does not exist in the chado.db table. Using this value will create a new DB. Are you sure?"); + if (!$answer) { + $this->terminate(); + } + } + + // Validate CV + $count = $this->count('chado.cv', 'name', $this->cv_name); + if ($count <= 0) { + $failed = TRUE; + $answer = $this->prompt->askBool("The CV \"{$this->cv_name}\" does not exist in the chado.cv table. Using this value will create a new CV. Are you sure?"); + if (!$answer) { + $this->terminate(); + } + } + + // Validate CV Term + $count = $this->count('chado.cvterm', 'name', $this->cv_term); + if ($count <= 0) { + $failed = TRUE; + $answer = $this->prompt->askBool("The CV term \"{$this->cv_term}\" does not exist in the chado.cvterm table. Using this value will create a new CV. Are you sure?"); + if (!$answer) { + $this->terminate(); + } + } + + if (!$failed) { + $results = $this->db->query('SELECT CV.name AS cv_name, DB.name AS db_name, DBX.accession AS accession + FROM chado.cvterm AS CVTERM + JOIN chado.cv AS CV ON CVTERM.cv_id = CV.cv_id + JOIN chado.dbxref AS DBX ON CVTERM.dbxref_id = DBX.dbxref_id + JOIN chado.db AS DB ON DBX.db_id = DB.db_id + WHERE CVTERM.name = :cv_term', [':cv_term' => $this->cv_term]) + ->get(); + $count = count($results); + switch ($count) { + case 0: + $this->prompt->askBool('Warning: the CV, DB, and CVterm are not properly linked through the chado.dbxref table. If this term was manually inserted into the db, remove it before adding the new term.', 'warning'); + break; + case 1: + $this->verifyAccession($results[0]); + break; + default: + $this->handleMultiDBXRef($results); + break; + } + } + + $this->prompt->info("Chosen CV Term ID {$this->db_name}:{$this->field_accession} and CV {$this->cv_name}"); + $this->prompt->info('DB checks succeeded'); + } + + /** + * Verify that the entered accession is equivalent to the one in the DB. + * + * @param array $result + */ + protected function verifyAccession($result) { + if (!$this->checkAccessionInDB($result)) { + $answer = $this->prompt->askBool("The accession in chado is {$result['accession']}, which does not match the provided the accession ({$this->field_accession}). Would you like to use {$result['accession']} instead?", 'warning'); + if ($answer) { + $this->field_accession = $result['accession']; + } + } + } + + /** + * Handle multiple results from DB. + * + * @param array $results 2d array of db results. + */ + protected function handleMultiDBXRef($results) { + // Create options array. + $options = []; + $len = count($results); + foreach ($results as $result) { + $options[] = "ID {$result['db_name']}:{$results['accesion']} and controlled vocabulary {$results['cv_name']}"; + } + $options[] = "None of the above. I'd like to keep my settings."; + + $index = $this->prompt->askMultipleChoice('Multiple links were found to the same CV term. Please select the most accurate CV term from the list below.', $options, 'warning'); + + if ($index === $len) { + return; + } + + $selected = $results[$index]; + + $this->field_accession = $selected['accession']; + $this->db_name = $selected['db_name']; + $this->cv_name = $selected['cv_name']; + } + + /** + * Checks the accession validity against the DB result. + * + * @param array $result A single DB result. + * + * @return bool + */ + protected function checkAccessionInDB($result) { + // Wrapped in quotes to make sure both are evaluated as strings + return "$this->field_accession" === trim("{$result['accession']}"); + } + + /** + * Get the total count to a query. + * + * @param $table + * @param $condition_column + * @param $condition_value + * + * @return mixed + */ + protected function count($table, $condition_column, $condition_value) { + $sql = "SELECT COUNT(*) AS count FROM $table AS DB WHERE DB.{$condition_column} = :condition_column"; + return $this->db->query($sql, [':condition_column' => $condition_value]) + ->count(); + } + + /** + * Terminate the generator. + * + * @throws \Exception + */ + protected function terminate() { + throw new Exception('User Terminated.'); + } + /** * Prints introduction message. */