added questions and prompt_module macros

This commit is contained in:
Lutetium-Vanadium 2021-05-11 18:18:59 +05:30
parent bf43beabfb
commit 2cc107cffe
217 changed files with 1988 additions and 10 deletions

View File

@ -7,13 +7,15 @@ edition = "2018"
[workspace]
members = [
".",
"inquisition-ui"
"inquisition-ui",
"inquisition-macros",
]
[dependencies]
tempfile = "3"
atty = "0.2"
ui = { package = "inquisition-ui", path = "./inquisition-ui" }
macros = { package = "inquisition-macros", path = "./inquisition-macros" }
ahash = { version = "0.7", optional = true }
async-trait = { version = "0.1", optional = true }
@ -26,6 +28,9 @@ smol-dep = { package = "smol", version = "1.2", optional = true }
tokio-dep = { package = "tokio", version = "1.5", optional = true, features = ["fs", "process", "io-util"] }
async-std-dep = { package = "async-std", version = "1.9", optional = true, features = ["unstable"] }
[dev-dependencies]
trybuild = "1.0.42"
[features]
default = ["crossterm", "ahash"]
crossterm = ["ui/crossterm"]

View File

@ -0,0 +1,14 @@
[package]
name = "inquisition-macros"
version = "0.0.1"
authors = ["Lutetium Vanadium"]
edition = "2018"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1"
syn = { version = "1", features = ["full"] }
quote = "1"
bitflags = "1.2"

View File

@ -0,0 +1,204 @@
use quote::{quote, quote_spanned, ToTokens};
use syn::{parse::Parse, spanned::Spanned, Token};
pub(crate) fn parse_optional_comma(
input: syn::parse::ParseStream,
) -> syn::Result<Option<Token![,]>> {
let lookahead = input.lookahead1();
if !lookahead.peek(Token![,]) {
if input.is_empty() {
return Ok(None);
} else {
return Err(lookahead.error());
}
}
Ok(Some(input.parse::<Token![,]>()?))
}
pub(crate) fn insert_non_dup<T: Parse>(
ident: syn::Ident,
item: &mut Option<T>,
input: syn::parse::ParseStream,
) -> syn::Result<()> {
insert_non_dup_parse(ident, item, input, T::parse)
}
pub(crate) fn insert_non_dup_parse<T>(
ident: syn::Ident,
item: &mut Option<T>,
input: syn::parse::ParseStream,
parser: fn(syn::parse::ParseStream) -> syn::Result<T>,
) -> syn::Result<()> {
match item {
Some(_) => Err(syn::Error::new(
ident.span(),
format!("duplicate option `{}`", ident),
)),
None => {
*item = Some(parser(input)?);
Ok(())
}
}
}
#[allow(clippy::large_enum_variant)]
pub(crate) enum Choices {
Array(syn::punctuated::Punctuated<Choice, Token![,]>),
Expr(syn::Expr),
}
impl Choices {
fn parse_impl(
input: syn::parse::ParseStream,
parser: fn(syn::parse::ParseStream) -> syn::Result<Choice>,
) -> syn::Result<Self> {
if input.peek(syn::token::Bracket) {
let content;
syn::bracketed!(content in input);
Ok(Choices::Array(content.parse_terminated(parser)?))
} else {
Ok(Choices::Expr(input.parse()?))
}
}
pub(crate) fn parse_choice(input: syn::parse::ParseStream) -> syn::Result<Self> {
Choices::parse_impl(input, Choice::parse)
}
pub(crate) fn parse_checkbox_choice(
input: syn::parse::ParseStream,
) -> syn::Result<Self> {
Choices::parse_impl(input, parse_checkbox_choice)
}
}
impl ToTokens for Choices {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
Choices::Array(ref elems) => {
let mut choices = proc_macro2::TokenStream::new();
for elem in elems.iter() {
elem.to_tokens(&mut choices);
choices.extend(quote! { , })
}
tokens.extend(quote_spanned! {
elems.span() => ::std::array::IntoIter::new([ #choices ])
});
}
Choices::Expr(ref choices) => {
choices.to_tokens(tokens);
}
}
}
}
pub(crate) enum Choice {
Choice(syn::Expr),
Separator(syn::Expr),
DefaultSeparator,
}
impl Parse for Choice {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
match input.fork().parse::<syn::Ident>() {
Ok(i) if i == "sep" || i == "separator" => {
input.parse::<syn::Ident>()?;
if input.is_empty() || input.peek(Token![,]) {
Ok(Choice::DefaultSeparator)
} else {
Ok(Choice::Separator(input.parse()?))
}
}
_ => Ok(Choice::Choice(input.parse()?)),
}
}
}
impl ToTokens for Choice {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let (ident_name, elem) = match self {
Choice::Choice(elem) => ("Choice", elem),
Choice::Separator(elem) => ("Separator", elem),
Choice::DefaultSeparator => {
tokens.extend(quote! { ::inquisition::DefaultSeparator });
return;
}
};
let ident = syn::Ident::new(ident_name, elem.span());
tokens.extend(quote_spanned! {
elem.span() => ::inquisition::#ident(
#[allow(clippy::useless_conversion)]
#elem.into()
)
})
}
}
fn make_into(expr: syn::Expr) -> syn::Expr {
syn::ExprMethodCall {
attrs: Vec::new(),
dot_token: syn::token::Dot(expr.span()),
method: syn::Ident::new("into", expr.span()),
paren_token: syn::token::Paren(expr.span()),
turbofish: None,
args: syn::punctuated::Punctuated::new(),
receiver: Box::new(expr),
}
.into()
}
// For checkbox, defaults can be given for each option, this method, takes option
// (`choice`), and the default value to put (`default`), and produces the following as
// an `Expr`:
// ```
// (choice.into(), default.into())`
// ```
fn make_checkbox_tuple(choice: syn::Expr, default: syn::Expr) -> syn::Expr {
let paren_token = syn::token::Paren(
choice
.span()
.join(default.span())
.unwrap_or_else(|| choice.span()),
);
let mut elems = syn::punctuated::Punctuated::new();
elems.push_value(make_into(choice));
elems.push(make_into(default));
syn::ExprTuple {
attrs: Vec::new(),
paren_token,
elems,
}
.into()
}
fn parse_checkbox_choice(input: syn::parse::ParseStream) -> syn::Result<Choice> {
let choice = input.parse()?;
let choice = match choice {
Choice::Choice(choice) if input.peek(Token![default]) => {
input.parse::<Token![default]>()?;
Choice::Choice(make_checkbox_tuple(choice, input.parse()?))
}
Choice::Choice(choice) => {
let span = choice.span();
Choice::Choice(make_checkbox_tuple(
choice,
syn::ExprLit {
lit: syn::LitBool { value: false, span }.into(),
attrs: Vec::new(),
}
.into(),
))
}
sep => sep,
};
Ok(choice)
}

View File

@ -0,0 +1,36 @@
extern crate proc_macro;
use proc_macro::TokenStream;
mod helpers;
mod question;
use question::*;
use quote::quote;
use syn::{parse::Parse, parse_macro_input, Token};
#[proc_macro]
pub fn questions(item: TokenStream) -> TokenStream {
let p = parse_macro_input!(item as Questions);
let questions = p.questions.into_iter();
let ts = quote! {
::std::array::IntoIter::new([
#(#questions),*
])
};
ts.into()
}
struct Questions {
questions: syn::punctuated::Punctuated<Question, Token![,]>,
}
impl Parse for Questions {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Self {
questions: input.parse_terminated(Question::parse)?,
})
}
}

View File

@ -0,0 +1,469 @@
use std::fmt;
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{parse::Parse, spanned::Spanned, Token};
use crate::helpers::*;
bitflags::bitflags! {
pub struct BuilderMethods: u8 {
const DEFAULT = 0b000_0001;
const TRANSFORM = 0b000_0010;
const VAL_FIL = 0b000_0100;
const LIST = 0b000_1000;
const MASK = 0b001_0000;
const EXTENSION = 0b010_0000;
const PLUGIN = 0b100_0000;
}
}
#[derive(Clone, Copy)]
pub(crate) enum QuestionKind {
Input,
Int,
Float,
Confirm,
List,
Rawlist,
Expand,
Checkbox,
Password,
Editor,
Plugin,
}
impl QuestionKind {
fn as_str(&self) -> &str {
match self {
QuestionKind::Input => "input",
QuestionKind::Int => "int",
QuestionKind::Float => "float",
QuestionKind::Confirm => "confirm",
QuestionKind::List => "list",
QuestionKind::Rawlist => "rawlist",
QuestionKind::Expand => "expand",
QuestionKind::Checkbox => "checkbox",
QuestionKind::Password => "password",
QuestionKind::Editor => "editor",
QuestionKind::Plugin => "plugin",
}
}
fn get_builder_methods(&self) -> BuilderMethods {
match *self {
QuestionKind::Input | QuestionKind::Int | QuestionKind::Float => {
BuilderMethods::DEFAULT
| BuilderMethods::TRANSFORM
| BuilderMethods::VAL_FIL
}
QuestionKind::Confirm => {
BuilderMethods::DEFAULT | BuilderMethods::TRANSFORM
}
QuestionKind::List | QuestionKind::Rawlist | QuestionKind::Expand => {
BuilderMethods::DEFAULT
| BuilderMethods::TRANSFORM
| BuilderMethods::LIST
}
QuestionKind::Checkbox => {
BuilderMethods::DEFAULT
| BuilderMethods::TRANSFORM
| BuilderMethods::VAL_FIL
| BuilderMethods::LIST
}
QuestionKind::Password => {
BuilderMethods::TRANSFORM
| BuilderMethods::VAL_FIL
| BuilderMethods::MASK
}
QuestionKind::Editor => {
BuilderMethods::DEFAULT
| BuilderMethods::TRANSFORM
| BuilderMethods::VAL_FIL
| BuilderMethods::EXTENSION
}
QuestionKind::Plugin => BuilderMethods::PLUGIN,
}
}
}
impl Parse for QuestionKind {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let ident = input.parse::<syn::Ident>()?;
let kind = if ident == "input" {
QuestionKind::Input
} else if ident == "int" {
QuestionKind::Int
} else if ident == "float" {
QuestionKind::Float
} else if ident == "confirm" {
QuestionKind::Confirm
} else if ident == "list" {
QuestionKind::List
} else if ident == "rawlist" {
QuestionKind::Rawlist
} else if ident == "expand" {
QuestionKind::Expand
} else if ident == "checkbox" {
QuestionKind::Checkbox
} else if ident == "password" {
QuestionKind::Password
} else if ident == "editor" {
QuestionKind::Editor
} else if ident == "plugin" {
QuestionKind::Plugin
} else {
return Err(syn::Error::new(
ident.span(),
format!("unknown question kind {}", ident),
));
};
Ok(kind)
}
}
impl fmt::Display for QuestionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
pub(crate) struct QuestionOpts {
pub(crate) message: Option<syn::Expr>,
pub(crate) when: Option<syn::Expr>,
pub(crate) ask_if_answered: Option<syn::Expr>,
pub(crate) default: Option<syn::Expr>,
pub(crate) validate: Option<syn::Expr>,
pub(crate) filter: Option<syn::Expr>,
pub(crate) transform: Option<syn::Expr>,
pub(crate) validate_async: Option<syn::Expr>,
pub(crate) filter_async: Option<syn::Expr>,
pub(crate) transform_async: Option<syn::Expr>,
pub(crate) choices: Option<Choices>,
pub(crate) page_size: Option<syn::Expr>,
pub(crate) should_loop: Option<syn::Expr>,
pub(crate) mask: Option<syn::Expr>,
pub(crate) extension: Option<syn::Expr>,
pub(crate) plugin: Option<syn::Expr>,
}
impl Default for QuestionOpts {
fn default() -> Self {
Self {
message: None,
when: None,
ask_if_answered: None,
default: None,
validate: None,
filter: None,
transform: None,
validate_async: None,
filter_async: None,
transform_async: None,
choices: None,
page_size: None,
should_loop: None,
mask: None,
extension: None,
plugin: None,
}
}
}
/// Checks if a _valid_ ident is disallowed
fn check_disallowed(
ident: &syn::Ident,
kind: QuestionKind,
allowed: BuilderMethods,
) -> syn::Result<()> {
#[rustfmt::skip]
fn disallowed(ident: &syn::Ident, allowed: BuilderMethods) -> bool {
(ident == "default" &&
!allowed.contains(BuilderMethods::DEFAULT)) ||
((ident == "transform_async" ||
ident == "transform") &&
!allowed.contains(BuilderMethods::TRANSFORM)) ||
((ident == "validate_async" ||
ident == "validate" ||
ident == "filter" ||
ident == "filter_async") &&
!allowed.contains(BuilderMethods::VAL_FIL)) ||
((ident == "choices" ||
ident == "page_size" ||
ident == "should_loop") &&
!allowed.contains(BuilderMethods::LIST)) ||
(ident == "mask" &&
!allowed.contains(BuilderMethods::MASK)) ||
(ident == "extension" &&
!allowed.contains(BuilderMethods::EXTENSION)) ||
(ident == "plugin" &&
!allowed.contains(BuilderMethods::PLUGIN))
}
if disallowed(ident, allowed) {
Err(syn::Error::new(
ident.span(),
format!("option `{}` does not exist for kind `{}`", ident, kind),
))
} else {
Ok(())
}
}
pub(crate) struct Question {
pub(crate) kind: QuestionKind,
pub(crate) name: syn::Expr,
pub(crate) opts: QuestionOpts,
}
impl Parse for Question {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let kind = QuestionKind::parse(input)?;
let content;
let brace = syn::braced!(content in input);
let mut opts = QuestionOpts::default();
let mut name = None;
let allowed_methods = kind.get_builder_methods();
while !content.is_empty() {
if content.peek(Token![async]) {
let asynct = content.parse::<Token![async]>()?;
let ident = content.parse::<syn::Ident>()?;
content.parse::<Token![:]>()?;
let full_ident_str = format!("{}_async", ident);
let full_ident = syn::Ident::new(
&full_ident_str,
asynct
.span
.join(ident.span())
.unwrap_or_else(|| ident.span()),
);
check_disallowed(&full_ident, kind, allowed_methods)?;
if ident == "validate" {
insert_non_dup(full_ident, &mut opts.validate_async, &content)?;
} else if ident == "filter" {
insert_non_dup(full_ident, &mut opts.filter_async, &content)?;
} else if ident == "transform" {
insert_non_dup(full_ident, &mut opts.transform_async, &content)?;
} else {
return Err(syn::Error::new(
ident.span(),
format!("unknown question option `{}`", full_ident_str),
));
}
} else {
let ident = content.parse::<syn::Ident>()?;
content.parse::<Token![:]>()?;
// it is not an issue if ident doesn't correspond to valid option
// since check_allowed only checks if valid idents are disallowed
check_disallowed(&ident, kind, allowed_methods)?;
// default options which are always there
if ident == "name" {
insert_non_dup(ident, &mut name, &content)?;
} else if ident == "message" {
insert_non_dup(ident, &mut opts.message, &content)?;
} else if ident == "when" {
insert_non_dup(ident, &mut opts.when, &content)?;
} else if ident == "ask_if_answered" {
insert_non_dup(ident, &mut opts.ask_if_answered, &content)?;
} else {
// the rest may or may not be there, so must be checked
// it is not an issue if ident doesn't correspond to valid option
// since check_allowed only checks if valid idents are disallowed
check_disallowed(&ident, kind, allowed_methods)?;
if ident == "default" {
insert_non_dup(ident, &mut opts.default, &content)?;
} else if ident == "validate" {
insert_non_dup(ident, &mut opts.validate, &content)?;
} else if ident == "filter" {
insert_non_dup(ident, &mut opts.filter, &content)?;
} else if ident == "transform" {
insert_non_dup(ident, &mut opts.transform, &content)?;
} else if ident == "choices" {
match kind {
QuestionKind::Checkbox => insert_non_dup_parse(
ident,
&mut opts.choices,
&content,
Choices::parse_checkbox_choice,
)?,
_ => insert_non_dup_parse(
ident,
&mut opts.choices,
&content,
Choices::parse_choice,
)?,
}
} else if ident == "page_size" {
insert_non_dup(ident, &mut opts.page_size, &content)?;
} else if ident == "should_loop" {
insert_non_dup(ident, &mut opts.should_loop, &content)?;
} else if ident == "mask" {
insert_non_dup(ident, &mut opts.mask, &content)?;
} else if ident == "extension" {
insert_non_dup(ident, &mut opts.extension, &content)?;
} else if ident == "plugin" {
insert_non_dup(ident, &mut opts.plugin, &content)?;
} else {
return Err(syn::Error::new(
ident.span(),
format!("unknown question option `{}`", ident),
));
}
}
}
if parse_optional_comma(&content)?.is_none() {
break;
}
}
if let QuestionKind::Plugin = kind {
if opts.plugin.is_none() {
return Err(syn::Error::new(
brace.span,
"missing required option `plugin`",
));
}
}
Ok(Self {
kind,
name: name.ok_or_else(|| {
syn::Error::new(brace.span, "missing required option `name`")
})?,
opts,
})
}
}
impl Question {
fn write_main_opts(&self, tokens: &mut TokenStream) {
if let Some(ref message) = self.opts.message {
tokens.extend(quote_spanned! { message.span() => .message(#message) });
}
if let Some(ref when) = self.opts.when {
tokens.extend(quote_spanned! { when.span() => .when(#when) });
}
if let Some(ref ask_if_answered) = self.opts.ask_if_answered {
tokens.extend(quote_spanned! {
ask_if_answered.span() => .ask_if_answered(#ask_if_answered)
});
}
}
}
impl quote::ToTokens for Question {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = &self.name;
if let QuestionKind::Plugin = self.kind {
let plugin = self.opts.plugin.as_ref().unwrap();
// If just the name was passed into Question::plugin, type errors associated
// with its conversion to a string would take the span _including_ that of
// plugin. Explicitly performing `String::from`, makes the error span due to
// the `From` trait will show the span of the name only
let name = quote_spanned! {
name.span() => String::from(#name)
};
tokens.extend(quote_spanned! {
plugin.span() => ::inquisition::Question::plugin(#name, #plugin)
});
self.write_main_opts(tokens);
tokens.extend(quote! { .build() });
return;
}
let kind = syn::Ident::new(self.kind.as_str(), name.span());
tokens.extend(quote_spanned! {
name.span() => ::inquisition::Question::#kind(#name)
});
self.write_main_opts(tokens);
if let Some(ref default) = self.opts.default {
tokens.extend(quote_spanned! { default.span() => .default(#default) });
}
if let Some(ref validate) = self.opts.validate {
tokens
.extend(quote_spanned! { validate.span() => .validate(#validate) });
}
if let Some(ref validate_async) = self.opts.validate_async {
tokens.extend(quote_spanned! {
validate_async.span() => .validate_async(#validate_async)
});
}
if let Some(ref filter) = self.opts.filter {
tokens.extend(quote_spanned! { filter.span() => .filter(#filter) });
}
if let Some(ref filter_async) = self.opts.filter_async {
tokens.extend(quote_spanned! {
filter_async.span() => .filter_async(#filter_async)
});
}
if let Some(ref transform) = self.opts.transform {
tokens.extend(
quote_spanned! { transform.span() => .transform(#transform) },
);
}
if let Some(ref transform_async) = self.opts.transform_async {
tokens.extend(quote_spanned! {
transform_async.span() => .transform_async(#transform_async)
});
}
if let Some(ref choices) = self.opts.choices {
tokens.extend(match self.kind {
QuestionKind::Checkbox => {
quote_spanned! { choices.span() => .choices_with_default(#choices) }
}
_ => quote_spanned! { choices.span() => .choices(#choices) },
});
}
if let Some(ref page_size) = self.opts.page_size {
tokens.extend(
quote_spanned! { page_size.span() => .page_size(#page_size) },
);
}
if let Some(ref should_loop) = self.opts.should_loop {
tokens.extend(
quote_spanned! { should_loop.span() => .should_loop(#should_loop) },
);
}
if let Some(ref mask) = self.opts.mask {
tokens.extend(quote_spanned! { mask.span() => .mask(#mask) });
}
if let Some(ref extension) = self.opts.extension {
tokens.extend(
quote_spanned! { extension.span() => .extension(#extension) },
);
}
tokens.extend(quote! { .build() });
}
}

View File

@ -124,9 +124,12 @@ pub struct ExpandItem {
pub name: String,
}
impl From<(char, String)> for ExpandItem {
fn from((key, name): (char, String)) -> Self {
Self { key, name }
impl<I: Into<String>> From<(char, I)> for ExpandItem {
fn from((key, name): (char, I)) -> Self {
Self {
key,
name: name.into(),
}
}
}

View File

@ -4,11 +4,27 @@ pub mod question;
use ui::{backend, error, events};
pub use answer::{Answer, Answers, ExpandItem, ListItem};
pub use macros::questions;
pub use question::{
Choice::Choice, Choice::DefaultSeparator, Choice::Separator, Question,
};
pub use ui::error::{ErrorKind, Result};
#[macro_export]
macro_rules! prompt_module {
($($tt:tt)*) => {
$crate::PromptModule::new($crate::questions! [ $($tt)* ])
};
}
pub mod plugin {
pub use crate::{question::Plugin, Answer, Answers};
pub use ui::{backend::Backend, events::Events};
crate::cfg_async! {
pub use ui::events::AsyncEvents;
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PromptModule<Q> {
questions: Q,

View File

@ -84,7 +84,7 @@ pub enum Choice<T> {
impl<T> Choice<T> {
pub(crate) fn is_separator(&self) -> bool {
matches!(self, Choice::Separator(_))
matches!(self, Choice::Separator(_) | Choice::DefaultSeparator)
}
pub(crate) fn as_ref(&self) -> Choice<&T> {
@ -96,10 +96,9 @@ impl<T> Choice<T> {
}
pub(crate) fn unwrap_choice(self) -> T {
if let Choice::Choice(c) = self {
c
} else {
panic!("Called unwrap_choice on separator")
match self {
Choice::Choice(c) => c,
_ => panic!("Called unwrap_choice on separator"),
}
}
}

View File

@ -266,7 +266,7 @@ pub struct RawlistBuilder<'m, 'w, 't> {
impl<'m, 'w, 't> RawlistBuilder<'m, 'w, 't> {
pub(crate) fn new(name: String) -> Self {
RawlistBuilder {
opts: Options::new(name.into()),
opts: Options::new(name),
list: Default::default(),
// It is one indexed for the user
choice_count: 1,

208
tests/macros.rs Normal file
View File

@ -0,0 +1,208 @@
struct Runner {
cases: trybuild::TestCases,
name: &'static str,
}
impl Runner {
fn new(name: &'static str) -> Self {
Self {
cases: trybuild::TestCases::new(),
name,
}
}
fn compile_fail(&self, test_name: &str) {
self.cases
.compile_fail(format!("tests/macros/{}/{}.rs", self.name, test_name))
}
fn pass(&self, test_name: &str) {
self.cases
.pass(format!("tests/macros/{}/{}.rs", self.name, test_name))
}
}
#[test]
fn duplicate() {
let t = Runner::new("duplicate");
t.compile_fail("name");
t.compile_fail("message");
t.compile_fail("when");
t.compile_fail("ask_if_answered");
t.compile_fail("default");
t.compile_fail("validate");
t.compile_fail("filter");
t.compile_fail("transform");
t.compile_fail("validate_async");
t.compile_fail("filter_async");
t.compile_fail("transform_async");
t.compile_fail("choices");
t.compile_fail("page_size");
t.compile_fail("should_loop");
t.compile_fail("mask");
t.compile_fail("extension");
t.compile_fail("plugin");
}
#[test]
fn unknown() {
let t = Runner::new("unknown");
t.compile_fail("kind");
t.compile_fail("option");
t.compile_fail("async-unknown");
t.compile_fail("async-option");
}
#[test]
fn checkbox() {
let t = Runner::new("checkbox");
t.pass("valid");
t.compile_fail("default");
t.compile_fail("default_with_sep");
t.compile_fail("mask");
t.compile_fail("extension");
t.compile_fail("plugin");
}
#[test]
fn confirm() {
let t = Runner::new("confirm");
t.pass("valid");
t.compile_fail("filter");
t.compile_fail("filter_async");
t.compile_fail("validate");
t.compile_fail("validate_async");
t.compile_fail("choices");
t.compile_fail("should_loop");
t.compile_fail("page_size");
t.compile_fail("mask");
t.compile_fail("extension");
t.compile_fail("plugin");
}
#[test]
fn editor() {
let t = Runner::new("editor");
t.pass("valid");
t.compile_fail("choices");
t.compile_fail("should_loop");
t.compile_fail("page_size");
t.compile_fail("mask");
t.compile_fail("plugin");
}
#[test]
fn expand() {
let t = Runner::new("expand");
t.pass("valid");
t.compile_fail("filter");
t.compile_fail("filter_async");
t.compile_fail("validate");
t.compile_fail("validate_async");
t.compile_fail("mask");
t.compile_fail("extension");
t.compile_fail("plugin");
}
#[test]
fn float() {
let t = Runner::new("float");
t.pass("valid");
t.compile_fail("choices");
t.compile_fail("should_loop");
t.compile_fail("page_size");
t.compile_fail("mask");
t.compile_fail("extension");
t.compile_fail("plugin");
}
#[test]
fn input() {
let t = Runner::new("input");
t.pass("valid");
t.compile_fail("choices");
t.compile_fail("should_loop");
t.compile_fail("page_size");
t.compile_fail("mask");
t.compile_fail("extension");
t.compile_fail("plugin");
}
#[test]
fn int() {
let t = Runner::new("int");
t.pass("valid");
t.compile_fail("choices");
t.compile_fail("should_loop");
t.compile_fail("page_size");
t.compile_fail("mask");
t.compile_fail("extension");
t.compile_fail("plugin");
}
#[test]
fn list() {
let t = Runner::new("list");
t.pass("valid");
t.compile_fail("filter");
t.compile_fail("filter_async");
t.compile_fail("validate");
t.compile_fail("validate_async");
t.compile_fail("mask");
t.compile_fail("extension");
t.compile_fail("plugin");
}
#[test]
fn password() {
let t = Runner::new("password");
t.pass("valid");
t.compile_fail("default");
t.compile_fail("choices");
t.compile_fail("should_loop");
t.compile_fail("page_size");
t.compile_fail("extension");
t.compile_fail("plugin");
}
#[test]
fn plugin() {
let t = Runner::new("plugin");
t.pass("valid");
t.compile_fail("default");
t.compile_fail("transform");
t.compile_fail("transform_async");
t.compile_fail("filter");
t.compile_fail("filter_async");
t.compile_fail("validate");
t.compile_fail("validate_async");
t.compile_fail("choices");
t.compile_fail("should_loop");
t.compile_fail("page_size");
t.compile_fail("mask");
t.compile_fail("extension");
}
#[test]
fn rawlist() {
let t = Runner::new("rawlist");
t.pass("valid");
t.compile_fail("filter");
t.compile_fail("filter_async");
t.compile_fail("validate");
t.compile_fail("validate_async");
t.compile_fail("mask");
t.compile_fail("extension");
t.compile_fail("plugin");
}

View File

@ -0,0 +1,3 @@
fn main() {
inquisition::questions![checkbox { default: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: missing required option `name`
--> $DIR/default.rs:2:38
|
2 | inquisition::questions![checkbox { default: todo!() }];
| ^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,10 @@
fn main() {
inquisition::questions! [
checkbox {
name: "name",
choices: [
sep "separator" default true,
],
}
];
}

View File

@ -0,0 +1,5 @@
error: expected `,`
--> $DIR/default_with_sep.rs:6:33
|
6 | sep "separator" default true,
| ^^^^^^^

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![checkbox { extension: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `extension` does not exist for kind `checkbox`
--> $DIR/extension.rs:2:48
|
2 | let q = inquisition::questions![checkbox { extension: todo!() }];
| ^^^^^^^^^

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![checkbox { mask: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `mask` does not exist for kind `checkbox`
--> $DIR/mask.rs:2:48
|
2 | let q = inquisition::questions![checkbox { mask: todo!() }];
| ^^^^

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![checkbox { plugin: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `plugin` does not exist for kind `checkbox`
--> $DIR/plugin.rs:2:48
|
2 | let q = inquisition::questions![checkbox { plugin: todo!() }];
| ^^^^^^

View File

@ -0,0 +1,27 @@
fn main() {
let choice = "choice";
let default_choice = true;
inquisition::questions! [
checkbox {
name: "name",
transform: |_, _, _| Ok(()),
async transform: |_, _, _| Box::pin(async { Ok(()) }),
validate: |_, _| Ok(()),
async validate: |_, _| Box::pin(async { Ok(()) }),
filter: |t, _| t,
async filter: |t, _| Box::pin(async move { t }),
choices: [
sep,
sep "separator",
separator,
separator "separator",
"choice",
"choice" default true,
choice default default_choice || false,
],
page_size: 0,
should_loop: true,
}
];
}

View File

@ -0,0 +1,3 @@
fn main() {
inquisition::questions![confirm { choices: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `choices` does not exist for kind `confirm`
--> $DIR/choices.rs:2:39
|
2 | inquisition::questions![confirm { choices: todo!() }];
| ^^^^^^^

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![confirm { extension: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `extension` does not exist for kind `confirm`
--> $DIR/extension.rs:2:47
|
2 | let q = inquisition::questions![confirm { extension: todo!() }];
| ^^^^^^^^^

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![confirm { filter: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `filter` does not exist for kind `confirm`
--> $DIR/filter.rs:2:47
|
2 | let q = inquisition::questions![confirm { filter: todo!() }];
| ^^^^^^

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![confirm { async filter: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `filter_async` does not exist for kind `confirm`
--> $DIR/filter_async.rs:2:53
|
2 | let q = inquisition::questions![confirm { async filter: todo!() }];
| ^^^^^^

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![confirm { mask: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `mask` does not exist for kind `confirm`
--> $DIR/mask.rs:2:47
|
2 | let q = inquisition::questions![confirm { mask: todo!() }];
| ^^^^

View File

@ -0,0 +1,3 @@
fn main() {
inquisition::questions![confirm { page_size: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `page_size` does not exist for kind `confirm`
--> $DIR/page_size.rs:2:39
|
2 | inquisition::questions![confirm { page_size: todo!() }];
| ^^^^^^^^^

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![confirm { plugin: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `plugin` does not exist for kind `confirm`
--> $DIR/plugin.rs:2:47
|
2 | let q = inquisition::questions![confirm { plugin: todo!() }];
| ^^^^^^

View File

@ -0,0 +1,5 @@
fn main() {
inquisition::questions![confirm {
should_loop: todo!()
}];
}

View File

@ -0,0 +1,5 @@
error: option `should_loop` does not exist for kind `confirm`
--> $DIR/should_loop.rs:3:9
|
3 | should_loop: todo!()
| ^^^^^^^^^^^

View File

@ -0,0 +1,8 @@
fn main() {
inquisition::questions![confirm {
name: "name",
default: true,
transform: |_, _, _| Ok(()),
async transform: |_, _, _| Box::pin(async { Ok(()) }),
}];
}

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![confirm { validate: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `validate` does not exist for kind `confirm`
--> $DIR/validate.rs:2:47
|
2 | let q = inquisition::questions![confirm { validate: todo!() }];
| ^^^^^^^^

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![confirm { async validate: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `validate_async` does not exist for kind `confirm`
--> $DIR/validate_async.rs:2:53
|
2 | let q = inquisition::questions![confirm { async validate: todo!() }];
| ^^^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![input {
ask_if_answered: todo!(),
ask_if_answered: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: duplicate option `ask_if_answered`
--> $DIR/ask_if_answered.rs:4:9
|
4 | ask_if_answered: todo!(),
| ^^^^^^^^^^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![list {
choices: todo!(),
choices: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: duplicate option `choices`
--> $DIR/choices.rs:4:9
|
4 | choices: todo!(),
| ^^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![input {
default: todo!(),
default: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: duplicate option `default`
--> $DIR/default.rs:4:9
|
4 | default: todo!(),
| ^^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![editor {
extension: todo!(),
extension: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: duplicate option `extension`
--> $DIR/extension.rs:4:9
|
4 | extension: todo!(),
| ^^^^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![input {
filter: todo!(),
filter: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: duplicate option `filter`
--> $DIR/filter.rs:4:9
|
4 | filter: todo!(),
| ^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![input {
filter_async: todo!(),
filter_async: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: unknown question option `filter_async`
--> $DIR/filter_async.rs:3:9
|
3 | filter_async: todo!(),
| ^^^^^^^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![password {
mask: todo!(),
mask: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: duplicate option `mask`
--> $DIR/mask.rs:4:9
|
4 | mask: todo!(),
| ^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![input {
message: todo!(),
message: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: duplicate option `message`
--> $DIR/message.rs:4:9
|
4 | message: todo!(),
| ^^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![input {
name: todo!(),
name: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: duplicate option `name`
--> $DIR/name.rs:4:9
|
4 | name: todo!(),
| ^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![list {
page_size: todo!(),
page_size: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: duplicate option `page_size`
--> $DIR/page_size.rs:4:9
|
4 | page_size: todo!(),
| ^^^^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![plugin {
plugin: todo!(),
plugin: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: duplicate option `plugin`
--> $DIR/plugin.rs:4:9
|
4 | plugin: todo!(),
| ^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![list {
should_loop: todo!(),
should_loop: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: duplicate option `should_loop`
--> $DIR/should_loop.rs:4:9
|
4 | should_loop: todo!(),
| ^^^^^^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![input {
transform: todo!(),
transform: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: duplicate option `transform`
--> $DIR/transform.rs:4:9
|
4 | transform: todo!(),
| ^^^^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![input {
transform_async: todo!(),
transform_async: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: unknown question option `transform_async`
--> $DIR/transform_async.rs:3:9
|
3 | transform_async: todo!(),
| ^^^^^^^^^^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![input {
validate: todo!(),
validate: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: duplicate option `validate`
--> $DIR/validate.rs:4:9
|
4 | validate: todo!(),
| ^^^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![input {
validate_async: todo!(),
validate_async: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: unknown question option `validate_async`
--> $DIR/validate_async.rs:3:9
|
3 | validate_async: todo!(),
| ^^^^^^^^^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
let q = inquisition::questions![input {
when: todo!(),
when: todo!(),
}];
}

View File

@ -0,0 +1,5 @@
error: duplicate option `when`
--> $DIR/when.rs:4:9
|
4 | when: todo!(),
| ^^^^

View File

@ -0,0 +1,3 @@
fn main() {
inquisition::questions![editor { choices: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `choices` does not exist for kind `editor`
--> $DIR/choices.rs:2:38
|
2 | inquisition::questions![editor { choices: todo!() }];
| ^^^^^^^

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![editor { mask: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `mask` does not exist for kind `editor`
--> $DIR/mask.rs:2:46
|
2 | let q = inquisition::questions![editor { mask: todo!() }];
| ^^^^

View File

@ -0,0 +1,3 @@
fn main() {
inquisition::questions![editor { page_size: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `page_size` does not exist for kind `editor`
--> $DIR/page_size.rs:2:38
|
2 | inquisition::questions![editor { page_size: todo!() }];
| ^^^^^^^^^

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![editor { plugin: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `plugin` does not exist for kind `editor`
--> $DIR/plugin.rs:2:46
|
2 | let q = inquisition::questions![editor { plugin: todo!() }];
| ^^^^^^

View File

@ -0,0 +1,5 @@
fn main() {
inquisition::questions![editor {
should_loop: todo!()
}];
}

View File

@ -0,0 +1,5 @@
error: option `should_loop` does not exist for kind `editor`
--> $DIR/should_loop.rs:3:9
|
3 | should_loop: todo!()
| ^^^^^^^^^^^

View File

@ -0,0 +1,15 @@
fn main() {
inquisition::questions! [
editor {
name: "name",
default: "hello world",
extension: ".rs",
transform: |_, _, _| Ok(()),
async transform: |_, _, _| Box::pin(async { Ok(()) }),
validate: |_, _| Ok(()),
async validate: |_, _| Box::pin(async { Ok(()) }),
filter: |t, _| t,
async filter: |t, _| Box::pin(async move { t }),
}
];
}

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![expand { extension: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `extension` does not exist for kind `expand`
--> $DIR/extension.rs:2:46
|
2 | let q = inquisition::questions![expand { extension: todo!() }];
| ^^^^^^^^^

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![expand { filter: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `filter` does not exist for kind `expand`
--> $DIR/filter.rs:2:46
|
2 | let q = inquisition::questions![expand { filter: todo!() }];
| ^^^^^^

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![expand { async filter: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `filter_async` does not exist for kind `expand`
--> $DIR/filter_async.rs:2:52
|
2 | let q = inquisition::questions![expand { async filter: todo!() }];
| ^^^^^^

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![expand { mask: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `mask` does not exist for kind `expand`
--> $DIR/mask.rs:2:46
|
2 | let q = inquisition::questions![expand { mask: todo!() }];
| ^^^^

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![expand { plugin: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `plugin` does not exist for kind `expand`
--> $DIR/plugin.rs:2:46
|
2 | let q = inquisition::questions![expand { plugin: todo!() }];
| ^^^^^^

View File

@ -0,0 +1,13 @@
fn main() {
inquisition::questions! [
expand {
name: "name",
default: 'c',
transform: |_, _, _| Ok(()),
async transform: |_, _, _| Box::pin(async { Ok(()) }),
choices: [('c', "choice")],
page_size: 0,
should_loop: true,
}
];
}

View File

@ -0,0 +1,3 @@
fn main() {
let q = inquisition::questions![expand { validate: todo!() }];
}

View File

@ -0,0 +1,5 @@
error: option `validate` does not exist for kind `expand`
--> $DIR/validate.rs:2:46
|
2 | let q = inquisition::questions![expand { validate: todo!() }];
| ^^^^^^^^

Some files were not shown because too many files have changed in this diff Show More