diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a903ec32..3ea5f6b1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -48,11 +48,11 @@ jobs: # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. - + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality - + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild @@ -61,7 +61,7 @@ jobs: # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - # If the Autobuild fails above, remove it and uncomment the following three lines. + # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1d597153..b956f490 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,24 +31,24 @@ jobs: - uses: actions/checkout@v3.1.0 with: fetch-depth: 0 - + - name: Setup Python uses: actions/setup-python@v4.3.0 with: python-version: 3.9 cache: 'pip' if: matrix.os != 'macos-latest' - + - name: Setup Python MacOS run: | wget https://www.python.org/ftp/python/3.10.11/python-3.10.11-macos11.pkg sudo installer -verbose -pkg ./python-3.10.11-macos11.pkg -target / echo "/Library/Frameworks/Python.framework/Versions/3.10/bin" >> $GITHUB_PATH if: matrix.os == 'macos-latest' - + - name: Install Requirements run: python3 -m pip install --upgrade pip && pip3 install wheel && pip3 install -r requirements.txt && pip3 uninstall -y typing - + - name: Build Wheel run: python3 setup.py bdist_wheel if: matrix.os == 'ubuntu-latest' @@ -59,7 +59,7 @@ jobs: name: pros-cli-wheel-${{needs.update_build_number.outputs.output1}} path: dist/* if: matrix.os == 'ubuntu-latest' - + - name: Run Pyinstaller run: | python3 version.py @@ -78,7 +78,7 @@ jobs: pyinstaller --onefile pros/cli/compile_commands/intercept-cc.py --name=intercept-cc --target-arch=universal2 pyinstaller --onefile pros/cli/compile_commands/intercept-cc.py --name=intercept-c++ --target-arch=universal2 if: matrix.os == 'macos-latest' - + - name: Package Everything Up shell: bash run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..c22efffd --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,22 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: mixed-line-ending + args: [--fix=lf] + - id: end-of-file-fixer + - id: check-yaml + - id: check-vcs-permalinks + - id: check-merge-conflict + - id: check-case-conflict + - id: check-ast + - id: trailing-whitespace + - id: requirements-txt-fixer + - repo: local + hooks: + - id: pylint + name: pylint + entry: python -m pylint + language: system + types: [python] + args: [--rcfile=.pylintrc] diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..5a21d69f --- /dev/null +++ b/.pylintrc @@ -0,0 +1,9 @@ +[MASTER] + +max-line-length = 120 +disable = C0114, C0115, C0116, R0903, C0415, R1705, R0913, W1203, R1729, E1120, E1123, C0209, R1710, W0621, C0121, + W0614, W0401, W1202, C0117, W0718, R0205, R0402, R0914, R1725, R1735, C0411, W0237, W0702, W0223, W0613, + W0108, R0912, R0911, W0511, E1136, R0902, W0611, C0412, C0103, C0301, R1732, R0915, W1514, R1718, W1510, + E0602, W1309, C0325, E1101, R1714, R0916, W0719, R1734, E1133, W1201, W0107, W3101, W0640, C0201, W1113, + W0246, W0622, W0221, E1111, R1720, W0221, R1723, E0102, W0201, E0203, E0401, W0602, W0212, W0707, R0904, + W0101, C0302, E0110, W0603, R1701, W0106, R1721, W0601, diff --git a/pip_version b/pip_version index a423d421..4d9d11cf 100644 --- a/pip_version +++ b/pip_version @@ -1 +1 @@ -3.4.2 \ No newline at end of file +3.4.2 diff --git a/pros/cli/click_classes.py b/pros/cli/click_classes.py index cb5a82c0..8272f488 100644 --- a/pros/cli/click_classes.py +++ b/pros/cli/click_classes.py @@ -162,4 +162,4 @@ def invoke(self, *args, **kwargs): if (isProject): #check if there is a project curr_proj = p() click.echo("PROS-Kernel Version: {}".format(curr_proj.kernel)) - raise e \ No newline at end of file + raise e diff --git a/pros/cli/common.py b/pros/cli/common.py index e666877d..b62c2d5b 100644 --- a/pros/cli/common.py +++ b/pros/cli/common.py @@ -139,7 +139,7 @@ def callback(ctx: click.Context, param: click.Parameter, value: bool): if value: echo("Not sending analytics for this command.\n") analytics.useAnalytics = False - pass + pass decorator = click.option('--no-analytics', expose_value=False, is_flag=True, default=False, is_eager=True, help="Don't send analytics for this command.", callback=callback, cls=PROSOption, hidden=True)(f) decorator.__name__ = f.__name__ diff --git a/pros/cli/conductor.py b/pros/cli/conductor.py index 79e098f1..3b4c8257 100644 --- a/pros/cli/conductor.py +++ b/pros/cli/conductor.py @@ -222,7 +222,7 @@ def new_project(ctx: click.Context, path: str, target: str, version: str, if version.lower() == 'latest' or not version: version = '>0' if not force_system and c.Project.find_project(path) is not None: - logger(__name__).error('A project already exists in this location at ' + c.Project.find_project(path) + + logger(__name__).error('A project already exists in this location at ' + c.Project.find_project(path) + '! Delete it first. Are you creating a project in an existing one?', extra={'sentry': False}) ctx.exit(-1) try: @@ -311,7 +311,7 @@ def info_project(project: c.Project, ls_upgrades): Visit https://pros.cs.purdue.edu/v5/cli/conductor.html to learn more """ - analytics.send("info-project") + analytics.send("info-project") from pros.conductor.project import ProjectReport report = ProjectReport(project) _conductor = c.Conductor() @@ -366,4 +366,3 @@ def query_depots(url: bool): _conductor = c.Conductor() ui.echo(f"Available Depots{' (Add --url for the url)' if not url else ''}:\n") ui.echo('\n'.join(_conductor.query_depots(url))+"\n") - \ No newline at end of file diff --git a/pros/cli/misc_commands.py b/pros/cli/misc_commands.py index 8566456a..d212a2fc 100644 --- a/pros/cli/misc_commands.py +++ b/pros/cli/misc_commands.py @@ -19,9 +19,9 @@ def upgrade(force_check, no_install): """ with ui.Notification(): ui.echo('The "pros upgrade" command is currently non-functioning. Did you mean to run "pros c upgrade"?', color='yellow') - + return # Dead code below - + analytics.send("upgrade") from pros.upgrade import UpgradeManager manager = UpgradeManager() diff --git a/pros/cli/terminal.py b/pros/cli/terminal.py index 2f05f2fe..a44b89d5 100644 --- a/pros/cli/terminal.py +++ b/pros/cli/terminal.py @@ -42,7 +42,7 @@ def terminal(port: str, backend: str, **kwargs): may be preferred when "share" doesn't perform adequately. Note: share backend is not yet implemented. - """ + """ analytics.send("terminal") from pros.serial.devices.vex.v5_user_device import V5UserDevice from pros.serial.terminal import Terminal @@ -91,7 +91,7 @@ def __init__(self, file): self.log = open(file, 'a') def write(self, data): self.terminal.write(data) - self.log.write(data) + self.log.write(data) def flush(self): pass def end(self): diff --git a/pros/cli/upload.py b/pros/cli/upload.py index 545609a4..e0c74b9b 100644 --- a/pros/cli/upload.py +++ b/pros/cli/upload.py @@ -22,7 +22,7 @@ def upload_cli(): cls=PROSDeprecated, replacement='after') @click.option('--run-screen/--execute', 'run_screen', default=None, help='Display run program screen on the brain after upload.', cls=PROSDeprecated, replacement='after') -@click.option('-af', '--after', type=click.Choice(['run','screen','none']), default=None, help='Action to perform on the brain after upload.', +@click.option('-af', '--after', type=click.Choice(['run','screen','none']), default=None, help='Action to perform on the brain after upload.', cls=PROSOption, group='V5 Options') @click.option('--quirk', type=int, default=0) @click.option('--name', 'remote_name', type=str, default=None, required=False, help='Remote program name.', @@ -37,9 +37,9 @@ def upload_cli(): cls=PROSOption, group='V5 Options', hidden=True) @click.option('--compress-bin/--no-compress-bin', 'compress_bin', cls=PROSOption, group='V5 Options', default=True, help='Compress the program binary before uploading.') -@click.option('--description', default="Made with PROS", type=str, cls=PROSOption, group='V5 Options', +@click.option('--description', default="Made with PROS", type=str, cls=PROSOption, group='V5 Options', help='Change the description displayed for the program.') -@click.option('--name', default=None, type=str, cls=PROSOption, group='V5 Options', +@click.option('--name', default=None, type=str, cls=PROSOption, group='V5 Options', help='Change the name of the program.') @default_options @@ -119,12 +119,12 @@ def upload(path: Optional[str], project: Optional[c.Project], port: str, **kwarg kwargs['remote_name'] = os.path.splitext(os.path.basename(path))[0] kwargs['remote_name'] = kwargs['remote_name'].replace('@', '_') kwargs['slot'] -= 1 - + action_to_kwarg = { - 'run' : vex.V5Device.FTCompleteOptions.RUN_IMMEDIATELY, - 'screen' : vex.V5Device.FTCompleteOptions.RUN_SCREEN, + 'run' : vex.V5Device.FTCompleteOptions.RUN_IMMEDIATELY, + 'screen' : vex.V5Device.FTCompleteOptions.RUN_SCREEN, 'none' : vex.V5Device.FTCompleteOptions.DONT_RUN - } + } after_upload_default = 'screen' #Determine which FTCompleteOption to assign to run_after if kwargs['after']==None: diff --git a/pros/common/utils.py b/pros/common/utils.py index 294da89f..d74d9a2d 100644 --- a/pros/common/utils.py +++ b/pros/common/utils.py @@ -35,7 +35,7 @@ def get_version(): module = pros.cli.main.__name__ for dist in pkg_resources.working_set: scripts = dist.get_entry_map().get('console_scripts') or {} - for script_name, entry_point in iter(scripts.items()): + for _, entry_point in iter(scripts.items()): if entry_point.module_name == module: ver = dist.version if ver is not None: diff --git a/pros/conductor/conductor.py b/pros/conductor/conductor.py index b8e50416..1080371d 100644 --- a/pros/conductor/conductor.py +++ b/pros/conductor/conductor.py @@ -177,7 +177,7 @@ 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 - + return list(results) def resolve_template(self, identifier: Union[str, BaseTemplate], **kwargs) -> Optional[BaseTemplate]: @@ -340,6 +340,6 @@ def add_depot(self, name: str, url: str): def remove_depot(self, name: str): del self.depots[name] self.save() - + def query_depots(self, url: bool): return [name + ((' -- ' + depot.location) if url else '') for name, depot in self.depots.items()] diff --git a/pros/conductor/depots.md b/pros/conductor/depots.md index 33a92336..f4efcf3f 100644 --- a/pros/conductor/depots.md +++ b/pros/conductor/depots.md @@ -13,7 +13,7 @@ $ pros conduct add-depot test "https://pros.cs.purdue.edu/v5/_static/beta/testin `pros conduct remove-depot ` Example: -```bash +```bash $ pros conduct remove-depot test > Removed depot test ``` @@ -28,11 +28,11 @@ Examples: ```bash $ pros conduct query-depots --url > Available Depots: -> +> > kernel-beta-mainline -- https://raw.githubusercontent.com/purduesigbots/pros-mainline/master/beta/kernel-beta-mainline.json > pros-mainline -- https://purduesigbots.github.io/pros-mainline/pros-mainline.json > test -- https://pros.cs.purdue.edu/v5/_static/beta/testing-mainline.json -> +> ``` ```bash $ pros conduct query-depots @@ -41,5 +41,5 @@ $ pros conduct query-depots > kernel-beta-mainline > pros-mainline > test -> +> ``` diff --git a/pros/conductor/project/ProjectTransaction.py b/pros/conductor/project/ProjectTransaction.py index 14034d42..edae1330 100644 --- a/pros/conductor/project/ProjectTransaction.py +++ b/pros/conductor/project/ProjectTransaction.py @@ -36,7 +36,6 @@ def execute(self, conductor: c.Conductor, project: c.Project): raise e else: ui.logger(__name__).warning(str(e)) - return None def describe(self, conductor: c.Conductor, project: c.Project): action = project.get_template_actions(conductor.resolve_template(self.template)) diff --git a/pros/conductor/project/__init__.py b/pros/conductor/project/__init__.py index 76e4d192..a0777595 100644 --- a/pros/conductor/project/__init__.py +++ b/pros/conductor/project/__init__.py @@ -293,7 +293,7 @@ def libscanbuild_capture(args: argparse.Namespace) -> Tuple[int, Iterable[Compil if not os.environ.get('PROS_TOOLCHAIN'): ui.logger(__name__).warn("PROS toolchain not found! Please ensure the toolchain is installed correctly and your environment variables are set properly.\n") ui.logger(__name__).error(f"ERROR WHILE CALLING '{make_cmd}' WITH EXCEPTION: {str(e)}\n",extra={'sentry':False}) - if not suppress_output: + if not suppress_output: pipe.close() sys.exit() if not suppress_output: @@ -411,7 +411,7 @@ def find_project(path: str, recurse_times: int = 10): if os.path.isfile(path): path = os.path.dirname(path) if os.path.isdir(path): - for n in range(recurse_times): + for _ in range(recurse_times): if path is not None and os.path.isdir(path): files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) and f.lower() == 'project.pros'] diff --git a/pros/ga/analytics.py b/pros/ga/analytics.py index 6f786105..247e6b31 100644 --- a/pros/ga/analytics.py +++ b/pros/ga/analytics.py @@ -53,16 +53,16 @@ def send(self,action): 'ni': 0 } - session = FuturesSession() + session = FuturesSession() - #Send payload to GA servers + #Send payload to GA servers future = session.post(url=url, data=payload, headers={'User-Agent': agent}, timeout=5.0) self.pendingRequests.append(future) - except Exception as e: + except Exception: from pros.cli.common import logger logger(__name__).warning("Unable to send analytics. Do you have a stable internet connection?", extra={'sentry': False}) @@ -71,13 +71,13 @@ def set_use(self, value: bool): self.useAnalytics = value self.cli_config.ga['enabled'] = self.useAnalytics self.cli_config.save() - + def process_requests(self): responses = [] for future in as_completed(self.pendingRequests): try: response = future.result() - + if not response.status_code==200: print("Something went wrong while sending analytics!") print(response) @@ -92,4 +92,4 @@ def process_requests(self): return responses -analytics = Analytics() \ No newline at end of file +analytics = Analytics() diff --git a/pros/serial/devices/vex/crc.py b/pros/serial/devices/vex/crc.py index f53bee5d..2e4270d7 100644 --- a/pros/serial/devices/vex/crc.py +++ b/pros/serial/devices/vex/crc.py @@ -9,7 +9,7 @@ def __init__(self, size: int, polynomial: int): for i in range(256): crc_accumulator = i << (self._size - 8) - for j in range(8): + for _ in range(8): if crc_accumulator & (1 << (self._size - 1)): crc_accumulator = (crc_accumulator << 1) ^ self._polynomial else: diff --git a/pros/serial/devices/vex/v5_device.py b/pros/serial/devices/vex/v5_device.py index a19d0777..124897e0 100644 --- a/pros/serial/devices/vex/v5_device.py +++ b/pros/serial/devices/vex/v5_device.py @@ -268,7 +268,6 @@ def upload_project(self, project: Project, **kwargs): def generate_ini_file(self, remote_name: str = None, slot: int = 0, ini: ConfigParser = None, **kwargs): project_ini = ConfigParser() - from semantic_version import Spec default_icon = 'USER902x.bmp' if Spec('>=1.0.0-22').match(self.status['cpu0_version']) else 'USER999x.bmp' project_ini['project'] = { 'version': str(kwargs.get('ide_version') or get_version()), @@ -612,7 +611,7 @@ def read_ini(self, remote_name: str) -> Optional[ConfigParser]: rx_io.seek(0, 0) config.read_string(rx_io.read().decode('ascii')) return config - except VEXCommError as e: + except VEXCommError: return None @retries @@ -918,7 +917,7 @@ def kv_write(self, kv: str, payload: Union[Iterable, bytes, bytearray, str]): payload = payload.encode(encoding='ascii') tx_fmt =f'<{len(encoded_kv)}s{len(payload)}s' tx_payload = struct.pack(tx_fmt, encoded_kv, payload) - ret = self._txrx_ext_packet(0x2f, tx_payload, 1, check_length=False, check_ack=True) + self._txrx_ext_packet(0x2f, tx_payload, 1, check_length=False, check_ack=True) logger(__name__).debug('Completed ext 0x2f command') return payload diff --git a/pros/serial/ports/__init__.py b/pros/serial/ports/__init__.py index be344a79..4850b2b9 100644 --- a/pros/serial/ports/__init__.py +++ b/pros/serial/ports/__init__.py @@ -1,7 +1,7 @@ from functools import lru_cache from pros.common import logger -from serial.tools import list_ports as list_ports +from serial.tools import list_ports from .base_port import BasePort, PortConnectionException, PortException from .direct_port import DirectPort diff --git a/pros/serial/ports/exceptions.py b/pros/serial/ports/exceptions.py index cd3f0bca..1a869f38 100644 --- a/pros/serial/ports/exceptions.py +++ b/pros/serial/ports/exceptions.py @@ -26,5 +26,3 @@ def __str__(self): return f"Port not found: Could not open port '{self.port_name}'. Try closing any other VEX IDEs such as VEXCode, Robot Mesh Studio, or " \ f"firmware utilities; moving to a different USB port; {extra}or " \ f"restarting the device." - - diff --git a/pros/serial/ports/v5_wireless_port.py b/pros/serial/ports/v5_wireless_port.py index 80d4717d..dc25c259 100644 --- a/pros/serial/ports/v5_wireless_port.py +++ b/pros/serial/ports/v5_wireless_port.py @@ -1,36 +1,36 @@ -from typing import * - -from pros.serial.devices.vex.v5_device import V5Device -from pros.serial.ports import BasePort, DirectPort - - -class V5WirelessPort(BasePort): - def __init__(self, port): - self.buffer: bytearray = bytearray() - - self.port_instance = DirectPort(port) - self.device = V5Device(self.port_instance) - self.download_channel = self.device.DownloadChannel(self.device) - self.download_channel.__enter__() - - def destroy(self): - self.port_instance.destroy() - self.download_channel.__exit__() - - def config(self, command: str, argument: Any): - return self.port_instance.config(command, argument) - - # TODO: buffer input? technically this is done by the user_fifo_write cmd blocking until whole input is written? - def write(self, data: bytes): - self.device.user_fifo_write(data) - - def read(self, n_bytes: int = 0) -> bytes: - if n_bytes > len(self.buffer): - self.buffer.extend(self.device.user_fifo_read()) - ret = self.buffer[:n_bytes] - self.buffer = self.buffer[n_bytes:] - return ret - - @property - def name(self) -> str: - return self.port_instance.name +from typing import * + +from pros.serial.devices.vex.v5_device import V5Device +from pros.serial.ports import BasePort, DirectPort + + +class V5WirelessPort(BasePort): + def __init__(self, port): + self.buffer: bytearray = bytearray() + + self.port_instance = DirectPort(port) + self.device = V5Device(self.port_instance) + self.download_channel = self.device.DownloadChannel(self.device) + self.download_channel.__enter__() + + def destroy(self): + self.port_instance.destroy() + self.download_channel.__exit__() + + def config(self, command: str, argument: Any): + return self.port_instance.config(command, argument) + + # TODO: buffer input? technically this is done by the user_fifo_write cmd blocking until whole input is written? + def write(self, data: bytes): + self.device.user_fifo_write(data) + + def read(self, n_bytes: int = 0) -> bytes: + if n_bytes > len(self.buffer): + self.buffer.extend(self.device.user_fifo_read()) + ret = self.buffer[:n_bytes] + self.buffer = self.buffer[n_bytes:] + return ret + + @property + def name(self) -> str: + return self.port_instance.name diff --git a/requirements.txt b/requirements.txt index 0cbde8f3..ae307540 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,18 @@ +cachetools click>=6,<7 +cobs +colorama +jsonpickle +observable +pre-commit +pyinstaller +pylint +pypng==0.0.20 pyserial -cachetools +pyzmq requests requests-futures -tabulate -jsonpickle -semantic_version -colorama -pyzmq -cobs scan-build==2.0.13 +semantic_version sentry-sdk -observable -pypng==0.0.20 -pyinstaller \ No newline at end of file +tabulate diff --git a/version b/version index a423d421..4d9d11cf 100644 --- a/version +++ b/version @@ -1 +1 @@ -3.4.2 \ No newline at end of file +3.4.2 diff --git a/win_version b/win_version index 0ccc3dcd..e44b972e 100644 --- a/win_version +++ b/win_version @@ -1 +1 @@ -3.4.2.0 \ No newline at end of file +3.4.2.0