Coverage for src/irorun/helpers.py: 100%

1# Copyright 2025 Faith O. Oyedemi

2# SPDX-License-Identifier: Apache-2.0

3#

4# Licensed under the Apache License, Version 2.0 (the "License");

5# you may not use this file except in compliance with the License.

6# You may obtain a copy of the License at

7#

8# http://www.apache.org/licenses/LICENSE-2.0

9#

10# Unless required by applicable law or agreed to in writing, software

11# distributed under the License is distributed on an "AS IS" BASIS,

12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

13# See the License for the specific language governing permissions and

14# limitations under the License.

15#

16# For more details, see the full text of the Apache License at:

17# http://www.apache.org/licenses/LICENSE-2.0

18

19 import os

20 import subprocess

21 from collections.abc import Iterable

22 from contextlib import contextmanager

23 from enum import Enum

24 from pathlib import Path

25 from typing import Optional

26

27 import typer

28

29

30 class EnvManager(Enum):

31 POETRY = 'poetry'

32 UV = 'uv'

33 VIRTUALENV = 'virtualenv'

34

35

36@contextmanager

37 def change_dir(new_dir: str):

38 """Context manager for changing the current working directory."""

39 previous_dir = os.getcwd()

40 os.chdir(new_dir)

41 try:

42 yield

43 finally:

44 os.chdir(previous_dir)

45

46

47 def run_command(cmd: list[str], cwd: Optional[str] = None) -> None:

48 """Wrapper for subprocess.run to execute a command and handle errors."""

49 try:

50 subprocess.run(cmd, check=True, cwd=cwd)

51 except subprocess.CalledProcessError as e:

52 typer.echo(f'Error running command {cmd}: {e}', err=True)

53 raise typer.Exit(1) from e

54

55

56 def add_dependencies(package_manager: EnvManager, dependencies: list[str]) -> None:

57 """

58 Adds dependencies using the specified package manager.

59 Only supports POETRY and UV.

60 """

61 if package_manager not in (EnvManager.POETRY, EnvManager.UV):

62 typer.echo('Invalid package manager', err=True)

63 raise typer.Exit(1)

64 run_command([package_manager.value, 'add'] + dependencies)

65 typer.echo(f'Added dependencies: {dependencies}')

66

67

68 def create_poetry_project(

69 project_dir: str, dependencies: Optional[list[str]] = None

70) -> None:

71 """

72 Creates a new Poetry project and optionally installs dependencies.

73 """

74 run_command(['poetry', 'new', project_dir])

75 typer.echo(f'Created new Poetry project in {project_dir}')

76 with change_dir(project_dir):

77 if dependencies:

78 typer.echo(f'Installing dependencies: {dependencies}')

79 add_dependencies(EnvManager.POETRY, dependencies)

80

81

82 def create_uv_project(

83 project_dir: str, venv_name: str, dependencies: Optional[list[str]] = None

84) -> None:

85 """

86 Creates a new project with a virtual environment using uv.

87 """

88 run_command(['uv', 'init', project_dir])

89 with change_dir(project_dir):

90 run_command(['uv', 'venv', venv_name])

91 typer.echo(f'Created virtual environment "{venv_name}" in {project_dir}')

92 if dependencies:

93 typer.echo(f'Installing dependencies: {dependencies}')

94 add_dependencies(EnvManager.UV, dependencies)

95

96

97 def create_virtualenv_project(

98 project_dir: str, venv_name: str, dependencies: Optional[list[str]] = None

99) -> None:

100 """

101 Creates a new project directory and virtual environment using virtualenv.

102 """

103 Path(project_dir).mkdir(parents=True, exist_ok=True)

104 with change_dir(project_dir):

105 run_command(['virtualenv', venv_name])

106

107 # Determine the correct path for the pip executable in the virtual environment.

108 venv_path = Path(venv_name)

109 bin_dir = 'Scripts' if os.name == 'nt' else 'bin'

110 pip_executable = str(venv_path / bin_dir / 'pip')

111

112 run_command([pip_executable, 'install', '-U', 'pip'])

113 if dependencies:

114 typer.echo(f'Installing dependencies: {dependencies}')

115 run_command([pip_executable, 'install'] + dependencies)

116 typer.echo('Dependencies installed successfully.')

117 typer.echo(f'Created virtual environment project in {project_dir}')

118

119

120 def create_subdirectories(project_dir: str, subdirectories: Iterable[str]) -> None:

121 """

122 Creates subdirectories within a project directory.

123

124 Parameters:

125 project_dir: The base project directory.

126 subdirectories: An iterable of subdirectory names to create under project_dir.

127 """

128 base = Path(project_dir)

129 for subdir in subdirectories:

130 sub_path = base / subdir

131 sub_path.mkdir(parents=True, exist_ok=True)

132 typer.echo(f'Created subdirectory: {sub_path}')