aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/io
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe/src/io')
-rw-r--r--crates/shirabe/src/io/base_io.rs6
-rw-r--r--crates/shirabe/src/io/buffer_io.rs208
-rw-r--r--crates/shirabe/src/io/console_io.rs250
-rw-r--r--crates/shirabe/src/io/io_interface.rs10
-rw-r--r--crates/shirabe/src/io/null_io.rs10
5 files changed, 353 insertions, 131 deletions
diff --git a/crates/shirabe/src/io/base_io.rs b/crates/shirabe/src/io/base_io.rs
index 6e6e7d0..f2b7ee5 100644
--- a/crates/shirabe/src/io/base_io.rs
+++ b/crates/shirabe/src/io/base_io.rs
@@ -446,11 +446,11 @@ pub trait BaseIO: IOInterface {
let mut message_str = message.as_string().unwrap_or("").to_string();
if !context.is_empty() {
- let json = Silencer::call(|| {
- json_encode_ex(
+ let json: anyhow::Result<Option<String>> = Silencer::call(|| {
+ Ok(json_encode_ex(
&PhpMixed::Array(context.clone()),
JSON_INVALID_UTF8_IGNORE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
- )
+ ))
});
if let Ok(Some(json_str)) = json {
message_str += " ";
diff --git a/crates/shirabe/src/io/buffer_io.rs b/crates/shirabe/src/io/buffer_io.rs
index 79fa9c3..ce4070a 100644
--- a/crates/shirabe/src/io/buffer_io.rs
+++ b/crates/shirabe/src/io/buffer_io.rs
@@ -3,11 +3,12 @@
use crate::io::console_io::ConsoleIO;
use anyhow::Result;
use shirabe_external_packages::composer::pcre::preg::Preg;
+use shirabe_external_packages::symfony::component::console::helper::helper_set::HelperSet;
+use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface;
+use shirabe_external_packages::symfony::component::console::input::string_input::StringInput;
+use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface;
use shirabe_external_packages::symfony::console::formatter::output_formatter_interface::OutputFormatterInterface;
-use shirabe_external_packages::symfony::console::helper::helper_set::HelperSet;
use shirabe_external_packages::symfony::console::helper::question_helper::QuestionHelper;
-use shirabe_external_packages::symfony::console::input::streamable_input_interface::StreamableInputInterface;
-use shirabe_external_packages::symfony::console::input::string_input::StringInput;
use shirabe_external_packages::symfony::console::output::stream_output::StreamOutput;
use shirabe_php_shim::{
PHP_EOL, PhpMixed, RuntimeException, fopen, fseek, fwrite, rewind, stream_get_contents,
@@ -16,7 +17,7 @@ use shirabe_php_shim::{
#[derive(Debug)]
pub struct BufferIO {
- inner: ConsoleIO,
+ pub(crate) inner: ConsoleIO,
}
impl BufferIO {
@@ -25,7 +26,7 @@ impl BufferIO {
verbosity: i64,
formatter: Option<Box<dyn OutputFormatterInterface>>,
) -> Result<Self> {
- let mut input_obj = StringInput::new(input);
+ let mut input_obj = StringInput::new(&input);
input_obj.set_interactive(false);
let stream = fopen("php://memory", "rw");
@@ -38,21 +39,34 @@ impl BufferIO {
}
let decorated = formatter.as_ref().map_or(false, |f| f.is_decorated());
- let output = StreamOutput::new(stream, verbosity, decorated, formatter);
+ // TODO(phase-b): StreamOutput lives under `symfony::console` (not `symfony::component::console`)
+ // and so does not implement the `component::console::output::OutputInterface` ConsoleIO expects.
+ // Real fix requires unifying the two crate paths.
+ let _ = formatter;
+ let _ = StreamOutput::new(stream, verbosity, Some(decorated));
+ let output: Box<dyn OutputInterface> = todo!("StreamOutput as Box<dyn OutputInterface>");
+ // TODO(phase-b): symfony console helper modules live under both `symfony::console`
+ // and `symfony::component::console`; QuestionHelper::new is not yet provided.
+ let helpers: Vec<PhpMixed> = vec![/* PhpMixed::Object(QuestionHelper::new()) */];
+ let _ = std::marker::PhantomData::<QuestionHelper>;
let inner = ConsoleIO::new(
- input_obj,
+ Box::new(input_obj) as Box<dyn InputInterface>,
output,
- HelperSet::new(vec![Box::new(QuestionHelper::new())]),
+ HelperSet::new(helpers),
);
Ok(Self { inner })
}
pub fn get_output(&self) -> String {
- fseek(self.inner.output.get_stream(), 0);
+ // TODO(phase-b): OutputInterface::get_stream returns PhpResource, while
+ // fseek/stream_get_contents take PhpMixed. Conversion is not yet defined.
+ let stream: PhpMixed =
+ todo!("PhpResource -> PhpMixed conversion for OutputInterface::get_stream");
+ fseek(stream.clone(), 0);
- let output = stream_get_contents(self.inner.output.get_stream()).unwrap_or_default();
+ let output = stream_get_contents(stream).unwrap_or_default();
let output = Preg::replace_callback(
r"{(?<=^|\n|\x08)(.+?)(\x08+)}",
@@ -80,28 +94,19 @@ impl BufferIO {
&output,
);
- output
+ // TODO(phase-b): Preg::replace_callback returns Result<String>, unwrap for now
+ output.unwrap_or_default()
}
pub fn set_user_inputs(&mut self, inputs: Vec<String>) -> Result<()> {
- if self
- .inner
- .input
- .as_any()
- .downcast_ref::<dyn StreamableInputInterface>()
- .is_none()
- {
- return Err(RuntimeException {
- message: "Setting the user inputs requires at least the version 3.2 of the symfony/console component.".to_string(),
- code: 0,
- }
- .into());
- }
-
- self.inner.input.set_stream(self.create_stream(inputs)?);
- self.inner.input.set_interactive(true);
-
- Ok(())
+ // TODO(phase-b): downcast Box<dyn InputInterface> to StreamableInputInterface.
+ // as_any/set_stream are not yet exposed on the InputInterface trait object.
+ let _ = inputs;
+ let _ = |i: &Box<dyn InputInterface>| -> bool {
+ let _ = i;
+ false
+ };
+ todo!("port BufferIO::set_user_inputs once StreamableInputInterface downcast is available")
}
fn create_stream(&self, inputs: Vec<String>) -> Result<PhpMixed> {
@@ -123,3 +128,148 @@ impl BufferIO {
Ok(stream)
}
}
+
+// TODO(phase-b): PHP `class BufferIO extends ConsoleIO` — delegate all IOInterface,
+// LoggerInterface, and BaseIO methods to `self.inner` (ConsoleIO).
+impl shirabe_external_packages::psr::log::logger_interface::LoggerInterface for BufferIO {
+ fn emergency(&self, message: &str, context: &[(&str, &str)]) {
+ self.inner.emergency(message, context)
+ }
+ fn alert(&self, message: &str, context: &[(&str, &str)]) {
+ self.inner.alert(message, context)
+ }
+ fn critical(&self, message: &str, context: &[(&str, &str)]) {
+ self.inner.critical(message, context)
+ }
+ fn error(&self, message: &str, context: &[(&str, &str)]) {
+ self.inner.error(message, context)
+ }
+ fn warning(&self, message: &str, context: &[(&str, &str)]) {
+ self.inner.warning(message, context)
+ }
+ fn notice(&self, message: &str, context: &[(&str, &str)]) {
+ self.inner.notice(message, context)
+ }
+ fn info(&self, message: &str, context: &[(&str, &str)]) {
+ self.inner.info(message, context)
+ }
+ fn debug(&self, message: &str, context: &[(&str, &str)]) {
+ self.inner.debug(message, context)
+ }
+ fn log(&self, level: &str, message: &str, context: &[(&str, &str)]) {
+ self.inner.log(level, message, context)
+ }
+}
+
+impl crate::io::io_interface::IOInterface for BufferIO {
+ fn is_interactive(&self) -> bool {
+ self.inner.is_interactive()
+ }
+ fn is_verbose(&self) -> bool {
+ self.inner.is_verbose()
+ }
+ fn is_very_verbose(&self) -> bool {
+ self.inner.is_very_verbose()
+ }
+ fn is_debug(&self) -> bool {
+ self.inner.is_debug()
+ }
+ fn is_decorated(&self) -> bool {
+ self.inner.is_decorated()
+ }
+ fn write3(&self, message: &str, newline: bool, verbosity: i64) {
+ self.inner.write3(message, newline, verbosity)
+ }
+ fn write_error3(&self, message: &str, newline: bool, verbosity: i64) {
+ self.inner.write_error3(message, newline, verbosity)
+ }
+ fn write_raw3(&self, message: &str, newline: bool, verbosity: i64) {
+ self.inner.write_raw3(message, newline, verbosity)
+ }
+ fn write_error_raw3(&self, message: &str, newline: bool, verbosity: i64) {
+ self.inner.write_error_raw3(message, newline, verbosity)
+ }
+ fn overwrite4(&self, message: &str, newline: bool, size: Option<i64>, verbosity: i64) {
+ self.inner.overwrite4(message, newline, size, verbosity)
+ }
+ fn overwrite_error4(&self, message: &str, newline: bool, size: Option<i64>, verbosity: i64) {
+ self.inner
+ .overwrite_error4(message, newline, size, verbosity)
+ }
+ fn ask(&self, question: String, default: PhpMixed) -> PhpMixed {
+ self.inner.ask(question, default)
+ }
+ fn ask_confirmation(&self, question: String, default: bool) -> bool {
+ self.inner.ask_confirmation(question, default)
+ }
+ fn ask_and_validate(
+ &self,
+ question: String,
+ validator: Box<dyn Fn(PhpMixed) -> PhpMixed>,
+ attempts: Option<i64>,
+ default: PhpMixed,
+ ) -> PhpMixed {
+ self.inner
+ .ask_and_validate(question, validator, attempts, default)
+ }
+ fn ask_and_hide_answer(&self, question: String) -> Option<String> {
+ self.inner.ask_and_hide_answer(question)
+ }
+ fn select(
+ &self,
+ question: String,
+ choices: Vec<String>,
+ default: PhpMixed,
+ attempts: PhpMixed,
+ error_message: String,
+ multiselect: bool,
+ ) -> PhpMixed {
+ self.inner.select(
+ question,
+ choices,
+ default,
+ attempts,
+ error_message,
+ multiselect,
+ )
+ }
+ fn get_authentications(
+ &self,
+ ) -> indexmap::IndexMap<String, indexmap::IndexMap<String, Option<String>>> {
+ self.inner.get_authentications()
+ }
+ fn has_authentication(&self, repository_name: &str) -> bool {
+ self.inner.has_authentication(repository_name)
+ }
+ fn get_authentication(
+ &self,
+ repository_name: &str,
+ ) -> indexmap::IndexMap<String, Option<String>> {
+ self.inner.get_authentication(repository_name)
+ }
+ fn set_authentication(
+ &mut self,
+ repository_name: String,
+ username: String,
+ password: Option<String>,
+ ) {
+ self.inner
+ .set_authentication(repository_name, username, password)
+ }
+ fn load_configuration(&mut self, config: &mut crate::config::Config) -> anyhow::Result<()> {
+ self.inner.load_configuration(config)
+ }
+}
+
+impl crate::io::base_io::BaseIO for BufferIO {
+ fn authentications(
+ &self,
+ ) -> &indexmap::IndexMap<String, indexmap::IndexMap<String, Option<String>>> {
+ self.inner.authentications()
+ }
+ fn authentications_mut(
+ &mut self,
+ ) -> &mut indexmap::IndexMap<String, indexmap::IndexMap<String, Option<String>>> {
+ self.inner.authentications_mut()
+ }
+}
diff --git a/crates/shirabe/src/io/console_io.rs b/crates/shirabe/src/io/console_io.rs
index 39099c4..2182c37 100644
--- a/crates/shirabe/src/io/console_io.rs
+++ b/crates/shirabe/src/io/console_io.rs
@@ -29,7 +29,6 @@ use crate::question::strict_confirmation_question::StrictConfirmationQuestion;
use crate::util::silencer::Silencer;
/// The Input/Output helper.
-#[derive(Debug)]
pub struct ConsoleIO {
authentications: indexmap::IndexMap<String, indexmap::IndexMap<String, Option<String>>>,
@@ -45,6 +44,21 @@ pub struct ConsoleIO {
verbosity_map: IndexMap<i64, i64>,
}
+// TODO(phase-b): dyn InputInterface / dyn OutputInterface do not implement Debug,
+// so we cannot derive Debug. Provide a manual impl that omits those fields.
+impl std::fmt::Debug for ConsoleIO {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("ConsoleIO")
+ .field("authentications", &self.authentications)
+ .field("helper_set", &self.helper_set)
+ .field("last_message", &self.last_message)
+ .field("last_message_err", &self.last_message_err)
+ .field("start_time", &self.start_time)
+ .field("verbosity_map", &self.verbosity_map)
+ .finish()
+ }
+}
+
impl ConsoleIO {
/// Constructor.
///
@@ -131,9 +145,11 @@ impl ConsoleIO {
// TODO(phase-b): downcast Box<dyn OutputInterface> to ConsoleOutputInterface
let console_output: &dyn ConsoleOutputInterface =
todo!("downcast self.output to ConsoleOutputInterface");
- console_output
- .get_error_output()
- .write3(messages.clone(), newline, sf_verbosity);
+ console_output.get_error_output().write(
+ &Self::to_string_list(&messages).join(if newline { "\n" } else { "" }),
+ newline,
+ sf_verbosity,
+ );
// PHP: implode($newline ? "\n" : '', (array) $messages)
*self.last_message_err.borrow_mut() = implode(
if newline { "\n" } else { "" },
@@ -143,7 +159,11 @@ impl ConsoleIO {
return;
}
- self.output.write3(messages.clone(), newline, sf_verbosity);
+ self.output.write(
+ &Self::to_string_list(&messages).join(if newline { "\n" } else { "" }),
+ newline,
+ sf_verbosity,
+ );
*self.last_message.borrow_mut() = implode(
if newline { "\n" } else { "" },
&Self::to_string_list(&messages),
@@ -168,11 +188,12 @@ impl ConsoleIO {
// since overwrite is supposed to overwrite last message...
let size = size.unwrap_or_else(|| {
// removing possible formatting of lastMessage with strip_tags
- strlen(&strip_tags(if stderr {
- &self.last_message_err.borrow()
+ let last = if stderr {
+ self.last_message_err.borrow().clone()
} else {
- &self.last_message.borrow()
- }))
+ self.last_message.borrow().clone()
+ };
+ strlen(&strip_tags(&last))
});
// ...let's fill its length with backspaces
self.do_write(
@@ -279,7 +300,7 @@ impl ConsoleIO {
};
if is_string(&messages) {
let message = Self::ensure_valid_utf8(messages.as_string().unwrap_or(""));
- return PhpMixed::String(Preg::replace(&pattern, "", &message));
+ return PhpMixed::String(Preg::replace(&pattern, "", &message).unwrap_or_default());
}
// PHP: $sanitized = []; foreach ($messages as $key => $message) { ... }
@@ -290,7 +311,7 @@ impl ConsoleIO {
let s = Self::ensure_valid_utf8(message.as_string().unwrap_or(""));
sanitized.insert(
key.to_string(),
- PhpMixed::String(Preg::replace(&pattern, "", &s)),
+ PhpMixed::String(Preg::replace(&pattern, "", &s).unwrap_or_default()),
);
}
}
@@ -299,7 +320,7 @@ impl ConsoleIO {
let s = Self::ensure_valid_utf8(message.as_string().unwrap_or(""));
sanitized.insert(
key.clone(),
- PhpMixed::String(Preg::replace(&pattern, "", &s)),
+ PhpMixed::String(Preg::replace(&pattern, "", &s).unwrap_or_default()),
);
}
}
@@ -358,40 +379,44 @@ impl ConsoleIO {
}
impl LoggerInterface for ConsoleIO {
- fn emergency(&self, message: &str, context: &[(&str, &str)]) {
- <Self as BaseIO>::emergency(self, message, context)
+ // TODO(phase-b): BaseIO's emergency/alert/.../log take PhpMixed and
+ // IndexMap<String, Box<PhpMixed>> while LoggerInterface takes &str and
+ // &[(&str, &str)]. Delegation requires reconciling signatures; for now,
+ // mirror NullIO and panic via todo!().
+ fn emergency(&self, _message: &str, _context: &[(&str, &str)]) {
+ todo!()
}
- fn alert(&self, message: &str, context: &[(&str, &str)]) {
- <Self as BaseIO>::alert(self, message, context)
+ fn alert(&self, _message: &str, _context: &[(&str, &str)]) {
+ todo!()
}
- fn critical(&self, message: &str, context: &[(&str, &str)]) {
- <Self as BaseIO>::critical(self, message, context)
+ fn critical(&self, _message: &str, _context: &[(&str, &str)]) {
+ todo!()
}
- fn error(&self, message: &str, context: &[(&str, &str)]) {
- <Self as BaseIO>::error(self, message, context)
+ fn error(&self, _message: &str, _context: &[(&str, &str)]) {
+ todo!()
}
- fn warning(&self, message: &str, context: &[(&str, &str)]) {
- <Self as BaseIO>::warning(self, message, context)
+ fn warning(&self, _message: &str, _context: &[(&str, &str)]) {
+ todo!()
}
- fn notice(&self, message: &str, context: &[(&str, &str)]) {
- <Self as BaseIO>::notice(self, message, context)
+ fn notice(&self, _message: &str, _context: &[(&str, &str)]) {
+ todo!()
}
- fn info(&self, message: &str, context: &[(&str, &str)]) {
- <Self as BaseIO>::info(self, message, context)
+ fn info(&self, _message: &str, _context: &[(&str, &str)]) {
+ todo!()
}
- fn debug(&self, message: &str, context: &[(&str, &str)]) {
- <Self as BaseIO>::debug(self, message, context)
+ fn debug(&self, _message: &str, _context: &[(&str, &str)]) {
+ todo!()
}
- fn log(&self, level: &str, message: &str, context: &[(&str, &str)]) {
- <Self as BaseIO>::log(self, level, message, context)
+ fn log(&self, _level: &str, _message: &str, _context: &[(&str, &str)]) {
+ todo!()
}
}
@@ -417,107 +442,142 @@ impl IOInterface for ConsoleIO {
}
fn write3(&self, message: &str, newline: bool, verbosity: i64) {
- let message = Self::sanitize(message, true);
+ let message = Self::sanitize(PhpMixed::String(message.to_string()), true);
self.do_write(message, newline, false, verbosity, false);
}
fn write_error3(&self, message: &str, newline: bool, verbosity: i64) {
- let message = Self::sanitize(message, true);
+ let message = Self::sanitize(PhpMixed::String(message.to_string()), true);
self.do_write(message, newline, true, verbosity, false);
}
fn write_raw3(&self, message: &str, newline: bool, verbosity: i64) {
- self.do_write(message, newline, false, verbosity, true);
+ self.do_write(
+ PhpMixed::String(message.to_string()),
+ newline,
+ false,
+ verbosity,
+ true,
+ );
}
fn write_error_raw3(&self, message: &str, newline: bool, verbosity: i64) {
- self.do_write(message, newline, true, verbosity, true);
+ self.do_write(
+ PhpMixed::String(message.to_string()),
+ newline,
+ true,
+ verbosity,
+ true,
+ );
}
fn overwrite4(&self, message: &str, newline: bool, size: Option<i64>, verbosity: i64) {
- self.do_overwrite(message, newline, size, false, verbosity);
+ self.do_overwrite(
+ PhpMixed::String(message.to_string()),
+ newline,
+ size,
+ false,
+ verbosity,
+ );
}
fn overwrite_error4(&self, message: &str, newline: bool, size: Option<i64>, verbosity: i64) {
- self.do_overwrite(message, newline, size, true, verbosity);
+ self.do_overwrite(
+ PhpMixed::String(message.to_string()),
+ newline,
+ size,
+ true,
+ verbosity,
+ );
}
- fn ask(&mut self, question: String, default: PhpMixed) -> PhpMixed {
+ fn ask(&self, question: String, default: PhpMixed) -> PhpMixed {
// PHP: $helper = $this->helperSet->get('question');
- let helper = self.helper_set.get("question");
- let question = Question::new(
- Self::sanitize(PhpMixed::String(question), true),
- if is_string(&default) {
- Self::sanitize(default, true)
- } else {
- default
- },
- );
+ let _helper = self.helper_set.get("question");
+ let sanitized_question = Self::sanitize(PhpMixed::String(question), true)
+ .as_string()
+ .unwrap_or("")
+ .to_string();
+ let sanitized_default = if is_string(&default) {
+ Some(Self::sanitize(default, true))
+ } else {
+ Some(default)
+ };
+ let _question = Question::new(&sanitized_question, sanitized_default);
- helper.ask(&*self.input, self.get_error_output(), &question)
+ // TODO(phase-b): HelperSet::get returns PhpMixed; QuestionHelper::ask is
+ // not yet modeled. Returning a placeholder until helper types are wired up.
+ todo!("call QuestionHelper::ask on resolved helper")
}
- fn ask_confirmation(&mut self, question: String, default: bool) -> bool {
- let helper = self.helper_set.get("question");
+ fn ask_confirmation(&self, question: String, default: bool) -> bool {
+ let _helper = self.helper_set.get("question");
// TODO(phase-b): Self::sanitize returns PhpMixed but new() expects String;
// also true/false regexes need to come through composer/symfony defaults.
let sanitized = Self::sanitize(PhpMixed::String(question), true)
.as_string()
.unwrap_or("")
.to_string();
- let question = StrictConfirmationQuestion::new(
+ let _question = StrictConfirmationQuestion::new(
sanitized,
default,
"/^y(?:es)?$/i".to_string(),
"/^no?$/i".to_string(),
);
- helper
- .ask(&*self.input, self.get_error_output(), &question)
- .as_bool()
- .unwrap_or(false)
+ // TODO(phase-b): see ask() above; placeholder until QuestionHelper is modeled.
+ todo!("call QuestionHelper::ask on resolved helper and coerce to bool")
}
fn ask_and_validate(
- &mut self,
+ &self,
question: String,
validator: Box<dyn Fn(PhpMixed) -> PhpMixed>,
attempts: Option<i64>,
default: PhpMixed,
) -> PhpMixed {
- let helper = self.helper_set.get("question");
- let mut question = Question::new(
- Self::sanitize(PhpMixed::String(question), true),
- if is_string(&default) {
- Self::sanitize(default, true)
- } else {
- default
- },
- );
- question.set_validator(validator);
+ let _helper = self.helper_set.get("question");
+ let sanitized_question = Self::sanitize(PhpMixed::String(question), true)
+ .as_string()
+ .unwrap_or("")
+ .to_string();
+ let sanitized_default = if is_string(&default) {
+ Some(Self::sanitize(default, true))
+ } else {
+ Some(default)
+ };
+ let mut question = Question::new(&sanitized_question, sanitized_default);
+ // TODO(phase-b): IOInterface validator type is Box<dyn Fn(PhpMixed) -> PhpMixed>
+ // but Question::set_validator expects Option<Box<dyn Fn(Option<PhpMixed>) -> Result<PhpMixed>>>.
+ // Bridge the signatures by adapting the input/output types.
+ let adapted: Box<dyn Fn(Option<PhpMixed>) -> anyhow::Result<PhpMixed>> =
+ Box::new(move |answer: Option<PhpMixed>| {
+ Ok(validator(answer.unwrap_or(PhpMixed::Null)))
+ });
+ question.set_validator(Some(adapted));
question.set_max_attempts(attempts);
- helper.ask(&*self.input, self.get_error_output(), &question)
+ // TODO(phase-b): QuestionHelper::ask not yet modeled.
+ todo!("call QuestionHelper::ask on resolved helper")
}
- fn ask_and_hide_answer(&mut self, question: String) -> Option<String> {
- let helper = self.helper_set.get("question");
- let mut question = Question::new(
- Self::sanitize(PhpMixed::String(question), true),
- PhpMixed::Null,
- );
+ fn ask_and_hide_answer(&self, question: String) -> Option<String> {
+ let _helper = self.helper_set.get("question");
+ let sanitized_question = Self::sanitize(PhpMixed::String(question), true)
+ .as_string()
+ .unwrap_or("")
+ .to_string();
+ let mut question = Question::new(&sanitized_question, Some(PhpMixed::Null));
question.set_hidden(true);
- helper
- .ask(&*self.input, self.get_error_output(), &question)
- .as_string()
- .map(|s| s.to_string())
+ // TODO(phase-b): QuestionHelper::ask not yet modeled.
+ todo!("call QuestionHelper::ask on resolved helper and coerce to Option<String>")
}
fn select(
- &mut self,
+ &self,
question: String,
choices: Vec<String>,
default: PhpMixed,
@@ -532,27 +592,39 @@ impl IOInterface for ConsoleIO {
.map(|s| Box::new(PhpMixed::String(s)))
.collect(),
);
- let helper = self.helper_set.get("question");
- let mut question = ChoiceQuestion::new(
- Self::sanitize(PhpMixed::String(question), true),
- Self::sanitize(choices.clone(), true),
- if is_string(&default) {
- Self::sanitize(default, true)
- } else {
- default
- },
- );
+ let _helper = self.helper_set.get("question");
+ let sanitized_question = Self::sanitize(PhpMixed::String(question), true)
+ .as_string()
+ .unwrap_or("")
+ .to_string();
+ // TODO(phase-b): ChoiceQuestion::new expects Vec<PhpMixed>; collect from the
+ // sanitized PhpMixed::List.
+ let sanitized_choices_mixed = Self::sanitize(choices.clone(), true);
+ let sanitized_choices: Vec<PhpMixed> = match sanitized_choices_mixed {
+ PhpMixed::List(l) => l.into_iter().map(|b| *b).collect(),
+ PhpMixed::Array(a) => a.into_values().map(|b| *b).collect(),
+ other => vec![other],
+ };
+ let sanitized_default = if is_string(&default) {
+ Some(Self::sanitize(default, true))
+ } else {
+ Some(default)
+ };
+ let mut question =
+ ChoiceQuestion::new(&sanitized_question, sanitized_choices, sanitized_default);
// PHP: IOInterface requires false, and Question requires null or int
let max_attempts = match attempts {
PhpMixed::Bool(false) => None,
PhpMixed::Int(i) => Some(i),
_ => None,
};
- question.set_max_attempts(max_attempts);
+ // ChoiceQuestion delegates set_max_attempts to its inner Question.
+ question.0.set_max_attempts(max_attempts);
question.set_error_message(&error_message);
question.set_multiselect(multiselect);
- let result = helper.ask(&*self.input, self.get_error_output(), &question);
+ // TODO(phase-b): QuestionHelper::ask not yet modeled.
+ let result: PhpMixed = todo!("call QuestionHelper::ask on resolved helper");
// PHP: $isAssoc = (bool) \count(array_filter(array_keys($choices), 'is_string'));
let choice_keys: Vec<String> = match &choices {
diff --git a/crates/shirabe/src/io/io_interface.rs b/crates/shirabe/src/io/io_interface.rs
index d9092b1..f014594 100644
--- a/crates/shirabe/src/io/io_interface.rs
+++ b/crates/shirabe/src/io/io_interface.rs
@@ -82,22 +82,22 @@ pub trait IOInterface: LoggerInterface + std::fmt::Debug {
}
fn overwrite_error4(&self, message: &str, newline: bool, size: Option<i64>, verbosity: i64);
- fn ask(&mut self, question: String, default: PhpMixed) -> PhpMixed;
+ fn ask(&self, question: String, default: PhpMixed) -> PhpMixed;
- fn ask_confirmation(&mut self, question: String, default: bool) -> bool;
+ fn ask_confirmation(&self, question: String, default: bool) -> bool;
fn ask_and_validate(
- &mut self,
+ &self,
question: String,
validator: Box<dyn Fn(PhpMixed) -> PhpMixed>,
attempts: Option<i64>,
default: PhpMixed,
) -> PhpMixed;
- fn ask_and_hide_answer(&mut self, question: String) -> Option<String>;
+ fn ask_and_hide_answer(&self, question: String) -> Option<String>;
fn select(
- &mut self,
+ &self,
question: String,
choices: Vec<String>,
default: PhpMixed,
diff --git a/crates/shirabe/src/io/null_io.rs b/crates/shirabe/src/io/null_io.rs
index 2536ad0..1a06e6e 100644
--- a/crates/shirabe/src/io/null_io.rs
+++ b/crates/shirabe/src/io/null_io.rs
@@ -58,16 +58,16 @@ impl IOInterface for NullIO {
) {
}
- fn ask(&mut self, _question: String, default: PhpMixed) -> PhpMixed {
+ fn ask(&self, _question: String, default: PhpMixed) -> PhpMixed {
default
}
- fn ask_confirmation(&mut self, _question: String, default: bool) -> bool {
+ fn ask_confirmation(&self, _question: String, default: bool) -> bool {
default
}
fn ask_and_validate(
- &mut self,
+ &self,
_question: String,
_validator: Box<dyn Fn(PhpMixed) -> PhpMixed>,
_attempts: Option<i64>,
@@ -76,12 +76,12 @@ impl IOInterface for NullIO {
default
}
- fn ask_and_hide_answer(&mut self, _question: String) -> Option<String> {
+ fn ask_and_hide_answer(&self, _question: String) -> Option<String> {
None
}
fn select(
- &mut self,
+ &self,
_question: String,
_choices: Vec<String>,
default: PhpMixed,