diff options
Diffstat (limited to 'crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs')
| -rw-r--r-- | crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs | 916 |
1 files changed, 0 insertions, 916 deletions
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs b/crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs deleted file mode 100644 index 91fb5bf0..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs +++ /dev/null @@ -1,916 +0,0 @@ -//! ref: composer/vendor/symfony/console/Helper/QuestionHelper.php - -use crate::symfony::console::cursor::Cursor; -use crate::symfony::console::exception::missing_input_exception::MissingInputException; -use crate::symfony::console::exception::runtime_exception::RuntimeException; -use crate::symfony::console::formatter::output_formatter::OutputFormatter; -use crate::symfony::console::formatter::output_formatter_style::OutputFormatterStyle; -use crate::symfony::console::helper::formatter_helper::FormatBlockMessages; -use crate::symfony::console::helper::helper::Helper; -use crate::symfony::console::helper::helper_interface::HelperInterface; -use crate::symfony::console::helper::helper_set::HelperSet; -use crate::symfony::console::input::input_interface::InputInterface; -use crate::symfony::console::output::console_output::ConsoleOutput; -use crate::symfony::console::output::console_output_interface::ConsoleOutputInterface; -use crate::symfony::console::output::console_section_output::ConsoleSectionOutput; -use crate::symfony::console::output::output_interface; -use crate::symfony::console::output::output_interface::OutputInterface; -use crate::symfony::console::question::ChoiceQuestion; -use crate::symfony::console::question::QuestionInterface; -use crate::symfony::console::terminal::Terminal; -use shirabe_php_shim::PhpMixed; -use shirabe_symfony_string::s; - -/// The QuestionHelper class provides helpers to interact with the user. -#[derive(Debug, Default)] -pub struct QuestionHelper { - pub(crate) inner: Helper, - - /// @var resource|null - input_stream: Option<shirabe_php_shim::PhpResource>, -} - -/// self::$stty -static STTY: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true); -/// self::$stdinIsInteractive -static STDIN_IS_INTERACTIVE: std::sync::Mutex<Option<bool>> = std::sync::Mutex::new(None); - -/// PHP dispatches `$this->writePrompt()` / `$this->writeError()` virtually, and -/// `SymfonyQuestionHelper` overrides both protected methods. The embedded-super port expresses -/// that late binding as a trait: the template methods are provided here and reach the overridable -/// hooks through `Self`, while `inner()` reaches the base-class state. PHP has no such interface; -/// the invented name follows the `QuestionInterface` precedent. -pub trait QuestionHelperInterface { - fn inner(&self) -> &QuestionHelper; - - fn inner_mut(&mut self) -> &mut QuestionHelper; - - /// Asks a question to the user. - /// - /// @return mixed The user answer - /// - /// @throws RuntimeException If there is no data to read in the input stream - fn ask( - &mut self, - input: &mut dyn InputInterface, - output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, - question: &impl QuestionInterface, - ) -> anyhow::Result<Result<PhpMixed, MissingInputException>> { - let mut output = output; - let error_output = { - let borrowed = output.borrow(); - (*borrowed) - .as_any() - .downcast_ref::<ConsoleOutput>() - .map(|console_output| console_output.get_error_output()) - }; - if let Some(error_output) = error_output { - output = error_output; - } - - if !input.is_interactive() { - return Ok(Ok(self.inner().get_default_answer(question))); - } - - if let Some(streamable) = input.as_streamable() - && let Some(stream) = streamable.get_stream() - { - self.inner_mut().input_stream = Some(stream); - } - - let result: anyhow::Result<Result<PhpMixed, MissingInputException>> = (|| { - if question.get_validator().is_none() { - return self.do_ask(std::rc::Rc::clone(&output), question); - } - - let interviewer = || self.do_ask(std::rc::Rc::clone(&output), question); - - self.validate_attempts(&interviewer, std::rc::Rc::clone(&output), question) - })(); - - let result = result?; - match result { - Ok(value) => Ok(Ok(value)), - Err(exception) => { - input.set_interactive(false); - - let fallback_output = self.inner().get_default_answer(question); - if matches!(fallback_output, PhpMixed::Null) { - return Ok(Err(exception)); - } - - Ok(Ok(fallback_output)) - } - } - } - - /// Asks the question to the user (PHP private; on the trait so it can late-bind the hooks). - /// - /// @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden - fn do_ask( - &self, - output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, - question: &impl QuestionInterface, - ) -> anyhow::Result<Result<PhpMixed, MissingInputException>> { - self.write_prompt(std::rc::Rc::clone(&output), question); - - let input_stream = self - .inner() - .input_stream - .clone() - .unwrap_or_else(shirabe_php_shim::stdin); - let autocomplete = question.get_autocompleter_callback(); - - let ret: PhpMixed; - - if let Some(autocomplete) = autocomplete - && STTY.load(std::sync::atomic::Ordering::SeqCst) - && Terminal::has_stty_available() - { - let callback = autocomplete; - // The autocompleter callback yields an iterable (Option here); PHP - // treats a null result as an empty list of suggestions. - let callback = move |input: &str| callback(input).unwrap_or_default(); - let autocomplete = match self.inner().autocomplete( - std::rc::Rc::clone(&output), - question, - &input_stream, - &callback, - ) { - Ok(value) => value, - Err(exception) => return Ok(Err(exception)), - }; - ret = PhpMixed::String(if question.is_trimmable() { - shirabe_php_shim::trim(&autocomplete, None) - } else { - autocomplete - }); - } else { - let mut r: PhpMixed = PhpMixed::Bool(false); - if question.is_hidden() { - match self.inner().get_hidden_response( - std::rc::Rc::clone(&output), - &input_stream, - question.is_trimmable(), - )? { - Ok(hidden_response) => { - r = PhpMixed::String(if question.is_trimmable() { - shirabe_php_shim::trim(&hidden_response, None) - } else { - hidden_response - }); - } - Err(e) => { - if !question.is_hidden_fallback() { - return Err(e.into()); - } - } - } - } - - if matches!(r, PhpMixed::Bool(false)) { - let is_blocked = shirabe_php_shim::stream_get_meta_data(&input_stream) - .get("blocked") - .cloned() - .unwrap_or(PhpMixed::Bool(true)); - - if !shirabe_php_shim::boolval(&is_blocked) { - shirabe_php_shim::stream_set_blocking(&input_stream, true); - } - - let read = self.inner().read_input(&input_stream, question); - - if !shirabe_php_shim::boolval(&is_blocked) { - shirabe_php_shim::stream_set_blocking(&input_stream, false); - } - - if matches!(read, PhpMixed::Bool(false)) { - return Ok(Err(MissingInputException::new("Aborted.".to_string()))); - } - r = read; - if question.is_trimmable() { - r = PhpMixed::String(shirabe_php_shim::trim(&r.to_string(), None)); - } - } - ret = r; - } - - let mut ret = ret; - { - let borrowed = output.borrow(); - if let Some(section_output) = - (*borrowed).as_any().downcast_ref::<ConsoleSectionOutput>() - { - section_output.add_content(&ret.to_string()); - } - } - - ret = if shirabe_php_shim::strlen(&ret.to_string()) > 0 { - ret - } else { - question.get_default() - }; - - if let Some(normalizer) = question.get_normalizer() { - return Ok(Ok(normalizer(ret))); - } - - Ok(Ok(ret)) - } - - /// Validates an attempt (PHP private; on the trait so it can late-bind the hooks). - /// - /// @param callable $interviewer A callable that will ask for a question and return the result - /// - /// @return mixed The validated response - /// - /// @throws \Exception In case the max number of attempts has been reached and no valid response has been given - fn validate_attempts( - &self, - interviewer: &dyn Fn() -> anyhow::Result<Result<PhpMixed, MissingInputException>>, - output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, - question: &impl QuestionInterface, - ) -> anyhow::Result<Result<PhpMixed, MissingInputException>> { - let mut error: Option<shirabe_php_shim::Exception> = None; - let mut attempts = question.get_max_attempts(); - - loop { - // while (null === $attempts || $attempts--) - match attempts { - None => {} - Some(0) => break, - Some(n) => attempts = Some(n - 1), - } - - if let Some(ref error) = error { - self.write_error(std::rc::Rc::clone(&output), error); - } - - let interviewed = match interviewer()? { - Ok(value) => value, - Err(missing) => return Ok(Err(missing)), - }; - - match question.get_validator().unwrap()(Some(interviewed)) { - Ok(value) => return Ok(Ok(value)), - Err(e) => { - // PHP: `catch (RuntimeException $e) { throw $e; } catch (\Exception $error) {}`. - // The validator return type is fixed to InvalidArgumentException here, so the - // RuntimeException rethrow branch is statically unreachable; record the error - // and retry. - error = Some(shirabe_php_shim::Exception::with_code( - e.get_message().to_string(), - e.get_code(), - )); - } - } - } - - // throw $error; - Err(anyhow::Error::msg( - error - .map(|e| e.get_message().to_string()) - .unwrap_or_default(), - )) - } - - /// Outputs the question prompt (PHP protected; the overridable hook). - fn write_prompt( - &self, - output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, - question: &impl QuestionInterface, - ) { - self.inner().write_prompt(output, question); - } - - /// Outputs an error message (PHP protected; the overridable hook). - fn write_error( - &self, - output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, - error: &shirabe_php_shim::Exception, - ) { - self.inner().write_error(output, error); - } -} - -impl QuestionHelperInterface for QuestionHelper { - fn inner(&self) -> &QuestionHelper { - self - } - - fn inner_mut(&mut self) -> &mut QuestionHelper { - self - } -} - -impl QuestionHelper { - pub fn get_name(&self) -> String { - "question".to_string() - } - - /// Prevents usage of stty. - pub fn disable_stty() { - STTY.store(false, std::sync::atomic::Ordering::SeqCst); - } - - fn get_default_answer(&self, question: &impl QuestionInterface) -> PhpMixed { - let default = question.get_default(); - - if matches!(default, PhpMixed::Null) { - return default; - } - - if let Some(validator) = question.get_validator() { - // call_user_func($question->getValidator(), $default) - return validator(Some(default)).unwrap(); - } else if let Some(choice_question) = question.as_choice() { - let choices = choice_question.get_choices(); - - if !choice_question.is_multiselect() { - return choices - .get(&default.to_string()) - .cloned() - .unwrap_or(default); - } - - let default_parts = shirabe_php_shim::explode(",", &default.to_string()); - let mut resolved: indexmap::IndexMap<String, PhpMixed> = indexmap::IndexMap::new(); - for (k, v) in default_parts.iter().enumerate() { - let v = if question.is_trimmable() { - shirabe_php_shim::trim(v, None) - } else { - v.clone() - }; - let value = choices.get(&v).cloned().unwrap_or(PhpMixed::String(v)); - resolved.insert(k.to_string(), value); - } - - return PhpMixed::Array(resolved); - } - - default - } - - /// Outputs the question prompt. - pub(crate) fn write_prompt( - &self, - output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, - question: &impl QuestionInterface, - ) { - let mut message = question.get_question().to_string(); - - if let Some(choice_question) = question.as_choice() { - let mut lines = vec![question.get_question().to_string()]; - lines.extend(self.format_choice_question_choices(choice_question, "info")); - output - .borrow() - .writeln(&lines, output_interface::OUTPUT_NORMAL); - - message = choice_question.get_prompt().to_string(); - } - - output - .borrow() - .write(&[message], false, output_interface::OUTPUT_NORMAL); - } - - pub(crate) fn format_choice_question_choices( - &self, - question: &ChoiceQuestion, - tag: &str, - ) -> Vec<String> { - let mut messages: Vec<String> = vec![]; - - let choices = question.get_choices(); - let max_width = choices - .keys() - .map(|key| Helper::width(key)) - .max() - .unwrap_or(0); - - for (key, value) in choices { - let padding = - shirabe_php_shim::str_repeat(" ", (max_width - Helper::width(key)) as usize); - - messages.push(format!( - " [<{tag}>{}{padding}</{tag}>] {}", - key.clone(), - value.clone(), - )); - } - - messages - } - - /// Outputs an error message. - pub(crate) fn write_error( - &self, - output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, - error: &shirabe_php_shim::Exception, - ) { - let message = if let Some(helper_set) = self.get_helper_set() { - let formatter = helper_set.borrow().get_formatter(); - - formatter.borrow().format_block( - FormatBlockMessages::String(error.get_message().to_string()), - "error", - false, - ) - } else { - format!("<error>{}</error>", error.get_message()) - }; - - output - .borrow() - .writeln(&[message], output_interface::OUTPUT_NORMAL); - } - - /// Autocompletes a question. - fn autocomplete( - &self, - output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, - question: &impl QuestionInterface, - input_stream: &shirabe_php_shim::PhpResource, - autocomplete: &dyn Fn(&str) -> Vec<PhpMixed>, - ) -> Result<String, MissingInputException> { - let cursor = Cursor::new(std::rc::Rc::clone(&output), Some(input_stream.clone())); - - let mut full_choice = String::new(); - let mut ret = String::new(); - - let mut i: i64 = 0; - let mut ofs: i64 = -1; - let mut matches = autocomplete(&ret); - let mut num_matches = matches.len() as i64; - - let stty_mode = shirabe_php_shim::shell_exec("stty -g").unwrap_or_default(); - let is_stdin = shirabe_php_shim::stream_get_meta_data(input_stream) - .get("uri") - .map(|uri| uri.to_string() == "php://stdin") - .unwrap_or(false); - let mut r = vec![input_stream.clone()]; - let w: Vec<shirabe_php_shim::PhpResource> = vec![]; - - // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) - shirabe_php_shim::shell_exec("stty -icanon -echo"); - - // Add highlighted text style - output.borrow().get_formatter().borrow_mut().set_style( - "hl", - Box::new(OutputFormatterStyle::new( - Some("black"), - Some("white"), - vec![], - )), - ); - - // Read a keypress - while !shirabe_php_shim::feof(input_stream) { - while is_stdin - && Some(0) - == shirabe_php_shim::stream_select( - &mut r, - &mut w.clone(), - &mut w.clone(), - 0, - Some(100), - ) - { - // Give signal handlers a chance to run - r = vec![input_stream.clone()]; - } - let mut c = shirabe_php_shim::fread(input_stream, 1); - - // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false. - if c.is_none() - || (ret.is_empty() - && c.as_deref() == Some("") - && matches!(question.get_default(), PhpMixed::Null)) - { - shirabe_php_shim::shell_exec(&format!("stty {}", stty_mode)); - return Err(MissingInputException::new("Aborted.".to_string())); - } else if c.as_deref() == Some("\u{7f}") { - // Backspace Character - if 0 == num_matches && 0 != i { - i -= 1; - cursor.move_left(s(&full_choice).slice(-1, None).width(false)); - - full_choice = QuestionHelper::substr(Some(&full_choice), 0, Some(i)); - } - - if 0 == i { - ofs = -1; - matches = autocomplete(&ret); - num_matches = matches.len() as i64; - } else { - num_matches = 0; - } - - // Pop the last character off the end of our string - ret = QuestionHelper::substr(Some(&ret), 0, Some(i)); - } else if c.as_deref() == Some("\u{1b}") { - // Did we read an escape sequence? - let escape = shirabe_php_shim::fread(input_stream, 2).unwrap_or_default(); - let cc = format!("{}{}", c.clone().unwrap_or_default(), escape); - c = Some(cc.clone()); - - // A = Up Arrow. B = Down Arrow - let c2 = cc.as_bytes().get(2).copied(); - if c2 == Some(b'A') || c2 == Some(b'B') { - if c2 == Some(b'A') && -1 == ofs { - ofs = 0; - } - - if 0 == num_matches { - continue; - } - - ofs += if c2 == Some(b'A') { -1 } else { 1 }; - ofs = (num_matches + ofs) % num_matches; - } - } else if shirabe_php_shim::ord(c.as_deref().unwrap_or("")) < 32 { - if c.as_deref() == Some("\t") || c.as_deref() == Some("\n") { - if num_matches > 0 && -1 != ofs { - ret = matches[ofs as usize].to_string(); - // Echo out remaining chars for current match - let remaining_characters = shirabe_php_shim::substr( - &ret, - shirabe_php_shim::strlen(&shirabe_php_shim::trim( - &self.most_recently_entered_value(&full_choice), - None, - )), - None, - ); - output.borrow().write( - std::slice::from_ref(&remaining_characters), - false, - output_interface::OUTPUT_NORMAL, - ); - full_choice.push_str(&remaining_characters); - i = match shirabe_php_shim::mb_detect_encoding(&full_choice, None, true) { - None => shirabe_php_shim::strlen(&full_choice), - Some(encoding) => shirabe_php_shim::mb_strlen(&full_choice, &encoding), - }; - - let ret_for_filter = ret.clone(); - matches = autocomplete(&ret) - .into_iter() - .filter(|m| { - ret_for_filter.is_empty() - || shirabe_php_shim::str_starts_with( - &m.to_string(), - &ret_for_filter, - ) - }) - .collect(); - ofs = -1; - } - - if c.as_deref() == Some("\n") { - output.borrow().write( - &[c.unwrap_or_default()], - false, - output_interface::OUTPUT_NORMAL, - ); - break; - } - - num_matches = 0; - } - - continue; - } else { - let cur = c.clone().unwrap_or_default(); - if "\u{80}" <= cur.as_str() { - let len = match shirabe_php_shim::str_bitand(&cur, "\u{f0}").as_str() { - "\u{c0}" => 1, - "\u{d0}" => 1, - "\u{e0}" => 2, - "\u{f0}" => 3, - _ => 0, - }; - let extra = shirabe_php_shim::fread(input_stream, len).unwrap_or_default(); - c = Some(format!("{}{}", cur, extra)); - } - - let cur = c.clone().unwrap_or_default(); - output.borrow().write( - std::slice::from_ref(&cur), - false, - output_interface::OUTPUT_NORMAL, - ); - ret.push_str(&cur); - full_choice.push_str(&cur); - i += 1; - - let mut temp_ret = ret.clone(); - - if let Some(choice_question) = question.as_choice() - && choice_question.is_multiselect() - { - temp_ret = self.most_recently_entered_value(&full_choice); - } - - num_matches = 0; - ofs = 0; - - for value in autocomplete(&ret) { - // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) - if shirabe_php_shim::str_starts_with(&value.to_string(), &temp_ret) { - if (num_matches as usize) < matches.len() { - matches[num_matches as usize] = value; - } else { - matches.push(value); - } - num_matches += 1; - } - } - } - - cursor.clear_line_after(); - - if num_matches > 0 && -1 != ofs { - cursor.save_position(); - // Write highlighted text, complete the partially entered response - let characters_entered = shirabe_php_shim::strlen(&shirabe_php_shim::trim( - &self.most_recently_entered_value(&full_choice), - None, - )); - output.borrow().write( - &[format!( - "<hl>{}</hl>", - OutputFormatter::escape_trailing_backslash(&shirabe_php_shim::substr( - &matches[ofs as usize].to_string(), - characters_entered, - None, - )) - )], - false, - output_interface::OUTPUT_NORMAL, - ); - cursor.restore_position(); - } - } - - // Reset stty so it behaves normally again - shirabe_php_shim::shell_exec(&format!("stty {}", stty_mode)); - - Ok(full_choice) - } - - fn most_recently_entered_value(&self, entered: &str) -> String { - // Determine the most recent value that the user entered - if !shirabe_php_shim::str_contains(entered, ",") { - return entered.to_string(); - } - - let choices = shirabe_php_shim::explode(",", entered); - let last_choice = shirabe_php_shim::trim(&choices[choices.len() - 1], None); - if !last_choice.is_empty() { - return last_choice; - } - - entered.to_string() - } - - /// Gets a hidden response from user. - /// - /// @param resource $inputStream The handler resource - /// @param bool $trimmable Is the answer trimmable - /// - /// @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden - fn get_hidden_response( - &self, - output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, - input_stream: &shirabe_php_shim::PhpResource, - trimmable: bool, - ) -> anyhow::Result<Result<String, RuntimeException>> { - if cfg!(windows) { - let mut exe = format!( - "{}/../Resources/bin/hiddeninput.exe", - shirabe_php_shim::dir() - ); - - // handle code running from a phar - let mut tmp_exe: Option<String> = None; - if shirabe_php_shim::substr(&magic_file(), 0, Some(5)) == "phar:" { - let tmp = format!("{}/hiddeninput.exe", shirabe_php_shim::sys_get_temp_dir()); - shirabe_php_shim::copy(&exe, &tmp); - exe = tmp.clone(); - tmp_exe = Some(tmp); - } - - let s_exec = shirabe_php_shim::shell_exec(&format!("\"{}\"", exe)).unwrap_or_default(); - let value = if trimmable { - shirabe_php_shim::rtrim(&s_exec, None) - } else { - s_exec - }; - output - .borrow() - .writeln(&["".to_string()], output_interface::OUTPUT_NORMAL); - - if let Some(tmp) = tmp_exe { - shirabe_php_shim::unlink(&tmp); - } - - return Ok(Ok(value)); - } - - let mut stty_mode = String::new(); - if STTY.load(std::sync::atomic::Ordering::SeqCst) && Terminal::has_stty_available() { - stty_mode = shirabe_php_shim::shell_exec("stty -g").unwrap_or_default(); - shirabe_php_shim::shell_exec("stty -echo"); - } else if self.is_interactive_input(input_stream) { - return Ok(Err(RuntimeException::new( - "Unable to hide the response.".to_string(), - ))); - } - - let value = shirabe_php_shim::fgets(input_stream, Some(4096)); - - if STTY.load(std::sync::atomic::Ordering::SeqCst) && Terminal::has_stty_available() { - shirabe_php_shim::shell_exec(&format!("stty {}", stty_mode)); - } - - let mut value = match value { - Some(value) => value, - None => { - return Err(MissingInputException::new("Aborted.".to_string()).into()); - } - }; - if trimmable { - value = shirabe_php_shim::trim(&value, None); - } - output - .borrow() - .writeln(&["".to_string()], output_interface::OUTPUT_NORMAL); - - Ok(Ok(value)) - } - - fn is_interactive_input(&self, input_stream: &shirabe_php_shim::PhpResource) -> bool { - let uri = shirabe_php_shim::stream_get_meta_data(input_stream) - .get("uri") - .map(|uri| uri.to_string()); - if uri.as_deref() != Some("php://stdin") { - return false; - } - - let mut stdin_is_interactive = STDIN_IS_INTERACTIVE.lock().unwrap(); - if let Some(value) = *stdin_is_interactive { - return value; - } - - let value = shirabe_php_shim::stream_isatty_resource( - &shirabe_php_shim::php_fopen_resource("php://stdin", "r"), - ); - *stdin_is_interactive = Some(value); - value - } - - /// Reads one or more lines of input and returns what is read. - /// - /// @param resource $inputStream The handler resource - /// @param Question $question The question being asked - /// - /// @return string|false The input received, false in case input could not be read - fn read_input( - &self, - input_stream: &shirabe_php_shim::PhpResource, - question: &impl QuestionInterface, - ) -> PhpMixed { - if !question.is_multiline() { - let cp = self.set_io_codepage(); - let ret = shirabe_php_shim::fgets(input_stream, Some(4096)); - - return self.reset_io_codepage( - cp, - ret.map(PhpMixed::String).unwrap_or(PhpMixed::Bool(false)), - ); - } - - let multi_line_stream_reader = self.clone_input_stream(input_stream); - let multi_line_stream_reader = match multi_line_stream_reader { - Some(reader) => reader, - None => return PhpMixed::Bool(false), - }; - - let mut ret = String::new(); - let cp = self.set_io_codepage(); - loop { - let char = shirabe_php_shim::fgetc(&multi_line_stream_reader); - let char = match char { - Some(char) => char, - None => break, - }; - if shirabe_php_shim::PHP_EOL == format!("{}{}", ret, char) { - break; - } - ret.push_str(&char); - } - - self.reset_io_codepage(cp, PhpMixed::String(ret)) - } - - /// Sets console I/O to the host code page. - /// - /// @return int Previous code page in IBM/EBCDIC format - fn set_io_codepage(&self) -> i64 { - if shirabe_php_shim::function_exists("sapi_windows_cp_set") { - let cp = shirabe_php_shim::sapi_windows_cp_get(None); - shirabe_php_shim::sapi_windows_cp_set(shirabe_php_shim::sapi_windows_cp_get(Some( - "oem", - ))); - - return cp; - } - - 0 - } - - /// Sets console I/O to the specified code page and converts the user input. - fn reset_io_codepage(&self, cp: i64, input: PhpMixed) -> PhpMixed { - let mut input = input; - if 0 != cp { - shirabe_php_shim::sapi_windows_cp_set(cp); - - if !matches!(input, PhpMixed::Bool(false)) && input.to_string() != "" { - input = PhpMixed::String(shirabe_php_shim::sapi_windows_cp_conv( - shirabe_php_shim::sapi_windows_cp_get(Some("oem")), - cp, - &input.to_string(), - )); - } - } - - input - } - - /// Clones an input stream in order to act on one instance of the same - /// stream without affecting the other instance. - /// - /// @param resource $inputStream The handler resource - /// - /// @return resource|null The cloned resource, null in case it could not be cloned - fn clone_input_stream( - &self, - input_stream: &shirabe_php_shim::PhpResource, - ) -> Option<shirabe_php_shim::PhpResource> { - let stream_meta_data = shirabe_php_shim::stream_get_meta_data(input_stream); - let seekable = stream_meta_data - .get("seekable") - .cloned() - .unwrap_or(PhpMixed::Bool(false)); - let mode = stream_meta_data - .get("mode") - .map(|m| m.to_string()) - .unwrap_or_else(|| "rb".to_string()); - let uri = stream_meta_data.get("uri").map(|u| u.to_string()); - - let uri = uri?; - - let clone_stream = shirabe_php_shim::fopen(&uri, &mode).ok()?; - - // For seekable and writable streams, add all the same data to the - // cloned stream and then seek to the same offset. - if matches!(seekable, PhpMixed::Bool(true)) && !["r", "rb", "rt"].contains(&mode.as_str()) { - let offset = shirabe_php_shim::ftell(input_stream).unwrap_or(0); - shirabe_php_shim::rewind(input_stream); - shirabe_php_shim::stream_copy_to_stream(input_stream, &clone_stream); - shirabe_php_shim::fseek(input_stream, offset, shirabe_php_shim::SEEK_SET); - shirabe_php_shim::fseek(&clone_stream, offset, shirabe_php_shim::SEEK_SET); - } - - Some(clone_stream) - } - - /// Helper::substr proxy (inherited static helper). - fn substr(string: Option<&str>, from: i64, length: Option<i64>) -> String { - Helper::substr(string.unwrap_or(""), from, length) - } -} - -/// PHP `__FILE__` magic constant. The executing code lives in the Shirabe binary itself, which is -/// the closest analogue for a native executable; it never carries the `phar:` scheme, so the -/// phar-relocation branch in `get_hidden_response` correctly never triggers. -fn magic_file() -> String { - std::env::current_exe() - .expect("current executable path") - .display() - .to_string() -} - -impl HelperInterface for QuestionHelper { - fn set_helper_set(&mut self, helper_set: Option<std::rc::Rc<std::cell::RefCell<HelperSet>>>) { - self.inner.set_helper_set(helper_set); - } - - fn get_helper_set(&self) -> Option<std::rc::Rc<std::cell::RefCell<HelperSet>>> { - self.inner.get_helper_set() - } - - fn get_name(&self) -> String { - self.get_name() - } -} |
