Coverage for heliumcli/actions/prepcode.py: 94.06%

101 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-22 22:16 +0000

1import datetime 

2import os 

3import shutil 

4import subprocess 

5 

6import git 

7 

8from .. import utils 

9 

10__author__ = "Alex Laird" 

11__copyright__ = "Copyright 2018, Helium Edu" 

12__version__ = "1.5.0" 

13 

14 

15class PrepCodeAction: 

16 def __init__(self): 

17 self.name = "prep-code" 

18 self.help = "Prepare code for release build, updating version and copyright information in project files" 

19 

20 def setup(self, subparsers): 

21 parser = subparsers.add_parser(self.name, help=self.help) 

22 parser.add_argument("--roles", action="store", type=str, nargs="*", 

23 help="Limit the project roles to be prepped") 

24 parser.set_defaults(action=self) 

25 

26 def run(self, args): 

27 self._copyright_name = utils.get_copyright_name() 

28 self._current_year = str(datetime.date.today().year) 

29 self._current_version = None 

30 

31 config = utils.get_config() 

32 projects_dir = utils.get_projects_dir() 

33 

34 for line in open(os.path.join(projects_dir, config["versionInfo"]["project"], config["versionInfo"]["path"]), 

35 "r"): 

36 if config["versionInfo"]["path"].endswith(".py") and line.startswith("__version__ = "): 

37 self._current_version = line.strip().split("__version__ = \"")[1].rstrip("\"") 

38 

39 if not self._current_version: 

40 print("WARN: helium-cli does not know how to process this type of file for version information: {}".format( 

41 config["versionInfo"]["path"])) 

42 

43 return 

44 

45 for project in utils.get_projects(config): 

46 if args.roles and project not in args.roles: 

47 continue 

48 

49 if config["projectsRelativeDir"] != ".": 

50 project_path = os.path.join(projects_dir, project) 

51 else: 

52 project_path = os.path.join(projects_dir) 

53 

54 repo = git.Repo(project_path) 

55 repo.git.fetch(tags=True, prune=True) 

56 

57 version_tags = utils.sort_tags(repo.tags) 

58 

59 if len(version_tags) == 0: 

60 print("No version tags have been created yet.") 

61 

62 return 

63 

64 latest_tag = version_tags[-1] 

65 changes = latest_tag.commit.diff(None) 

66 

67 print( 

68 "Checking the {} file(s) in \"{}\" that have been modified since {} was tagged ...".format(len(changes), 

69 project, 

70 latest_tag.tag.tag)) 

71 print("-------------------------------") 

72 

73 count = 0 

74 for change in changes: 

75 file_path = os.path.join(project_path, change.b_rawpath.decode("utf-8")) 

76 

77 if os.path.exists(file_path) and not os.path.isdir(file_path) and os.path.splitext(file_path)[1] in \ 

78 [".py", ".js", ".jsx", ".css", ".scss"]: 

79 if self._process_file(file_path): 

80 count += 1 

81 

82 print("-------------------------------") 

83 print("Updated {} file(s).".format(count)) 

84 print("") 

85 

86 if os.path.exists(os.path.join(project_path, "package.json")): 

87 self._process_file(os.path.join(project_path, "package.json")) 

88 

89 # This is to ensure the lock file also gets updated 

90 subprocess.call(["npm", "--prefix", project_path, "install"]) 

91 

92 def _process_file(self, file_path): 

93 filename = os.path.basename(file_path) 

94 initial_file = open(file_path, "r") 

95 new_file = open(file_path + ".tmp", "w") 

96 

97 updated = False 

98 for line in initial_file: 

99 line_updated = False 

100 

101 if file_path.endswith(".py"): 

102 line, line_updated = self._process_python_line(line) 

103 elif file_path.endswith(".js") or file_path.endswith(".jsx") or \ 

104 file_path.endswith(".css") or file_path.endswith(".scss"): 

105 line, line_updated = self._process_js_or_css_line(line) 

106 elif filename == "package.json": 

107 line, line_updated = self._process_package_json(line) 

108 # TODO: implement other known types 

109 

110 if line_updated: 

111 updated = True 

112 

113 new_file.write(line) 

114 

115 initial_file.close() 

116 new_file.close() 

117 

118 if updated: 

119 print("Updated {}.".format(file_path)) 

120 

121 shutil.copy(file_path + ".tmp", file_path) 

122 os.remove(file_path + ".tmp") 

123 

124 return updated 

125 

126 def _process_python_line(self, line): 

127 if utils.should_update(line, "__version__ = \"{}\"".format(self._current_version), "__version__ ="): 

128 

129 line = "__version__ = \"{}\"\n".format(self._current_version) 

130 return line, True 

131 elif utils.should_update(line, 

132 "__copyright__ = \"Copyright {}, {}\"".format(self._current_year, self._copyright_name), 

133 "__copyright__ = ", "{}\"".format(self._copyright_name)): 

134 

135 line = "__copyright__ = \"Copyright {}, {}\"\n".format(self._current_year, self._copyright_name) 

136 return line, True 

137 return line, False 

138 

139 def _process_js_or_css_line(self, line): 

140 if utils.should_update(line, "* @version " + self._current_version, "* @version"): 

141 line = " * @version {}\n".format(self._current_version) 

142 return line, True 

143 elif utils.should_update(line, "* Copyright (c) {} {}.".format(self._current_year, self._copyright_name), 

144 "* Copyright (c)", "{}.".format(self._copyright_name)): 

145 line = " * Copyright (c) {} {}.\n".format(self._current_year, self._copyright_name) 

146 return line, True 

147 return line, False 

148 

149 def _process_package_json(self, line): 

150 if utils.should_update(line, "\"version\": \"{}\",".format(self._current_version), "\"version\": \""): 

151 line = " \"version\": \"{}\",\n".format(self._current_version) 

152 return line, True 

153 return line, False