Coverage for rust2rpm/cli.py: 100%
106 statements
« prev ^ index » next coverage.py v7.6.7, created at 2024-11-25 18:52 +0100
« prev ^ index » next coverage.py v7.6.7, created at 2024-11-25 18:52 +0100
1"""Module containing command-line argument parsing logic."""
3import argparse
4from argparse import ArgumentParser, RawTextHelpFormatter
5from dataclasses import KW_ONLY, dataclass
6from enum import StrEnum, auto
7from pathlib import Path
9from rust2rpm.sysinfo import TARGET_NAMES, Target, get_default_target
12class VendorMode(StrEnum):
13 """Description of different modes for using vendored dependencies."""
15 OFF = auto()
16 """Generate a spec file without any references to a vendor tarball."""
18 AUTO = auto()
19 """Generate a spec file for use with a vendor tarball, and create a
20 tarball with vendored dependencies with default settings."""
22 MANUAL = auto()
23 """Generate a spec file for use with a vendor tarball, but require
24 manual assembly of vendored sources into a tarball."""
27VENDOR_MODES = [mode.value for mode in VendorMode]
30def get_parser() -> ArgumentParser:
31 """Construct an `argparse.ArgumentParser` for parsing command-line arguments."""
32 parser = ArgumentParser("rust2rpm", formatter_class=RawTextHelpFormatter)
34 # ==================== #
35 # positional arguments #
36 # ==================== #
38 # pkgid: str | None = None
39 parser.add_argument(
40 "pkgid",
41 nargs="?",
42 help="""\
43crate (crate on crates.io)
44crate@version (pkgid on crates.io)
45@version (auto-detect crate name)""",
46 )
48 # ==================== #
49 # options with a value #
50 # ==================== #
52 # config_file: str | None
53 parser.add_argument(
54 "-C",
55 "--config-file",
56 action="store",
57 default=None,
58 dest="config_file",
59 help="Path to rust2rpm.toml config file (defaults to ./rust2rpm.toml)",
60 )
62 # output_directory: str | None
63 parser.add_argument(
64 "-o",
65 "--output-directory",
66 action="store",
67 default=None,
68 dest="output_directory",
69 help="Target directory for any created files",
70 )
72 # path: str | None = None
73 parser.add_argument(
74 "--path",
75 action="store",
76 default=None,
77 dest="path",
78 help="Path to local project directory or Cargo.toml file to check instead of crates.io",
79 )
81 # target: str | None = None
82 parser.add_argument(
83 "-t",
84 "--target",
85 action="store",
86 choices=TARGET_NAMES,
87 default=None,
88 dest="target",
89 help="Distribution target",
90 )
92 # vendor: VendorMode = VendorMode.OFF
93 parser.add_argument(
94 "-V",
95 "--vendor",
96 action="store",
97 choices=VENDOR_MODES,
98 default=None,
99 dest="vendor",
100 help="Write a spec for building with vendored dependencies (default=off)",
101 )
103 # ==================== #
104 # flag-only options #
105 # ==================== #
107 # rpmautospec: bool | None = None
108 parser.add_argument(
109 "-a",
110 "--rpmautospec",
111 action="store_true",
112 default=None,
113 dest="rpmautospec",
114 help="Use autorelease and autochangelog features",
115 )
116 parser.add_argument(
117 "--no-rpmautospec",
118 action="store_false",
119 default=None,
120 dest="rpmautospec",
121 help="Do not use rpmautospec",
122 )
124 # auto_changelog_entry: bool = True
125 parser.add_argument(
126 "--no-auto-changelog-entry",
127 action="store_false",
128 dest="auto_changelog_entry",
129 help=argparse.SUPPRESS,
130 )
132 # compat: bool = False
133 parser.add_argument(
134 "--compat",
135 action="store_true",
136 dest="compat",
137 help="Create a compat package with appropriate version suffix",
138 )
140 # ignore_missing_license_files: bool = False
141 parser.add_argument(
142 "-I",
143 "--ignore-missing-license-files",
144 action="store_true",
145 dest="ignore_missing_license_files",
146 help=argparse.SUPPRESS,
147 )
149 # existence_check: bool = True
150 parser.add_argument(
151 "--no-existence-check",
152 action="store_false",
153 dest="existence_check",
154 help="Do not check whether the package already exists in dist-git",
155 )
157 # offline: bool = False
158 parser.add_argument(
159 "-O",
160 "--offline",
161 action="store_true",
162 dest="offline",
163 help="Skip network queries to crates.io and / or src.fedoraproject.org",
164 )
166 # patch: bool = False
167 parser.add_argument(
168 "-p",
169 "--patch",
170 action="store_true",
171 dest="patch",
172 help="Do manual patching of Cargo.toml",
173 )
175 # patch_foreign: bool = True
176 parser.add_argument(
177 "--no-patch-foreign",
178 action="store_false",
179 dest="patch_foreign",
180 help="Do not automatically drop foreign dependencies in Cargo.toml",
181 )
183 # relative_license_paths: bool = False
184 parser.add_argument(
185 "--relative-license-paths",
186 action="store_true",
187 dest="relative_license_paths",
188 help=argparse.SUPPRESS,
189 )
191 # reuse_patch: bool = False
192 parser.add_argument(
193 "-r",
194 "--reuse-patch",
195 action="store_true",
196 dest="reuse_patch",
197 help="Attempt to re-apply existing Cargo.toml patch",
198 )
200 # store_crate: bool = False
201 parser.add_argument(
202 "-s",
203 "--store-crate",
204 action="store_true",
205 dest="store_crate",
206 help="Store crate in current directory",
207 )
209 # validate_only: bool = False
210 parser.add_argument(
211 "-v",
212 "--validate-only",
213 action="store_true",
214 dest="validate_only",
215 help="Validate rust2rpm.toml config file and exit",
216 )
218 return parser
221@dataclass
222class Options:
223 """Collection of boolean or flag-like command-line arguments."""
225 _: KW_ONLY
226 # keep sorted alphabetically
228 auto_changelog_entry: bool = True
229 """Include an auto-generated changelog entry in rendered spec files
230 (default: ON)."""
232 compat: bool = False
233 """Apply *"compat"* version suffis to the RPM `Name` and file name of the
234 written spec file (default: OFF).
235 """
237 existence_check: bool = True
238 """Check whether the package that a spec file is being generated for already
239 exists in the distribution repositories (default: ON)."""
241 ignore_missing_license_files: bool = False
242 """Do not treat it as a hard error if the heuristics for detecting license
243 files in project sources don't detect any files (default: OFF)."""
245 offline: bool = False
246 """Skip any operations that would require accessing the network and fail if
247 any requested operation would require internet access default: OFF)."""
249 patch: bool = False
250 """Open an editor for interactively patching `Cargo.toml` metadata. This is
251 the only supported way of editing `Cargo.toml` since it is the only way to
252 have metadata changes affect the generated spec file (default: OFF)."""
254 patch_foreign: bool = True
255 """Automatically generate a patch that strips "foreign" dependencies from
256 `Cargo.toml` (i.e. dependencies for non-Linux targets; default: ON)."""
258 relative_license_paths: bool = False
259 """Use relative paths instead of absolute paths for `%license` files in
260 `%files` lists in spec files. This usually causes built RPM packages to
261 contain two copies of these files (default: OFF)."""
263 reuse_patch: bool = False
264 """Attempt to re-apply existing `Cargo.toml` patch. Falls back to opening an
265 editor if the `patch` flag is set (default: OFF)."""
267 rpmautospec: bool | None = None
268 """Override automatically detected use of rpmautospec in an existing spec
269 file (default: fall back to autodetection)."""
271 store_crate: bool = False
272 """Copy the `.crate` file from the download cache into the output directory
273 after downloading and processing it. This option has no effect when using
274 local sources (default: OFF)."""
276 validate_only: bool = False
277 """Only attempt to load and validate a `rust2rpm.toml` configuration file
278 and exit (default: OFF)."""
280 vendor: VendorMode = VendorMode.OFF
281 """Use vendored dependencies and / or create a tarball containing vendored
282 dependencies (default: OFF)."""
285@dataclass(frozen=True)
286class CliArgs:
287 """Parsed and processed command-line arguments."""
289 pkgid: str | None
290 """Project identifier consisting of name and version (both optional) in the
291 format `<name>`, `<name>@<version>`, `@<version>`, or `None`."""
293 output_directory: Path | None
294 """Optional output directory (default: current working directory)."""
296 path: Path | None
297 """Optional path to a local `.crate` file, local `Cargo.toml` file, or a
298 directory containing a `Cargo.toml` file for operating on local sources."""
300 config_file: Path | None
301 """Optional explicit path to a `rust2rpm.toml` configuration file
302 (default: `rust2rpm.toml` in current working directory, if it exists)."""
304 target: Target
305 """Target to generate spec file for (default: based on current system)."""
307 options: Options
308 """Collection of other flags and flag-like command-line arguments."""
310 @staticmethod
311 def parse(args: list[str] | None = None) -> "CliArgs":
312 """Parse a list of arguments into `CliArgs`."""
313 parser = get_parser()
314 parsed = parser.parse_args(args)
316 output_directory = Path(parsed.output_directory) if parsed.output_directory else None
317 path = Path(parsed.path) if parsed.path else None
318 config_file = Path(parsed.config_file) if parsed.config_file else None
319 target = Target(parsed.target) if parsed.target else Target(get_default_target())
320 vendor = VendorMode(parsed.vendor) if parsed.vendor else VendorMode.OFF
322 options = Options(
323 auto_changelog_entry=parsed.auto_changelog_entry,
324 compat=parsed.compat,
325 existence_check=parsed.existence_check,
326 ignore_missing_license_files=parsed.ignore_missing_license_files,
327 offline=parsed.offline,
328 patch=parsed.patch,
329 patch_foreign=parsed.patch_foreign,
330 relative_license_paths=parsed.relative_license_paths,
331 reuse_patch=parsed.reuse_patch,
332 rpmautospec=parsed.rpmautospec,
333 store_crate=parsed.store_crate,
334 validate_only=parsed.validate_only,
335 vendor=vendor,
336 )
338 return CliArgs(
339 pkgid=parsed.pkgid,
340 output_directory=output_directory,
341 path=path,
342 config_file=config_file,
343 target=target,
344 options=options,
345 )
347 @property
348 def name(self) -> str | None:
349 """The optional `<name>` part of the `pkgid` argument."""
350 if self.pkgid is None:
351 return None
353 if "@" in self.pkgid:
354 name, _version = self.pkgid.split("@")
355 return name if name != "" else None
357 return self.pkgid
359 @property
360 def version(self) -> str | None:
361 """The optional `<version>` part of the `pkgid` argument."""
362 if self.pkgid is None:
363 return None
365 if "@" in self.pkgid:
366 _name, version = self.pkgid.split("@")
367 return version if version != "" else None
369 return None