Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e77bbae
Add ruff.toml
jwallwork23 Aug 11, 2025
a0b238b
Add Python linting to CI
jwallwork23 Aug 11, 2025
a1927c0
Apply ruff check --fix
jwallwork23 Sep 1, 2025
438d258
Docstrings
jwallwork23 Aug 11, 2025
b431842
Remove blank line
jwallwork23 Aug 11, 2025
24cf358
Avoid redefinition of loop variable
jwallwork23 Aug 11, 2025
de2a19c
Don't use f-strings in exceptions
jwallwork23 Aug 11, 2025
547797c
Docstring for magic method
jwallwork23 Aug 11, 2025
a17409a
Docstring for SingleColumnThermo
jwallwork23 Aug 11, 2025
a1d8a1a
Module imports at top
jwallwork23 Aug 11, 2025
c22fd82
Avoid trailing whitespace
jwallwork23 Aug 11, 2025
3fa2197
Avoid unnecessary multi-line docstrings
jwallwork23 Aug 11, 2025
9b4075a
Avoid assert False
jwallwork23 Aug 11, 2025
190df08
Add trailing period
jwallwork23 Aug 11, 2025
f6cf882
Avoid raw strings in exceptions
jwallwork23 Aug 11, 2025
6bfc1d7
Use imperative mood
jwallwork23 Aug 11, 2025
2d72ea8
Format docstrings
jwallwork23 Aug 11, 2025
8832a1b
Avoid shadowing builtins
jwallwork23 Aug 11, 2025
87fce99
Move imports to top level
jwallwork23 Aug 11, 2025
6c01365
Use context manager for files
jwallwork23 Aug 11, 2025
2c5ced2
Fix ThermoIntegration test
jwallwork23 Aug 11, 2025
6a27399
Avoid bare except
jwallwork23 Aug 29, 2025
e65907f
Avoid unused loop variable
jwallwork23 Aug 29, 2025
bfaab82
Avoid unused function arguments
jwallwork23 Aug 29, 2025
f4fbce8
Ignore magic value comparison
jwallwork23 Aug 29, 2025
4ef809c
Fix and ignore S101 (no assert)
jwallwork23 Aug 29, 2025
5264653
Ignore too-many-X rules for now
jwallwork23 Aug 29, 2025
8aef2a6
Ignore subprocess rules for now
jwallwork23 Aug 29, 2025
dfe3917
Ignore built-in variable shadowing in docs/conf
jwallwork23 Aug 29, 2025
71a425c
Ignore missing docstrings for now
jwallwork23 Aug 29, 2025
759c819
Drop ruff format in CI
jwallwork23 Aug 29, 2025
a64d714
Run linting tests when not merging into main or develop
jwallwork23 Aug 29, 2025
64e31ec
Add exclusions for data files
jwallwork23 Sep 1, 2025
93b3b3e
Avoid multiple comparisons
jwallwork23 Sep 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 26 additions & 24 deletions .cmake-lint-config.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""Configuration file for cmake-lint and cmake-format."""

# ----------------------------------
# Options affecting listfile parsing
# ----------------------------------
with section("parse"):

# Specify structure for custom cmake functions
additional_commands = { 'foo': { 'flags': ['BAR', 'BAZ'],
'kwargs': {'DEPENDS': '*', 'HEADERS': '*', 'SOURCES': '*'}}}
additional_commands = { "foo": { "flags": ["BAR", "BAZ"],
"kwargs": {"DEPENDS": "*", "HEADERS": "*", "SOURCES": "*"}}}

# Override configurations per-command where available
override_spec = {}
Expand Down Expand Up @@ -41,7 +43,7 @@
# 'use-space', fractional indentation is left as spaces (utf-8 0x20). If set
# to `round-up` fractional indentation is replaced with a single tab character
# (utf-8 0x09) effectively shifting the column to the next tabstop
fractional_tab_policy = 'use-space'
fractional_tab_policy = "use-space"

# If an argument group contains more than this many sub-groups (parg or kwarg
# groups) then force it to a vertical layout.
Expand Down Expand Up @@ -69,7 +71,7 @@
# to this reference: `prefix`: the start of the statement, `prefix-indent`:
# the start of the statement, plus one indentation level, `child`: align to
# the column of the arguments
dangle_align = 'prefix'
dangle_align = "prefix"

# If the statement spelling length (including space and parenthesis) is
# smaller than this amount, then force reject nested layouts.
Expand All @@ -85,13 +87,13 @@
max_lines_hwrap = 2

# What style line endings to use in the output.
line_ending = 'unix'
line_ending = "unix"

# Format command names consistently as 'lower' or 'upper' case
command_case = 'canonical'
command_case = "canonical"

# Format keywords consistently as 'lower' or 'upper' case
keyword_case = 'unchanged'
keyword_case = "unchanged"

# A list of command names which should always be wrapped
always_wrap = []
Expand Down Expand Up @@ -120,10 +122,10 @@
with section("markup"):

# What character to use for bulleted lists
bullet_char = '*'
bullet_char = "*"

# What character to use as punctuation after numerals in an enumerated list
enum_char = '.'
enum_char = "."

# If comment markup is enabled, don't reflow the first comment block in each
# listfile. Use this to preserve formatting of your copyright/license
Expand All @@ -136,15 +138,15 @@

# Regular expression to match preformat fences in comments default=
# ``r'^\s*([`~]{3}[`~]*)(.*)$'``
fence_pattern = '^\\s*([`~]{3}[`~]*)(.*)$'
fence_pattern = "^\\s*([`~]{3}[`~]*)(.*)$"

# Regular expression to match rulers in comments default=
# ``r'^\s*[^\w\s]{3}.*[^\w\s]{3}$'``
ruler_pattern = '^\\s*[^\\w\\s]{3}.*[^\\w\\s]{3}$'
ruler_pattern = "^\\s*[^\\w\\s]{3}.*[^\\w\\s]{3}$"

# If a comment line matches starts with this pattern then it is explicitly a
# trailing comment for the preceeding argument. Default is '#<'
explicit_trailing_pattern = '#<'
explicit_trailing_pattern = "#<"

# If a comment line starts with at least this many consecutive hash
# characters, then don't lstrip() them off. This allows for lazy hash rulers
Expand All @@ -164,41 +166,41 @@
with section("lint"):

# a list of lint codes to disable
disabled_codes = ['C0103', 'C0113']
disabled_codes = ["C0103", "C0113"]

# regular expression pattern describing valid function names
function_pattern = '[0-9a-z_]+'
function_pattern = "[0-9a-z_]+"

# regular expression pattern describing valid macro names
macro_pattern = '[0-9A-Z_]+'
macro_pattern = "[0-9A-Z_]+"

# regular expression pattern describing valid names for variables with global
# (cache) scope
global_var_pattern = '[A-Z][0-9A-Z_]+'
global_var_pattern = "[A-Z][0-9A-Z_]+"

# regular expression pattern describing valid names for variables with global
# scope (but internal semantic)
internal_var_pattern = '_[A-Z][0-9A-Z_]+'
internal_var_pattern = "_[A-Z][0-9A-Z_]+"

# regular expression pattern describing valid names for variables with local
# scope
local_var_pattern = '[a-z][a-z0-9_]+'
local_var_pattern = "[a-z][a-z0-9_]+"

# regular expression pattern describing valid names for privatedirectory
# variables
private_var_pattern = '_[0-9a-z_]+'
private_var_pattern = "_[0-9a-z_]+"

# regular expression pattern describing valid names for public directory
# variables
public_var_pattern = '[A-Z][0-9A-Z_]+'
public_var_pattern = "[A-Z][0-9A-Z_]+"

# regular expression pattern describing valid names for function/macro
# arguments and loop variables.
argument_var_pattern = '[a-z][a-z0-9_]+'
argument_var_pattern = "[a-z][a-z0-9_]+"

# regular expression pattern describing valid names for keywords used in
# functions or macros
keyword_pattern = '[A-Z][0-9A-Z_]+'
keyword_pattern = "[A-Z][0-9A-Z_]+"

# In the heuristic for C0201, how many conditionals to match within a loop in
# before considering the loop a parser.
Expand All @@ -224,11 +226,11 @@
emit_byteorder_mark = False

# Specify the encoding of the input file. Defaults to utf-8
input_encoding = 'utf-8'
input_encoding = "utf-8"

# Specify the encoding of the output file. Defaults to utf-8. Note that cmake
# only claims to support utf-8 so be careful when using anything else
output_encoding = 'utf-8'
output_encoding = "utf-8"

# -------------------------------------
# Miscellaneous configurations options.
Expand Down
19 changes: 10 additions & 9 deletions .github/workflows/linting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,24 @@ on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
paths:
- '.github/workflows/linting.yml'
- '**.cpp'
- '**.hpp'
- '**.py'
- '**CMakeLists.txt'
pull_request_review:
branches: [ main, develop ]

jobs:
clang-format:
linting:
runs-on: ubuntu-latest

timeout-minutes: 5

steps:
- uses: actions/checkout@v2

- name: clang-format
run: |
sudo apt update
Expand All @@ -32,14 +33,14 @@ jobs:
cd -
done

cmake-lint:
runs-on: ubuntu-latest

timeout-minutes: 5
- name: ruff
if: always()
run: |
cd ${{ github.workspace }}
pip install ruff
ruff check --diff ./

steps:
- uses: actions/checkout@v2
- name: cmake-format
- name: cmake-lint
run: |
pip install cmakelang
cmake-lint $(find . -name CMakeLists.txt) -c .cmake-lint-config.py
Expand Down
35 changes: 18 additions & 17 deletions core/src/modules/module_maker.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,40 @@
"""Generate the code for a Nextsim module class source file."""

def denamespace(nname):
"""Returns the last element of the name, without any of the qualifying
namespaces."""
"""Return the last element of the name, without any of the qualifying namespaces."""
return nname.split(":")[-1]

def generator(module_class_name, fq_interface_name, fq_impl_names, help_names):
"""Generates the text for a module support file."""
"""Generate the text for a module support file."""
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a reason the prescriptive second person form (generate), rather than the descriptive third person form (generates)? Admittedly I got this from writing javadoc comments many years ago, rather than any Python standard, but I feel it makes more sense to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It comes from D401 First line of docstring should be in imperative mood. We could ignore this rule if preferred?

Copy link
Collaborator

Choose a reason for hiding this comment

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

No, I'll try to follow that rule in future.

interface_name = denamespace(fq_interface_name)

# Add the include file for the module class
print(f"#include \"include/{module_class_name}.hpp\"")
print(f'#include "include/{module_class_name}.hpp"')
print("")
# List the include files from the implementation names
for fq_impl_name in fq_impl_names:
print(f"#include \"include/{denamespace(fq_impl_name)}.hpp\"")
print(f'#include "include/{denamespace(fq_impl_name)}.hpp"')

# Standard includes
print("")
print("#include <string>")

print("")
print("namespace Module {")

# String constants to name the implementations
for fq_impl_name in fq_impl_names:
print(f"const std::string {denamespace(fq_impl_name).upper()} = \"{fq_impl_name}\";")
print(f'const std::string {denamespace(fq_impl_name).upper()} = "{fq_impl_name}";')
print("")

# Create the functionMap from the FQ implementation and FQ module names
print("template <>")
print(f"Module<{fq_interface_name}>::map Module<{fq_interface_name}>::functionMap" + " = {")
for fq_impl_name in fq_impl_names:
print(f" {{ {denamespace(fq_impl_name).upper()}, newImpl<{fq_interface_name}, {fq_impl_name}> }},")
print("};")
print("")

# Set up the function and static pointer (FQ Module)
print("template <>")
print(f"Module<{fq_interface_name}>::fn Module<{fq_interface_name}>::spf = functionMap.at({denamespace(fq_impl_names[0]).upper()});")
Expand All @@ -44,9 +45,9 @@ def generator(module_class_name, fq_interface_name, fq_impl_names, help_names):

# Module name string
print("template <>")
print(f"std::string Module<{fq_interface_name}>::moduleName(){{ return \"{fq_interface_name}\"; }}")
print(f'std::string Module<{fq_interface_name}>::moduleName(){{ return "{fq_interface_name}"; }}')
print("")

# global functions (FQ module & module class names)

# Recursive help generation
Expand All @@ -58,15 +59,15 @@ def generator(module_class_name, fq_interface_name, fq_impl_names, help_names):
print(f"template<> HelpMap& getHelpRecursive<{fq_interface_name}>(HelpMap& map, bool getAll)")
print("{")
print(" const std::string& pfx = Nextsim::ConfiguredModule::MODULE_PREFIX;")
print(f" map[pfx].push_back({{ pfx + \".\" + Module<{fq_interface_name}>::moduleName(), ConfigType::MODULE,")
print(f' map[pfx].push_back({{ pfx + "." + Module<{fq_interface_name}>::moduleName(), ConfigType::MODULE,')
impl_namelist_uc = ", ".join(impl_names_uc)
print(f" {{ {impl_namelist_uc} }}, {impl_names_uc[0]}, \"\",")
print(" \"MODULE DESCRIPTION HERE\" });")
print(f' {{ {impl_namelist_uc} }}, {impl_names_uc[0]}, "",')
print(' "MODULE DESCRIPTION HERE" });')
for help_name in help_names:
print(f" {help_name}::getHelpRecursive(map, getAll);")
print(" return map;")
print("}")

print("template <>")
print(f"{fq_interface_name}& getImplementation<{fq_interface_name}>()")
print("{")
Expand Down Expand Up @@ -98,11 +99,11 @@ def generator(module_class_name, fq_interface_name, fq_impl_names, help_names):
import argparse
parser = argparse.ArgumentParser(description = "Write out the text for a Nextsim module class source file.",
epilog = "Suffix the interface or any implementation name with an asterisk (*) to include a call to getHelpRecursive().")
parser.add_argument("impl", metavar = "impls", nargs = '*', default = None, help = "Fully qualified name of the implementation classes.")
parser.add_argument("impl", metavar = "impls", nargs = "*", default = None, help = "Fully qualified name of the implementation classes.")
parser.add_argument("--interface", dest = "interface", required = True, help = "Fully qualified name of the interface class.")
parser.add_argument("--module-prefix", dest = "modulepfx", help = "Name of the module, will be suffixed by 'Module'.")
args = parser.parse_args()

if (len(args.impl) == 0):
raise SystemExit

Expand Down
1 change: 1 addition & 0 deletions core/test/ParaGridIO_input_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Generate the ParaGridIO_input_test.nc file used by core/test/ParaGrid_test.cpp."""
import netCDF4
import numpy as np

Expand Down
1 change: 1 addition & 0 deletions core/test/old_names.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Generate the old_names.nc file used by core/test/ParaGrid_test.cpp."""
import time

import netCDF4
Expand Down
38 changes: 19 additions & 19 deletions docs/changelog/clog.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,55 @@
"""Script for generating a changelog file."""
import os
import re
import string

# Prerequisites:
#
#
# github_changelog_generator repo -t token --date-format '%Y-%m-%d %H:%M' -o 'CHANGELOG.md'
# git tag -l -n9 > a.txt
# All tags should be of type 'v\d[\.\w]*'

if __name__ == '__main__':
with open('a.txt', 'r') as foo:
if __name__ == "__main__":
with open("a.txt", "r") as foo:
data = foo.readlines()
foo.close()
tags_title_annotation = {}
matched = None
for line in data:
if re.search('v\d[\.\w]*', line):
matched = re.findall('v\d[\.\w]*', line)[0]
if re.search(r"v\d[\.\w]*", line):
matched = re.findall(r"v\d[\.\w]*", line)[0]
tags_title_annotation[matched] = {
'title': '',
'annotation': []
"title": "",
"annotation": []
}
title = line.replace(matched, '').strip()
title = line.replace(matched, "").strip()
title = title[0].upper() + title[1:]
tags_title_annotation[matched]['title'] = title
tags_title_annotation[matched]["title"] = title
elif matched is not None:
line = line.strip()
annotation = line.replace(matched, '')
tags_title_annotation[matched]['annotation'].append(annotation)
annotation = line.strip().replace(matched, "")
tags_title_annotation[matched]["annotation"].append(annotation)

with open('CHANGELOG.md', 'r') as fil:
with open("CHANGELOG.md", "r") as fil:
data = fil.readlines()
fil.close()
matched = None
data_w = data
for index, line in enumerate(data):
if re.search('## \[v\d[\.\w]*\]', line):
matched = re.findall('v\d[\.\w]*', line)[0]
if re.search(r"## \[v\d[\.\w]*\]", line):
matched = re.findall(r"v\d[\.\w]*", line)[0]

insert_data = [
"**Description:**",
tags_title_annotation[matched]['title']
tags_title_annotation[matched]["title"]
]
values = "\n" + \
"\n".join(insert_data) + \
"\n" + \
"\n".join(tags_title_annotation[matched]['annotation']) + \
"\n".join(tags_title_annotation[matched]["annotation"]) + \
"\n"
data_w.insert(index + 1, values)
with open('CHANGELOG.md', 'w') as fin:

with open("CHANGELOG.md", "w") as fin:
fin.writelines(data_w)
fin.close()

Loading
Loading