diff --git a/pros/cli/common.py b/pros/cli/common.py index eb546db0..6c12fa06 100644 --- a/pros/cli/common.py +++ b/pros/cli/common.py @@ -191,10 +191,13 @@ def callback(ctx: click.Context, param: click.Parameter, value: str): if project_path is None: if allow_none: return None - else: + elif required: raise click.UsageError(f'{os.path.abspath(value or ".")} is not inside a PROS project. ' f'Execute this command from within a PROS project or specify it ' f'with --project project/path') + else: + return None + return c.Project(project_path) def wrapper(f: Union[click.Command, Callable]): diff --git a/pros/cli/conductor.py b/pros/cli/conductor.py index e540e574..00a0ecb5 100644 --- a/pros/cli/conductor.py +++ b/pros/cli/conductor.py @@ -94,7 +94,7 @@ def fetch(query: c.BaseTemplate): help="Force apply the template, disregarding if the template is already installed.") @click.option('--remove-empty-dirs/--no-remove-empty-dirs', 'remove_empty_directories', is_flag=True, default=True, help='Remove empty directories when removing files') -@click.option('--early-access/--disable-early-access', '--early/--disable-early', '-ea/-dea', 'early_access', '--beta/--disable-beta', default=None, +@click.option('--early-access/--no-early-access', '--early/--no-early', '-ea/-nea', 'early_access', '--beta/--no-beta', default=None, help='Create a project using the PROS 4 kernel') @project_option() @template_query(required=True) @@ -145,7 +145,7 @@ def install(ctx: click.Context, **kwargs): help="Force apply the template, disregarding if the template is already installed.") @click.option('--remove-empty-dirs/--no-remove-empty-dirs', 'remove_empty_directories', is_flag=True, default=True, help='Remove empty directories when removing files') -@click.option('--early-access/--disable-early-access', '--early/--disable-early', '-ea/-dea', 'early_access', '--beta/--disable-beta', default=None, +@click.option('--early-access/--no-early-access', '--early/--no-early', '-ea/-nea', 'early_access', '--beta/--no-beta', default=None, help='Create a project using the PROS 4 kernel') @project_option() @template_query(required=False) @@ -207,7 +207,7 @@ def uninstall_template(project: c.Project, query: c.BaseTemplate, remove_user: b help='Compile the project after creation') @click.option('--build-cache', is_flag=True, default=None, show_default=False, help='Build compile commands cache after creation. Overrides --compile-after if both are specified.') -@click.option('--early-access/--disable-early-access', '--early/--disable-early', '-ea/-dea', 'early_access', '--beta/--disable-beta', default=None, +@click.option('--early-access/--no-early-access', '--early/--no-early', '-ea/-nea', 'early_access', '--beta/--no-beta', default=None, help='Create a project using the PROS 4 kernel') @click.pass_context @default_options @@ -261,12 +261,13 @@ def new_project(ctx: click.Context, path: str, target: str, version: str, help='Force update all remote depots, ignoring automatic update checks') @click.option('--limit', type=int, default=15, help='The maximum number of displayed results for each library') -@click.option('--early-access/--disable-early-access', '--early/--disable-early', '-ea/-dea', 'early_access', '--beta/--disable-beta', default=None, +@click.option('--early-access/--no-early-access', '--early/--no-early', '-ea/-nea', 'early_access', '--beta/--no-beta', default=None, help='View a list of early access templates') @template_query(required=False) +@project_option(required=False) @click.pass_context @default_options -def query_templates(ctx, query: c.BaseTemplate, allow_offline: bool, allow_online: bool, force_refresh: bool, +def query_templates(ctx, project: Optional[c.Project], query: c.BaseTemplate, allow_offline: bool, allow_online: bool, force_refresh: bool, limit: int, early_access: bool): """ Query local and remote templates based on a spec @@ -276,12 +277,10 @@ def query_templates(ctx, query: c.BaseTemplate, allow_offline: bool, allow_onlin analytics.send("query-templates") if limit < 0: limit = 15 + if early_access is None and project is not None: + early_access = project.use_early_access templates = c.Conductor().resolve_templates(query, allow_offline=allow_offline, allow_online=allow_online, force_refresh=force_refresh, early_access=early_access) - if early_access: - templates += c.Conductor().resolve_templates(query, allow_offline=allow_offline, allow_online=allow_online, - force_refresh=force_refresh, early_access=False) - render_templates = {} for template in templates: key = (template.identifier, template.origin) @@ -372,7 +371,6 @@ def query_depots(url: bool): ui.echo(f"Available Depots{' (Add --url for the url)' if not url else ''}:\n") ui.echo('\n'.join(_conductor.query_depots(url))+"\n") - @conductor.command('reset') @click.option('--force', is_flag=True, default=False, help='Force reset') @default_options @@ -394,4 +392,3 @@ def reset(force: bool): os.remove(file) ui.echo("Conductor was reset") - diff --git a/pros/cli/main.py b/pros/cli/main.py index 679d9897..8e4d6725 100644 --- a/pros/cli/main.py +++ b/pros/cli/main.py @@ -27,6 +27,7 @@ import pros.cli.misc_commands import pros.cli.interactive import pros.cli.user_script +import pros.conductor as c if sys.platform == 'win32': kernel32 = ctypes.windll.kernel32 @@ -99,7 +100,23 @@ def use_analytics(ctx: click.Context, param, value): ctx.exit(0) ctx.ensure_object(dict) analytics.set_use(touse) - ui.echo('Analytics set to : {}'.format(analytics.useAnalytics)) + ui.echo(f'Analytics usage set to: {analytics.useAnalytics}') + ctx.exit(0) + +def use_early_access(ctx: click.Context, param, value): + if value is None: + return + conductor = c.Conductor() + value = str(value).lower() + if value.startswith("t") or value in ["1", "yes", "y"]: + conductor.use_early_access = True + elif value.startswith("f") or value in ["0", "no", "n"]: + conductor.use_early_access = False + else: + ui.echo('Invalid argument provided for \'--use-early-access\'. Try \'--use-early-access=False\' or \'--use-early-access=True\'') + ctx.exit(0) + conductor.save() + ui.echo(f'Early access set to: {conductor.use_early_access}') ctx.exit(0) @@ -112,6 +129,8 @@ def use_analytics(ctx: click.Context, param, value): callback=version) @click.option('--use-analytics', help='Set analytics usage (True/False).', type=str, expose_value=False, is_eager=True, default=None, callback=use_analytics) +@click.option('--use-early-access', type=str, expose_value=False, is_eager=True, default=None, + help='Create projects with PROS 4 kernel by default', callback=use_early_access) def cli(ctx): pros.common.sentry.register() ctx.call_on_close(after_command) diff --git a/pros/conductor/conductor.py b/pros/conductor/conductor.py index 51eee752..fb40d7e1 100644 --- a/pros/conductor/conductor.py +++ b/pros/conductor/conductor.py @@ -188,7 +188,9 @@ def resolve_templates(self, identifier: Union[str, BaseTemplate], allow_online: results = list() if not unique else set() kernel_version = kwargs.get('kernel_version', None) if kwargs.get('early_access', None) is not None: - self.use_early_access = kwargs.get('early_access', False) + use_early_access = kwargs.get('early_access', False) + else: + use_early_access = self.use_early_access if isinstance(identifier, str): query = BaseTemplate.create_query(name=identifier, **kwargs) else: @@ -196,7 +198,7 @@ def resolve_templates(self, identifier: Union[str, BaseTemplate], allow_online: if allow_offline: offline_results = list() - if self.use_early_access: + if use_early_access: offline_results.extend(filter(lambda t: t.satisfies(query, kernel_version=kernel_version), self.early_access_local_templates)) offline_results.extend(filter(lambda t: t.satisfies(query, kernel_version=kernel_version), self.local_templates)) @@ -208,7 +210,7 @@ def resolve_templates(self, identifier: Union[str, BaseTemplate], allow_online: if allow_online: for depot in self.depots.values(): # EarlyAccess depot will only be accessed when the --early-access flag is true - if depot.name != EARLY_ACCESS_NAME or (depot.name == EARLY_ACCESS_NAME and self.use_early_access): + if depot.name != EARLY_ACCESS_NAME or (depot.name == EARLY_ACCESS_NAME and use_early_access): remote_templates = depot.get_remote_templates(force_check=force_refresh, **kwargs) online_results = list(filter(lambda t: t.satisfies(query, kernel_version=kernel_version), remote_templates)) @@ -219,8 +221,8 @@ def resolve_templates(self, identifier: Union[str, BaseTemplate], allow_online: results.extend(online_results) logger(__name__).debug('Saving Conductor config after checking for remote updates') self.save() # Save self since there may have been some updates from the depots - - if len(results) == 0 and not self.use_early_access: + + if len(results) == 0 and not use_early_access: raise dont_send( InvalidTemplateException(f'{identifier.name} does not support kernel version {kernel_version}')) @@ -298,15 +300,16 @@ def apply_template(self, project: Project, identifier: Union[str, BaseTemplate], if not confirm: raise dont_send( InvalidTemplateException(f'Not downgrading')) - elif not self.use_early_access and template.version[0] == '3' and not self.warn_early_access: + elif not project.use_early_access and template.version[0] == '3' and not self.warn_early_access: confirm = ui.confirm(f'PROS 4 is now in early access. ' f'Please use the --early-access flag if you would like to use it.\n' f'Do you want to use PROS 4 instead?') self.warn_early_access = True if confirm: # use pros 4 - self.use_early_access = True + project.use_early_access = True + project.save() kwargs['version'] = '>=0' - self.save() + kwargs['early_access'] = True # Recall the function with early access enabled return self.apply_template(project, identifier, **kwargs) @@ -351,14 +354,25 @@ def remove_template(project: Project, identifier: Union[str, BaseTemplate], remo def new_project(self, path: str, no_default_libs: bool = False, **kwargs) -> Project: if kwargs.get('early_access', None) is not None: - self.use_early_access = kwargs.get('early_access', False) + use_early_access = kwargs.get('early_access', False) + else: + use_early_access = self.use_early_access + kwargs["early_access"] = use_early_access if kwargs["version_source"]: # If true, then the user has not specified a version - if not self.use_early_access and self.warn_early_access: + if not use_early_access and self.warn_early_access: ui.echo(f"PROS 4 is now in early access. " f"If you would like to use it, use the --early-access flag.") - elif self.use_early_access: + elif not use_early_access and not self.warn_early_access: + confirm = ui.confirm(f'PROS 4 is now in early access. ' + f'Please use the --early-access flag if you would like to use it.\n' + f'Do you want to use PROS 4 instead?') + self.warn_early_access = True + if confirm: + use_early_access = True + kwargs['early_access'] = True + elif use_early_access: ui.echo(f'Early access is enabled. Using PROS 4.') - elif self.use_early_access: + elif use_early_access: ui.echo(f'Early access is enabled.') if not is_pathname_valid(str(Path(path).absolute())): @@ -367,7 +381,7 @@ def new_project(self, path: str, no_default_libs: bool = False, **kwargs) -> Pro if Path(path).exists() and Path(path).samefile(os.path.expanduser('~')): raise dont_send(ValueError('Will not create a project in user home directory')) - proj = Project(path=path, create=True) + proj = Project(path=path, create=True, early_access=use_early_access) if 'target' in kwargs: proj.target = kwargs['target'] if 'project_name' in kwargs and kwargs['project_name'] and not kwargs['project_name'].isspace(): @@ -381,7 +395,7 @@ def new_project(self, path: str, no_default_libs: bool = False, **kwargs) -> Pro proj.save() if not no_default_libs: - libraries = self.early_access_libraries if self.use_early_access and (kwargs.get("version", ">").startswith("4") or kwargs.get("version", ">").startswith(">")) else self.default_libraries + libraries = self.early_access_libraries if proj.use_early_access and (kwargs.get("version", ">").startswith("4") or kwargs.get("version", ">").startswith(">")) else self.default_libraries for library in libraries[proj.target]: try: # remove kernel version so that latest template satisfying query is correctly selected diff --git a/pros/conductor/project/__init__.py b/pros/conductor/project/__init__.py index b30733b5..c71fcd1f 100644 --- a/pros/conductor/project/__init__.py +++ b/pros/conductor/project/__init__.py @@ -16,7 +16,7 @@ class Project(Config): - def __init__(self, path: str = '.', create: bool = False, raise_on_error: bool = True, defaults: dict = None): + def __init__(self, path: str = '.', create: bool = False, raise_on_error: bool = True, defaults: dict = None, early_access: bool = False): """ Instantiates a PROS project configuration :param path: A path to the project, may be the actual project.pros file, any child directory of the project, @@ -38,6 +38,7 @@ def __init__(self, path: str = '.', create: bool = False, raise_on_error: bool = self.templates: Dict[str, Template] = defaults.get('templates', {}) self.upload_options: Dict = defaults.get('upload_options', {}) self.project_name: str = defaults.get('project_name', None) + self.use_early_access = early_access super(Project, self).__init__(file, error_on_decode=raise_on_error) if 'kernel' in self.__dict__: # Add backwards compatibility with PROS CLI 2 projects by adding kernel as a pseudo-template