Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for specifying overrides in the command line for new projects #1552

Merged
merged 3 commits into from
Nov 27, 2023

Conversation

rmartin16
Copy link
Member

@rmartin16 rmartin16 commented Nov 22, 2023

Changes

  • Configuration for new projects can be specified at the command line via -Q
    • For instance, to specify the license, -Q "license=MIT license"
  • Question prompts are now re-flowed based on terminal size
  • Questions are more clearly formatted and separated
  • Issues with user inputs are written as logging warnings
  • All user prompts are now written in bold by default

PR Checklist:

  • All new features have been tested
  • All new features have been documented
  • I have read the CONTRIBUTING.md file
  • I will abide by the code of conduct

Copy link
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is a work in progress, but it was just so tasty I couldn't help myself :-)

Broad strokes look good; a couple of suggestions inline.

One documentation related issue - the names of the configuration keys will be mostly undiscoverable by end users. Unless you actually open the template, you won't know that you can use app_name to set the app name. However, I wonder if we should make this deliberate - i.e., provide the functionality, but not document the specific keys that can be used (and document them as being not documented), so that we don't have a backwards incompatibility problem if we need to change the template. The docs for the -Q option can then tell the user that the keys are template dependent, and we make specific guarantees of compatibility of keys between Briefcase versions.

else:
if not ((key := key.strip()) and (value := value.strip())):
raise BriefcaseCommandError(
f"Invalid Project configuration override '{override}'"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
f"Invalid Project configuration override '{override}'"
f"Invalid project configuration override {override!r}"

key, value = override.split("=", 1)
except ValueError as e:
raise BriefcaseCommandError(
f"Unable to parse project configuration override '{override}'"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
f"Unable to parse project configuration override '{override}'"
f"Unable to parse project configuration override {override!r}"

)
if key in overrides:
raise BriefcaseCommandError(
f"Project configuration override '{key}' specified multiple times"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
f"Project configuration override '{key}' specified multiple times"
f"Project configuration override {key!r} specified multiple times"

# Use the override as the answer but clear it so if validation fails,
# the user will be solicited for a value on the next loop
self.input.prompt(
f"Using override value '{override_value}' for {titlecase(variable)}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
f"Using override value '{override_value}' for {titlecase(variable)}"
f"Using override value {override_value!r} for {titlecase(variable)}"

if override_value is not None:
self.input.prompt()
self.input.prompt(
f"Using override value '{override_value}' for {choice_name}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
f"Using override value '{override_value}' for {choice_name}"
f"Using override value {override_value!r} for {choice_name}"

**options,
)


0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stray content?

Comment on lines 415 to 416
"Based on your formal name, we suggest an app name of"
f"'{default_app_name}', but you can use another name if you want."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a missing space here:

Suggested change
"Based on your formal name, we suggest an app name of"
f"'{default_app_name}', but you can use another name if you want."
"Based on your formal name, we suggest an app name of "
f"{default_app_name!r}, but you can use another name if you want."

"""
self.input.prompt(intro)
self.prompt_intro(intro=intro)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to display the intro prompt if there's an override? Agreed we should display the "Using override" text, but the only reason I can see to display the text is so that in the case of bad input, the context for fixing it is known. I'd argue it's better to just raise an error on the basis that bad command line input can/should be fixed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thought process here was to only pivot the experience of creating a new project based on whether --no-input was provided.

When --no-input is absent (i.e. "normal"), everything is printed; however, when it is present, anything written with input.prompt() is hidden.

If we try to tweak the "normal" experience based on whether an override value exists, I see two obstacles:

  1. Printing the new divider between questions becomes more difficult
    • This divider is a part of prompt_intro()....so it'd have to be separated out
    • If we omit the divider for questions with overrides, then I think it's a little confusing because it will appear to be associated with the preceding question
  2. If the override is invalid...then we're in a pickle
    • When an override is considered validated, the loop runs again and now solicits the user
    • But we'll need to now show the prompt....this means the prompt printing needs to be moved in to the loop....and we'll need to track already showing it
    • Alternatively, I suppose the approach could be re-implemented like this...
def input_text(self, intro, variable, default, validator=None, override_value=None):
        if override_value:
            if self.validate_user_input(validator, override_value):
                return override_value
        
        self.prompt_intro(intro=intro)

        while True:
            self.input.prompt()

            answer = self.input.text_input(
                prompt=f"{titlecase(variable)} [{default}]: ",
                default=default,
            )

            if self.validate_user_input(validator, answer):
                return answer

Given that arguably may be a better implementation anyway...it gives more credence to this idea I suppose.

I would still like to print the divider; so, I can make that it's own method. I would also thinking of incorporating the essence of the question in to the divider.

Maybe something like this:

-- Formal Name ----------------------------------------------------------------

First, we need a formal name for your application.

This is the name that will be displayed to humans whenever the name of the
application is displayed. It can have spaces and punctuation if you like, and
any capitalization will be used as you type it.

Formal Name [Hello World]: 

-- Package Name ---------------------------------------------------------------

Next, we need a name that can serve as a machine-readable Python package name
for your application.

This name must be PEP508-compliant - that means the name may only contain
letters, numbers, hyphens and underscores; it can't contain spaces or
punctuation, and it can't start with a hyphen or underscore.

Based on your formal name, we suggest an app name of 'helloworld', but you can
use another name if you want.

App Name [helloworld]: 

-- Bundle Identifier ----------------------------------------------------------

Now we need a bundle identifier for your application. App stores need to
protect against having multiple applications with the same name; the bundle
identifier is the namespace they use to identify applications that come from
you. The bundle identifier is usually the domain name of your company or
project, in reverse order.

For example, if you are writing an application for Example Corp, whose website
is example.com, your bundle would be ``com.example``. The bundle will be
combined with your application's machine readable name to form a complete
application identifier (e.g., com.example.helloworld).

Bundle Identifier [com.example]: 

It's a bit repetitive....but it also kinda nicely bookends the question...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thought process here was to only pivot the experience of creating a new project based on whether --no-input was provided.

When --no-input is absent (i.e. "normal"), everything is printed; however, when it is present, anything written with input.prompt() is hidden.

That's fair - the use case for using -Q and not using --no-input is admittedly small.

If we try to tweak the "normal" experience based on whether an override value exists, I see two obstacles:

  1. Printing the new divider between questions becomes more difficult

    • This divider is a part of prompt_intro()....so it'd have to be separated out
    • If we omit the divider for questions with overrides, then I think it's a little confusing because it will appear to be associated with the preceding question

Agreed that there should still be a divider; implementation complexity notwithstanding, I was thinking we'd just avoid giving the impression that a question was actually asked.

  1. If the override is invalid...then we're in a pickle

    • When an override is considered validated, the loop runs again and now solicits the user
    • But we'll need to now show the prompt....this means the prompt printing needs to be moved in to the loop....and we'll need to track already showing it
    • Alternatively, I suppose the approach could be re-implemented like this...
      ...
      Given that arguably may be a better implementation anyway...it gives more credence to this idea I suppose.

That was more in line with what I was thinking - that the core "question" loop itself wasn't quite right in the circumstances.

I would still like to print the divider; so, I can make that its own method. I would also thinking of incorporating the essence of the question in to the divider.
...
It's a bit repetitive....but it also kinda nicely bookends the question...

Agreed. Bonus points if the output in the override case is:

-- Package Name ----------------------------------------------------------

Using override value 'org.foobar' for Package Name.

-- Bundle Identifier ----------------------------------------------------------
...

The only additional thought I've got in that space is whether it's an opportunity to surface the actual variable name for -Q purposes (.e.g, --- Formal Name (formal_name) ------) - but I'm not convinced that's worth it given that it's potentially confusing detail for a new user.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all in here now; I also tweaked the presentation of the actual generation of the new project. I did have an impulse to add some festive emojis....but I held back...

@rmartin16 rmartin16 changed the title Add support for specify new project overrides from command line Add support for specifying overrides in the command line for new projects Nov 24, 2023
@rmartin16
Copy link
Member Author

One documentation related issue - the names of the configuration keys will be mostly undiscoverable by end users. Unless you actually open the template, you won't know that you can use app_name to set the app name. However, I wonder if we should make this deliberate - i.e., provide the functionality, but not document the specific keys that can be used (and document them as being not documented), so that we don't have a backwards incompatibility problem if we need to change the template. The docs for the -Q option can then tell the user that the keys are template dependent, and we make specific guarantees of compatibility of keys between Briefcase versions.

I agree with that....although, it also kinda makes me wonder if we shouldn't let this be a backdoor to putting extra key/value pairs in to the cookiecutter context without requiring a custom bootstrap...

@rmartin16
Copy link
Member Author

I suppose this also begs the question if these overrides should be exposed to third-party bootstraps....probably either via the bootstrap's constructor or passing it to bootstrap.extra_content().

Although, this is also really just letting scope creep take the reins.....and YAGNI, anyway.

That said, I do think we should add **kwargs to all the methods in the built-in bootstraps; this should encourage anyone who makes a bootstrap to do the same and IDEs should help with detecting incongruent method overriding. This would just help provide cover for breaks in compatibility when new arguments are passed.

@rmartin16
Copy link
Member Author

rmartin16 commented Nov 24, 2023

I updated the default behavior for all user prompts to be bold by default. Personally, I think this has better aesthetics to visually promote the actual essence of the prompt from any preamble as well as the user's own input. If you disagree, it'll be trivial to drop the commit.

@rmartin16 rmartin16 marked this pull request as ready for review November 24, 2023 18:13
@rmartin16
Copy link
Member Author

rmartin16 commented Nov 24, 2023

Copy link
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A minor tweak to the docs, but otherwise this looks good to me.

@freakboy3742 freakboy3742 merged commit 6b2657e into beeware:main Nov 27, 2023
44 checks passed
@rmartin16 rmartin16 deleted the new-project-args branch November 27, 2023 04:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants