atomicshop 2.11.47__py3-none-any.whl → 3.10.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
  3. atomicshop/a_mains/addons/process_list/compile.cmd +7 -0
  4. atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
  5. atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
  6. atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
  7. atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +8 -1
  8. atomicshop/a_mains/dns_gateway_setting.py +11 -0
  9. atomicshop/a_mains/get_local_tcp_ports.py +85 -0
  10. atomicshop/a_mains/github_wrapper.py +11 -0
  11. atomicshop/a_mains/install_ca_certificate.py +172 -0
  12. atomicshop/{addons/mains → a_mains}/msi_unpacker.py +3 -1
  13. atomicshop/a_mains/process_from_port.py +119 -0
  14. atomicshop/a_mains/set_default_dns_gateway.py +90 -0
  15. atomicshop/a_mains/update_config_toml.py +38 -0
  16. atomicshop/appointment_management.py +5 -3
  17. atomicshop/basics/ansi_escape_codes.py +3 -1
  18. atomicshop/basics/argparse_template.py +2 -0
  19. atomicshop/basics/booleans.py +27 -30
  20. atomicshop/basics/bytes_arrays.py +43 -0
  21. atomicshop/basics/classes.py +149 -1
  22. atomicshop/basics/dicts.py +12 -0
  23. atomicshop/basics/enums.py +2 -2
  24. atomicshop/basics/exceptions.py +5 -1
  25. atomicshop/basics/list_of_classes.py +29 -0
  26. atomicshop/basics/list_of_dicts.py +69 -5
  27. atomicshop/basics/lists.py +14 -0
  28. atomicshop/basics/multiprocesses.py +374 -50
  29. atomicshop/basics/package_module.py +10 -0
  30. atomicshop/basics/strings.py +160 -7
  31. atomicshop/basics/threads.py +14 -0
  32. atomicshop/basics/tracebacks.py +13 -4
  33. atomicshop/certificates.py +153 -52
  34. atomicshop/config_init.py +12 -7
  35. atomicshop/console_user_response.py +7 -14
  36. atomicshop/consoles.py +9 -0
  37. atomicshop/datetimes.py +98 -0
  38. atomicshop/diff_check.py +340 -40
  39. atomicshop/dns.py +128 -12
  40. atomicshop/etws/_pywintrace_fix.py +17 -0
  41. atomicshop/etws/const.py +38 -0
  42. atomicshop/etws/providers.py +21 -0
  43. atomicshop/etws/sessions.py +43 -0
  44. atomicshop/etws/trace.py +168 -0
  45. atomicshop/etws/traces/trace_dns.py +162 -0
  46. atomicshop/etws/traces/trace_sysmon_process_creation.py +126 -0
  47. atomicshop/etws/traces/trace_tcp.py +130 -0
  48. atomicshop/file_io/csvs.py +222 -24
  49. atomicshop/file_io/docxs.py +35 -18
  50. atomicshop/file_io/file_io.py +35 -19
  51. atomicshop/file_io/jsons.py +49 -0
  52. atomicshop/file_io/tomls.py +139 -0
  53. atomicshop/filesystem.py +864 -293
  54. atomicshop/get_process_list.py +133 -0
  55. atomicshop/{process_name_cmd.py → get_process_name_cmd_dll.py} +52 -19
  56. atomicshop/http_parse.py +149 -93
  57. atomicshop/ip_addresses.py +6 -1
  58. atomicshop/mitm/centered_settings.py +132 -0
  59. atomicshop/mitm/config_static.py +207 -0
  60. atomicshop/mitm/config_toml_editor.py +55 -0
  61. atomicshop/mitm/connection_thread_worker.py +875 -357
  62. atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
  63. atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
  64. atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
  65. atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
  66. atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
  67. atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
  68. atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
  69. atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
  70. atomicshop/mitm/engines/create_module_template.py +58 -14
  71. atomicshop/mitm/import_config.py +359 -139
  72. atomicshop/mitm/initialize_engines.py +160 -74
  73. atomicshop/mitm/message.py +64 -23
  74. atomicshop/mitm/mitm_main.py +892 -0
  75. atomicshop/mitm/recs_files.py +183 -0
  76. atomicshop/mitm/shared_functions.py +4 -10
  77. atomicshop/mitm/ssh_tester.py +82 -0
  78. atomicshop/mitm/statistic_analyzer.py +257 -166
  79. atomicshop/mitm/statistic_analyzer_helper/analyzer_helper.py +136 -0
  80. atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +525 -0
  81. atomicshop/monitor/change_monitor.py +96 -120
  82. atomicshop/monitor/checks/dns.py +139 -70
  83. atomicshop/monitor/checks/file.py +77 -0
  84. atomicshop/monitor/checks/network.py +81 -77
  85. atomicshop/monitor/checks/process_running.py +33 -34
  86. atomicshop/monitor/checks/url.py +94 -0
  87. atomicshop/networks.py +671 -0
  88. atomicshop/on_exit.py +205 -0
  89. atomicshop/package_mains_processor.py +84 -0
  90. atomicshop/permissions/permissions.py +22 -0
  91. atomicshop/permissions/ubuntu_permissions.py +239 -0
  92. atomicshop/permissions/win_permissions.py +33 -0
  93. atomicshop/print_api.py +24 -41
  94. atomicshop/process.py +63 -17
  95. atomicshop/process_poller/__init__.py +0 -0
  96. atomicshop/process_poller/pollers/__init__.py +0 -0
  97. atomicshop/process_poller/pollers/psutil_pywin32wmi_dll.py +95 -0
  98. atomicshop/process_poller/process_pool.py +207 -0
  99. atomicshop/process_poller/simple_process_pool.py +311 -0
  100. atomicshop/process_poller/tracer_base.py +45 -0
  101. atomicshop/process_poller/tracers/__init__.py +0 -0
  102. atomicshop/process_poller/tracers/event_log.py +46 -0
  103. atomicshop/process_poller/tracers/sysmon_etw.py +68 -0
  104. atomicshop/python_file_patcher.py +1 -1
  105. atomicshop/python_functions.py +27 -75
  106. atomicshop/question_answer_engine.py +2 -2
  107. atomicshop/scheduling.py +24 -5
  108. atomicshop/sound.py +4 -2
  109. atomicshop/speech_recognize.py +8 -0
  110. atomicshop/ssh_remote.py +158 -172
  111. atomicshop/startup/__init__.py +0 -0
  112. atomicshop/startup/win/__init__.py +0 -0
  113. atomicshop/startup/win/startup_folder.py +53 -0
  114. atomicshop/startup/win/task_scheduler.py +119 -0
  115. atomicshop/system_resource_monitor.py +61 -46
  116. atomicshop/system_resources.py +8 -8
  117. atomicshop/tempfiles.py +1 -2
  118. atomicshop/timer.py +30 -11
  119. atomicshop/urls.py +41 -0
  120. atomicshop/venvs.py +28 -0
  121. atomicshop/versioning.py +27 -0
  122. atomicshop/web.py +110 -25
  123. atomicshop/web_apis/__init__.py +0 -0
  124. atomicshop/web_apis/google_custom_search.py +44 -0
  125. atomicshop/web_apis/google_llm.py +188 -0
  126. atomicshop/websocket_parse.py +450 -0
  127. atomicshop/wrappers/certauthw/certauth.py +1 -0
  128. atomicshop/wrappers/cryptographyw.py +29 -8
  129. atomicshop/wrappers/ctyping/etw_winapi/__init__.py +0 -0
  130. atomicshop/wrappers/ctyping/etw_winapi/const.py +335 -0
  131. atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +393 -0
  132. atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
  133. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  134. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +13 -9
  135. atomicshop/wrappers/ctyping/msi_windows_installer/tables.py +35 -0
  136. atomicshop/wrappers/ctyping/setup_device.py +466 -0
  137. atomicshop/wrappers/ctyping/win_console.py +39 -0
  138. atomicshop/wrappers/dockerw/dockerw.py +113 -2
  139. atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
  140. atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
  141. atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
  142. atomicshop/wrappers/factw/get_file_data.py +12 -5
  143. atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
  144. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
  145. atomicshop/wrappers/factw/postgresql/firmware.py +4 -6
  146. atomicshop/wrappers/githubw.py +583 -51
  147. atomicshop/wrappers/loggingw/consts.py +49 -0
  148. atomicshop/wrappers/loggingw/filters.py +102 -0
  149. atomicshop/wrappers/loggingw/formatters.py +58 -71
  150. atomicshop/wrappers/loggingw/handlers.py +459 -40
  151. atomicshop/wrappers/loggingw/loggers.py +19 -0
  152. atomicshop/wrappers/loggingw/loggingw.py +1010 -178
  153. atomicshop/wrappers/loggingw/reading.py +344 -19
  154. atomicshop/wrappers/mongodbw/__init__.py +0 -0
  155. atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
  156. atomicshop/wrappers/mongodbw/mongodbw.py +1432 -0
  157. atomicshop/wrappers/netshw.py +271 -0
  158. atomicshop/wrappers/playwrightw/engine.py +34 -19
  159. atomicshop/wrappers/playwrightw/infra.py +5 -0
  160. atomicshop/wrappers/playwrightw/javascript.py +7 -3
  161. atomicshop/wrappers/playwrightw/keyboard.py +14 -0
  162. atomicshop/wrappers/playwrightw/scenarios.py +172 -5
  163. atomicshop/wrappers/playwrightw/waits.py +9 -7
  164. atomicshop/wrappers/powershell_networking.py +80 -0
  165. atomicshop/wrappers/psutilw/processes.py +81 -0
  166. atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
  167. atomicshop/wrappers/psutilw/psutilw.py +9 -0
  168. atomicshop/wrappers/pyopensslw.py +9 -2
  169. atomicshop/wrappers/pywin32w/__init__.py +0 -0
  170. atomicshop/wrappers/pywin32w/cert_store.py +116 -0
  171. atomicshop/wrappers/pywin32w/console.py +34 -0
  172. atomicshop/wrappers/pywin32w/win_event_log/__init__.py +0 -0
  173. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  174. atomicshop/wrappers/pywin32w/win_event_log/subscribe.py +212 -0
  175. atomicshop/wrappers/pywin32w/win_event_log/subscribes/__init__.py +0 -0
  176. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +57 -0
  177. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +49 -0
  178. atomicshop/wrappers/pywin32w/win_event_log/subscribes/schannel_logging.py +97 -0
  179. atomicshop/wrappers/pywin32w/winshell.py +19 -0
  180. atomicshop/wrappers/pywin32w/wmis/__init__.py +0 -0
  181. atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
  182. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
  183. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
  184. atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
  185. atomicshop/wrappers/socketw/accepter.py +21 -7
  186. atomicshop/wrappers/socketw/certificator.py +216 -150
  187. atomicshop/wrappers/socketw/creator.py +190 -50
  188. atomicshop/wrappers/socketw/dns_server.py +500 -173
  189. atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
  190. atomicshop/wrappers/socketw/process_getter.py +86 -0
  191. atomicshop/wrappers/socketw/receiver.py +144 -102
  192. atomicshop/wrappers/socketw/sender.py +65 -35
  193. atomicshop/wrappers/socketw/sni.py +334 -165
  194. atomicshop/wrappers/socketw/socket_base.py +134 -0
  195. atomicshop/wrappers/socketw/socket_client.py +137 -95
  196. atomicshop/wrappers/socketw/socket_server_tester.py +14 -9
  197. atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
  198. atomicshop/wrappers/socketw/ssl_base.py +15 -14
  199. atomicshop/wrappers/socketw/statistics_csv.py +148 -17
  200. atomicshop/wrappers/sysmonw.py +157 -0
  201. atomicshop/wrappers/ubuntu_terminal.py +65 -26
  202. atomicshop/wrappers/win_auditw.py +189 -0
  203. atomicshop/wrappers/winregw/__init__.py +0 -0
  204. atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
  205. atomicshop/wrappers/winregw/winreg_network.py +232 -0
  206. {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -49
  207. atomicshop-3.10.5.dist-info/RECORD +306 -0
  208. {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
  209. atomicshop/_basics_temp.py +0 -101
  210. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  211. atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
  212. atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
  213. atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
  214. atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
  215. atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
  216. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  217. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  218. atomicshop/addons/package_setup/Setup.cmd +0 -7
  219. atomicshop/addons/process_list/compile.cmd +0 -2
  220. atomicshop/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
  221. atomicshop/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
  222. atomicshop/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
  223. atomicshop/archiver/_search_in_zip.py +0 -189
  224. atomicshop/archiver/archiver.py +0 -34
  225. atomicshop/archiver/search_in_archive.py +0 -250
  226. atomicshop/archiver/sevenz_app_w.py +0 -86
  227. atomicshop/archiver/sevenzs.py +0 -44
  228. atomicshop/archiver/zips.py +0 -293
  229. atomicshop/etw/dns_trace.py +0 -118
  230. atomicshop/etw/etw.py +0 -61
  231. atomicshop/file_types.py +0 -24
  232. atomicshop/mitm/engines/create_module_template_example.py +0 -13
  233. atomicshop/mitm/initialize_mitm_server.py +0 -240
  234. atomicshop/monitor/checks/hash.py +0 -44
  235. atomicshop/monitor/checks/hash_checks/file.py +0 -55
  236. atomicshop/monitor/checks/hash_checks/url.py +0 -62
  237. atomicshop/pbtkmultifile_argparse.py +0 -88
  238. atomicshop/permissions.py +0 -110
  239. atomicshop/process_poller.py +0 -237
  240. atomicshop/script_as_string_processor.py +0 -38
  241. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  242. atomicshop/ssh_scripts/process_from_port.py +0 -27
  243. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  244. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  245. atomicshop/wrappers/dockerw/install_docker.py +0 -209
  246. atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
  247. atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
  248. atomicshop/wrappers/ffmpegw.py +0 -125
  249. atomicshop/wrappers/loggingw/checks.py +0 -20
  250. atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
  251. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  252. atomicshop/wrappers/socketw/base.py +0 -59
  253. atomicshop/wrappers/socketw/get_process.py +0 -107
  254. atomicshop/wrappers/wslw.py +0 -191
  255. atomicshop-2.11.47.dist-info/RECORD +0 -251
  256. /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
  257. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  258. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  259. /atomicshop/{addons/mains → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  260. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  261. /atomicshop/{addons/mains → a_mains}/search_for_hyperlinks_in_docx.py +0 -0
  262. /atomicshop/{archiver → etws}/__init__.py +0 -0
  263. /atomicshop/{etw → etws/traces}/__init__.py +0 -0
  264. /atomicshop/{monitor/checks/hash_checks → mitm/statistic_analyzer_helper}/__init__.py +0 -0
  265. /atomicshop/{wrappers/nodejsw → permissions}/__init__.py +0 -0
  266. /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
  267. {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
  268. {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
@@ -1,19 +1,32 @@
1
1
  import requests
2
2
  import fnmatch
3
+ import os
4
+ import tempfile
5
+ from typing import Literal
3
6
 
4
- from .. import web, urls
7
+ from .. import web, urls, filesystem
5
8
  from ..print_api import print_api
6
9
 
7
10
 
11
+ class MoreThanOneReleaseFoundError(Exception):
12
+ pass
13
+
14
+ class NoReleaseFoundError(Exception):
15
+ pass
16
+
17
+
8
18
  class GitHubWrapper:
9
- # You also can use '.tar.gz' as extension.
10
19
  def __init__(
11
20
  self,
12
21
  user_name: str = None,
13
22
  repo_name: str = None,
14
23
  repo_url: str = None,
15
- branch: str = 'master',
16
- branch_file_extension: str = '.zip'
24
+ branch: str = 'main',
25
+ path: str = None,
26
+ pat: str = None,
27
+ branch_file_extension: Literal[
28
+ 'zip',
29
+ 'tar.gz'] = 'zip'
17
30
  ):
18
31
  """
19
32
  This class is a wrapper for GitHub repositories. It can download the branch file from the repository and extract
@@ -26,7 +39,11 @@ class GitHubWrapper:
26
39
  You can provide the full url to the repository directly and then extract the user_name and repo_name from it
27
40
  with the 'build_links_from_repo_url' function.
28
41
  :param branch: str, the branch name. The default is 'master'.
29
- :param branch_file_extension: str, the branch file extension. The default is '.zip'.
42
+ :param path: str, the path to the file/folder inside the repo that we'll do certain actions on.
43
+ Actions example: get_latest_commit_comment, download_path_from_branch.
44
+ :param pat: str, the personal access token to the repo.
45
+ :param branch_file_extension: str, the branch file extension. The default is 'zip'.
46
+ You also can use 'tar.gz' as extension.
30
47
 
31
48
  ================================================================================================================
32
49
  Usage to download the 'master' branch file:
@@ -61,7 +78,7 @@ class GitHubWrapper:
61
78
  Usage to download the latest release where the file name is 'test_file.zip':
62
79
  git_wrapper = GitHubWrapper(user_name='user_name', repo_name='repo_name')
63
80
  git_wrapper.download_and_extract_latest_release(
64
- target_directory='target_directory', string_pattern='test_*.zip')
81
+ target_directory='target_directory', asset_pattern='test_*.zip')
65
82
  ================================================================================================================
66
83
  Usage to get the latest release json:
67
84
  git_wrapper = GitHubWrapper(user_name='user_name', repo_name='repo_name')
@@ -76,17 +93,32 @@ class GitHubWrapper:
76
93
  self.repo_name: str = repo_name
77
94
  self.repo_url: str = repo_url
78
95
  self.branch: str = branch
96
+ self.path: str = path
97
+ self.pat: str = pat
79
98
  self.branch_file_extension: str = branch_file_extension
80
99
 
81
100
  # Default variables.
82
- self.archive_directory: str = 'archive'
83
- self.branch_file_name: str = f'{self.branch}{self.branch_file_extension}'
101
+ if self.branch_file_extension == 'zip':
102
+ self.branch_type_directory: str = 'zipball'
103
+ elif self.branch_file_extension == 'tar.gz':
104
+ self.branch_type_directory: str = 'tarball'
105
+ else:
106
+ raise ValueError(f"Unsupported branch file extension: {self.branch_file_extension}")
107
+
84
108
  self.domain: str = 'github.com'
85
109
 
86
110
  # Initialize variables.
87
111
  self.branch_download_link: str = str()
88
112
  self.branch_downloaded_folder_name: str = str()
113
+ self.branch_file_name: str = str()
114
+ self.api_url: str = str()
89
115
  self.latest_release_json_url: str = str()
116
+ self.commits_url: str = str()
117
+ self.contents_url: str = str()
118
+
119
+ self.releases_url: str = str()
120
+ self.releases_per_page: int = 100
121
+ self.releases_starting_page: int = 1
90
122
 
91
123
  if self.user_name and self.repo_name and not self.repo_url:
92
124
  self.build_links_from_user_and_repo()
@@ -94,21 +126,35 @@ class GitHubWrapper:
94
126
  if self.repo_url and not self.user_name and not self.repo_name:
95
127
  self.build_links_from_repo_url()
96
128
 
129
+ def _get_headers(self) -> dict:
130
+ """
131
+ Returns headers for the GitHub API requests. If a personal access token (PAT) is provided, it adds the
132
+ 'Authorization' header.
133
+ """
134
+ headers = {}
135
+ if self.pat:
136
+ headers['Authorization'] = f'token {self.pat}'
137
+ return headers
138
+
97
139
  def build_links_from_user_and_repo(self, **kwargs):
98
140
  if not self.user_name or not self.repo_name:
99
- message = "'user_name' or 'repo_name' is empty."
100
- print_api(message, color="red", error_type=True, **kwargs)
141
+ raise ValueError("'user_name' or 'repo_name' is empty.")
101
142
 
102
143
  self.repo_url = f'https://{self.domain}/{self.user_name}/{self.repo_name}'
103
- self.branch_download_link = f'{self.repo_url}/{self.archive_directory}/{self.branch_file_name}'
104
144
  self.branch_downloaded_folder_name = f'{self.repo_name}-{self.branch}'
105
- self.latest_release_json_url: str = \
106
- f'https://api.{self.domain}/repos/{self.user_name}/{self.repo_name}/releases/latest'
145
+ self.branch_file_name: str = f'{self.repo_name}-{self.branch}.{self.branch_file_extension}'
146
+
147
+ self.api_url = f'https://api.{self.domain}/repos/{self.user_name}/{self.repo_name}'
148
+
149
+ self.latest_release_json_url: str = f'{self.api_url}/releases/latest'
150
+ self.releases_url: str = f'{self.api_url}/releases'
151
+ self.commits_url: str = f'{self.api_url}/commits'
152
+ self.contents_url: str = f'{self.api_url}/contents'
153
+ self.branch_download_link = f'{self.api_url}/{self.branch_type_directory}/{self.branch}'
107
154
 
108
155
  def build_links_from_repo_url(self, **kwargs):
109
156
  if not self.repo_url:
110
- message = "'repo_url' is empty."
111
- print_api(message, color="red", error_type=True, **kwargs)
157
+ raise ValueError("'repo_url' is empty.")
112
158
 
113
159
  repo_url_parsed = urls.url_parser(self.repo_url)
114
160
  self.check_github_domain(repo_url_parsed['netloc'])
@@ -117,88 +163,574 @@ class GitHubWrapper:
117
163
 
118
164
  self.build_links_from_user_and_repo()
119
165
 
120
- def check_github_domain(self, domain):
166
+ def check_github_domain(
167
+ self,
168
+ domain: str
169
+ ):
121
170
  if self.domain not in domain:
122
171
  print_api(
123
172
  f'This is not [{self.domain}] domain.', color="red", error_type=True)
124
173
 
174
+ def download_file(
175
+ self,
176
+ file_name: str,
177
+ target_dir: str
178
+ ) -> str:
179
+ """
180
+ Download a single repo file to a local directory.
181
+
182
+ :param file_name: string, Full repo-relative path to the file. Example:
183
+ "eng.traineddata"
184
+ "script\\English.script"
185
+ :param target_dir: string, Local directory to save into.
186
+
187
+ :return: The local path to the downloaded file.
188
+ """
189
+
190
+ # Normalize to GitHub path format
191
+ file_path = file_name.replace("\\", "/").strip("/")
192
+
193
+ headers = self._get_headers()
194
+ url = f"{self.contents_url}/{file_path}"
195
+ params = {"ref": self.branch}
196
+
197
+ resp = requests.get(url, headers=headers, params=params)
198
+ resp.raise_for_status()
199
+ item = resp.json()
200
+
201
+ # Expect a single file object
202
+ if isinstance(item, list) or item.get("type") != "file":
203
+ raise ValueError(f"'{file_name}' is not a file in branch '{self.branch}'.")
204
+
205
+ download_url = item.get("download_url")
206
+ if not download_url:
207
+ raise ValueError(f"Unable to obtain download URL for '{file_name}'.")
208
+
209
+ os.makedirs(target_dir, exist_ok=True)
210
+ local_name = item.get("name") or os.path.basename(file_path)
211
+
212
+ from .. import web # ensure available in your module structure
213
+ web.download(
214
+ file_url=download_url,
215
+ target_directory=target_dir,
216
+ file_name=local_name,
217
+ headers=headers,
218
+ )
219
+ return os.path.join(target_dir, local_name)
220
+
221
+ def download_directory(
222
+ self,
223
+ folder_name: str,
224
+ target_dir: str
225
+ ) -> None:
226
+ """
227
+ Recursively download a repo directory to a local directory.
228
+
229
+ :param folder_name: string, Repo-relative directory path to download (e.g., "tests/langs").
230
+ :param target_dir: string, Local directory to save the folder tree into.
231
+ """
232
+ headers = self._get_headers()
233
+ root_path = folder_name.replace("\\", "/").strip("/")
234
+
235
+ def _walk_dir(rel_path: str, local_dir: str) -> None:
236
+ contents_url = f"{self.contents_url}/{rel_path}" if rel_path else self.contents_url
237
+ params = {"ref": self.branch}
238
+
239
+ response = requests.get(contents_url, headers=headers, params=params)
240
+ response.raise_for_status()
241
+ items = response.json()
242
+
243
+ # If a file path was passed accidentally, delegate to download_file
244
+ if isinstance(items, dict) and items.get("type") == "file":
245
+ self.download_file(rel_path, local_dir)
246
+ return
247
+
248
+ if not isinstance(items, list):
249
+ raise ValueError(f"Unexpected response shape when listing '{rel_path or '/'}'.")
250
+
251
+ os.makedirs(local_dir, exist_ok=True)
252
+
253
+ for item in items:
254
+ name = item["name"]
255
+ if item["type"] == "file":
256
+ self.download_file(f"{rel_path}/{name}" if rel_path else name, local_dir)
257
+ elif item["type"] == "dir":
258
+ _walk_dir(f"{rel_path}/{name}" if rel_path else name, os.path.join(local_dir, name))
259
+ # ignore symlinks/submodules if present
260
+
261
+ _walk_dir(root_path, target_dir)
262
+
125
263
  def download_and_extract_branch(
126
264
  self,
127
265
  target_directory: str,
128
266
  archive_remove_first_directory: bool = False,
267
+ download_each_file: bool = False,
129
268
  **kwargs
130
269
  ):
131
270
  """
132
271
  This function will download the branch file from GitHub, extract the file and remove the file, leaving
133
272
  only the extracted folder.
273
+ If the 'path' was specified during the initialization of the class, only the path will be downloaded.
134
274
 
135
- :param target_directory:
136
- :param archive_remove_first_directory: boolean, sets if archive extract function will extract the archive
275
+ :param target_directory: str, the target directory to download the branch/path.
276
+ :param archive_remove_first_directory: boolean, available only if 'path' was not specified during the initialization
277
+ Sets if archive extract function will extract the archive
137
278
  without first directory in the archive. Check reference in the
138
- 'archiver.zip.extract_archive_with_zipfile' function.
279
+ 'dkarchiver.arch_wrappers.zips.extract_archive_with_zipfile' function.
280
+ :param download_each_file: bool, available only if 'path' was specified during the initialization of the class.
281
+ Sets if each file will be downloaded separately.
282
+
283
+ True: Meaning the directory '/home/user/Downloads/files/' will be created and each file will be downloaded
284
+ ('file1.txt', 'file2.txt', 'file3.txt') separately to this directory.
285
+ False: The branch file will be downloaded to temp directory then the provided path
286
+ will be extracted from there, then the downloaded branch directory will be removed.
139
287
  :return:
140
288
  """
141
289
 
142
- # Download the repo to current working directory, extract and remove the archive.
143
- web.download_and_extract_file(
144
- file_url=self.branch_download_link,
145
- target_directory=target_directory,
146
- archive_remove_first_directory=archive_remove_first_directory,
147
- **kwargs)
290
+ headers: dict = self._get_headers()
148
291
 
149
- def download_and_extract_latest_release(
292
+ if not download_each_file:
293
+ if self.path:
294
+ download_target_directory = tempfile.mkdtemp()
295
+ current_archive_remove_first_directory = True
296
+ else:
297
+ download_target_directory = target_directory
298
+ current_archive_remove_first_directory = archive_remove_first_directory
299
+
300
+ # Download the repo to current working directory, extract and remove the archive.
301
+ web.download_and_extract_file(
302
+ file_url=self.branch_download_link,
303
+ file_name=self.branch_file_name,
304
+ target_directory=download_target_directory,
305
+ archive_remove_first_directory=current_archive_remove_first_directory,
306
+ headers=headers,
307
+ **kwargs)
308
+
309
+ if self.path:
310
+ source_path: str = f"{download_target_directory}{os.sep}{self.path}"
311
+
312
+ if not archive_remove_first_directory:
313
+ target_directory = os.path.join(target_directory, self.path)
314
+ filesystem.create_directory(target_directory)
315
+
316
+ # Move the path to the target directory.
317
+ filesystem.move_folder_contents_to_folder(
318
+ source_path, target_directory)
319
+
320
+ # Remove the downloaded branch directory.
321
+ filesystem.remove_directory(download_target_directory)
322
+ else:
323
+ if archive_remove_first_directory:
324
+ current_target_directory = target_directory
325
+ else:
326
+ current_target_directory = os.path.join(target_directory, self.path)
327
+
328
+ self.download_directory(self.path, current_target_directory)
329
+
330
+ def get_releases_json(
150
331
  self,
151
- target_directory: str,
152
- string_pattern: str,
153
- archive_remove_first_directory: bool = False,
332
+ asset_pattern: str = None,
333
+ latest: bool = False,
334
+ per_page: int = None,
335
+ starting_page: int = None,
336
+ all_assets: bool = False,
337
+ ):
338
+ """
339
+ This function will get the releases json.
340
+ :param asset_pattern: str, the string pattern to search in the asset names of releases. Wildcards can be used.
341
+ If there is a match, the release will be added to the result list.
342
+ :param latest: bool, if True, will get only the latest release.
343
+ If 'asset_pattern' is provided, 'latest' will find the latest release matching the pattern.
344
+ Of course if you want to get it from all releases, you must set 'all_assets' to True.
345
+ :param per_page: int, the number of releases per page. Default is 100.
346
+ :param starting_page: int, the starting page number. Default is 1.
347
+ :param all_assets: bool, if True, will get all releases matching the pattern across all pages
348
+ OR all assets if no pattern is provided.
349
+ :return:
350
+ """
351
+
352
+ # If 'latest' is True and no 'asset_pattern' is provided, we only need to get 1 release from page 1.
353
+ # No need to get more assets than the first one.
354
+ if latest and not asset_pattern:
355
+ per_page = 1
356
+ starting_page = 1
357
+ all_assets = False
358
+ # In all other cases, get the releases according to the provided parameters or defaults.
359
+ else:
360
+ if not per_page:
361
+ per_page = self.releases_per_page
362
+
363
+ if not starting_page:
364
+ starting_page = self.releases_starting_page
365
+
366
+ headers: dict = self._get_headers()
367
+
368
+ params: dict = {
369
+ 'per_page': per_page,
370
+ 'page': starting_page
371
+ }
372
+
373
+ all_releases = []
374
+ while True:
375
+ response = requests.get(self.releases_url, headers=headers, params=params)
376
+ releases = response.json()
377
+ # If no releases found on current page, there will be none on the next as well, break the loop.
378
+ if not releases:
379
+ break
380
+
381
+ # If 'asset_pattern' is provided, filter releases to only those that have matching assets.
382
+ if asset_pattern:
383
+ for release in releases:
384
+ assets = release.get('assets', [])
385
+ matching_assets = [asset for asset in assets if fnmatch.fnmatch(asset.get('name', ''), asset_pattern)]
386
+ if matching_assets:
387
+ all_releases.append(release)
388
+
389
+ if latest:
390
+ return all_releases
391
+ else:
392
+ all_releases.extend(releases)
393
+
394
+ if not all_assets:
395
+ break
396
+
397
+ params['page'] += 1
398
+
399
+ return all_releases
400
+
401
+ def get_latest_release_json(
402
+ self,
403
+ asset_pattern: str = None
404
+ ) -> dict:
405
+ """
406
+ This function will get the latest releases json.
407
+ :param asset_pattern: str, the string pattern to search in the asset names of releases. Wildcards can be used.
408
+ If there is a match, the release will be added to the result list.
409
+ :return: dict, the latest release json.
410
+ """
411
+
412
+ if asset_pattern:
413
+ releases = self.get_releases_json(
414
+ asset_pattern=asset_pattern,
415
+ latest=True,
416
+ all_assets=True
417
+ )
418
+ else:
419
+ releases = self.get_releases_json(latest=True)
420
+
421
+ if not releases:
422
+ return {}
423
+ else:
424
+ return releases[0]
425
+
426
+ def get_latest_release_version(
427
+ self,
428
+ asset_pattern: str = None
429
+ ) -> str:
430
+ """
431
+ This function will get the latest release version number.
432
+
433
+ :param asset_pattern: str, the string pattern to search in the asset names of releases. Wildcards can be used.
434
+ If there is a match, the release will be added to the result list.
435
+ :return: str, the latest release version number.
436
+ """
437
+
438
+ latest_release_json: dict = self.get_latest_release_json(asset_pattern=asset_pattern)
439
+ latest_release_version: str = latest_release_json['tag_name']
440
+ return latest_release_version
441
+
442
+ def get_latest_release_url(
443
+ self,
444
+ asset_pattern: str,
445
+ exclude_string: str = None,
154
446
  **kwargs):
155
447
  """
156
- This function will download the latest release from the GitHub repository, extract the file and remove the file,
157
- leaving only the extracted folder.
158
- :param target_directory: str, the target directory to download and extract the file.
159
- :param string_pattern: str, the string pattern to search in the latest release. Wildcards can be used.
160
- :param archive_remove_first_directory: bool, sets if archive extract function will extract the archive
161
- without first directory in the archive. Check reference in the
162
- 'archiver.zip.extract_archive_with_zipfile' function.
448
+ This function will return the latest release url.
449
+ :param asset_pattern: str, the string pattern to search in the latest release. Wildcards can be used.
450
+ :param exclude_string: str, the string to exclude from the search. No wildcards can be used.
163
451
  :param kwargs: dict, the print arguments for the 'print_api' function.
164
- :return:
452
+ :return: str, the latest release url.
165
453
  """
166
454
 
167
455
  # Get the 'assets' key of the latest release json.
168
- github_latest_releases_list = self.get_the_latest_release_json()['assets']
456
+ github_latest_releases_list = self.get_latest_release_json()['assets']
169
457
 
170
458
  # Get only download urls of the latest releases.
171
459
  download_urls: list = list()
172
460
  for single_dict in github_latest_releases_list:
173
461
  download_urls.append(single_dict['browser_download_url'])
174
462
 
175
- # Find urls against 'string_pattern'.
176
- found_urls = fnmatch.filter(download_urls, string_pattern)
463
+ # Exclude urls against 'exclude_string'.
464
+ if exclude_string:
465
+ for download_url in download_urls:
466
+ if exclude_string in download_url:
467
+ download_urls.remove(download_url)
468
+
469
+ # Find urls against 'asset_pattern'.
470
+ found_urls: list = fnmatch.filter(download_urls, asset_pattern)
177
471
 
178
472
  # If more than 1 url answer the criteria, we can't download it. The user must be more specific in his input
179
473
  # strings.
180
474
  if len(found_urls) > 1:
181
475
  message = f'More than 1 result found in JSON response, try changing search string or extension.\n' \
182
476
  f'{found_urls}'
183
- print_api(message, color="red", error_type=True, **kwargs)
477
+ raise MoreThanOneReleaseFoundError(message)
478
+ elif len(found_urls) == 0:
479
+ message = f'No result found in JSON response, try changing search string or extension.'
480
+ raise NoReleaseFoundError(message)
481
+ else:
482
+ return found_urls[0]
483
+
484
+ def download_latest_release(
485
+ self,
486
+ target_directory: str,
487
+ asset_pattern: str,
488
+ exclude_string: str = None,
489
+ **kwargs
490
+ ) -> str:
491
+ """
492
+ This function will download the latest release from the GitHub repository.
493
+ :param target_directory: str, the target directory to download the file.
494
+ :param asset_pattern: str, the string pattern to search in the latest release. Wildcards can be used.
495
+ :param exclude_string: str, the string to exclude from the search. No wildcards can be used.
496
+ The 'excluded_string' will be filtered before the 'asset_pattern' entries.
497
+ :param kwargs: dict, the print arguments for the 'print_api' function.
498
+ :return: str, the downloaded file path.
499
+ """
500
+
501
+ headers: dict = self._get_headers()
502
+
503
+ # Get the latest release url.
504
+ found_url = self.get_latest_release_url(asset_pattern=asset_pattern, exclude_string=exclude_string, **kwargs)
505
+
506
+ downloaded_file_path = web.download(
507
+ file_url=found_url, target_directory=target_directory, headers=headers, **kwargs)
508
+ return downloaded_file_path
509
+
510
+ def download_and_extract_latest_release(
511
+ self,
512
+ target_directory: str,
513
+ asset_pattern: str,
514
+ exclude_string: str = None,
515
+ archive_remove_first_directory: bool = False,
516
+ **kwargs):
517
+ """
518
+ This function will download the latest release from the GitHub repository, extract the file and remove the file,
519
+ leaving only the extracted folder.
520
+ :param target_directory: str, the target directory to download and extract the file.
521
+ :param asset_pattern: str, the string pattern to search in the latest release. Wildcards can be used.
522
+ :param exclude_string: str, the string to exclude from the search. No wildcards can be used.
523
+ :param archive_remove_first_directory: bool, sets if archive extract function will extract the archive
524
+ without first directory in the archive. Check reference in the
525
+ 'dkarchiver.arch_wrappers.zips.extract_archive_with_zipfile' function.
526
+ :param kwargs: dict, the print arguments for the 'print_api' function.
527
+ :return:
528
+ """
529
+
530
+ headers: dict = self._get_headers()
531
+
532
+ # Get the latest release url.
533
+ found_url = self.get_latest_release_url(asset_pattern=asset_pattern, exclude_string=exclude_string, **kwargs)
184
534
 
185
535
  web.download_and_extract_file(
186
- file_url=found_urls[0],
536
+ file_url=found_url,
187
537
  target_directory=target_directory,
188
538
  archive_remove_first_directory=archive_remove_first_directory,
539
+ headers=headers,
189
540
  **kwargs)
190
541
 
191
- def get_the_latest_release_json(self):
542
+ def get_latest_commit(self) -> dict:
192
543
  """
193
- This function will get the latest releases json.
194
- :return:
544
+ This function retrieves the latest commit on the specified branch.
545
+ It uses the GitHub API endpoint for commits.
546
+
547
+ :return: dict, the latest commit data.
195
548
  """
196
- response = requests.get(self.latest_release_json_url)
197
- return response.json()
198
549
 
199
- def get_the_latest_release_version_number(self):
550
+ headers: dict = self._get_headers()
551
+
552
+ # Use query parameters to filter commits by branch (sha) and limit results to 1
553
+ params: dict = {
554
+ 'sha': self.branch,
555
+ 'per_page': 1
556
+ }
557
+
558
+ if self.path:
559
+ params['path'] = self.path
560
+
561
+ response = requests.get(self.commits_url, headers=headers, params=params)
562
+ response.raise_for_status() # Raises an HTTPError if the HTTP request returned an unsuccessful status code.
563
+
564
+ commits = response.json()
565
+ if not commits:
566
+ return {}
567
+
568
+ latest_commit = commits[0]
569
+ return latest_commit
570
+
571
+ def get_latest_commit_message(self):
200
572
  """
201
- This function will get the latest release version number.
202
- :return:
573
+ This function retrieves the commit message (comment) of the latest commit on the specified branch.
574
+ It uses the GitHub API endpoint for commits.
575
+
576
+ :return: str, the commit message of the latest commit.
577
+ """
578
+
579
+ latest_commit: dict = self.get_latest_commit()
580
+
581
+ commit_message = latest_commit.get("commit", {}).get("message", "")
582
+ return commit_message
583
+
584
+ def list_files(
585
+ self,
586
+ pattern: str = "*",
587
+ recursive: bool = True,
588
+ path: str | None = None,
589
+ ) -> list[str]:
203
590
  """
204
- return self.get_the_latest_release_json()['tag_name']
591
+ List files in the repository (or in a specific subfolder).
592
+
593
+ :param pattern: Glob-style pattern (e.g., "*.ex*", "*test*.py"). Matching is done
594
+ against the file's base name (not the full path).
595
+ :param recursive: If True, include files in all subfolders (returns full repo-relative
596
+ paths). If False, list only the immediate files in the chosen folder.
597
+ :param path: Optional subfolder to list from (e.g., "tests/langs"). If omitted,
598
+ uses self.path if set, otherwise the repo root.
599
+
600
+ :return: A list of repo-relative file paths that match the pattern.
601
+ """
602
+ headers = self._get_headers()
603
+ base_path = (path or self.path or "").strip("/")
604
+
605
+ if recursive:
606
+ # Use the Git Trees API to fetch all files in one call, then filter.
607
+ tree_url = f"{self.api_url}/git/trees/{self.branch}"
608
+ params = {"recursive": "1"}
609
+ resp = requests.get(tree_url, headers=headers, params=params)
610
+ resp.raise_for_status()
611
+ data = resp.json()
612
+
613
+ files = []
614
+ for entry in data.get("tree", []):
615
+ if entry.get("type") != "blob":
616
+ continue # only files
617
+ entry_path = entry.get("path", "")
618
+ # If a base_path was provided, keep only files under it
619
+ if base_path and not entry_path.startswith(base_path + "/") and entry_path != base_path:
620
+ continue
621
+ # Match pattern against the *file name* (basename)
622
+ if fnmatch.fnmatch(os.path.basename(entry_path), pattern):
623
+ files.append(entry_path)
624
+ return files
625
+
626
+ else:
627
+ # Non-recursive: use the Contents API to list a single directory.
628
+ # If base_path is empty, list the repo root.
629
+ if base_path:
630
+ contents_url = f"{self.contents_url}/{base_path}"
631
+ else:
632
+ contents_url = self.contents_url
633
+
634
+ params = {"ref": self.branch}
635
+ resp = requests.get(contents_url, headers=headers, params=params)
636
+ resp.raise_for_status()
637
+ items = resp.json()
638
+
639
+ # The Contents API returns a dict when the path points to a single file;
640
+ # normalize to a list to simplify handling.
641
+ if isinstance(items, dict):
642
+ items = [items]
643
+
644
+ files = []
645
+ for item in items:
646
+ if item.get("type") == "file":
647
+ name = item.get("name", "")
648
+ if fnmatch.fnmatch(name, pattern):
649
+ # item["path"] is the full repo-relative path we want to return
650
+ files.append(item.get("path", name))
651
+ return files
652
+
653
+
654
+ def _make_parser():
655
+ import argparse
656
+
657
+ parser = argparse.ArgumentParser(description='GitHub Wrapper')
658
+ parser.add_argument(
659
+ '-u', '--repo_url', type=str, required=True,
660
+ help='The repository url. Example: https://github.com/{user_name}/{repo_name}')
661
+ parser.add_argument(
662
+ '-b', '--branch', type=str, required=True,
663
+ help='The branch name. The specific branch from the repo you want to operate on.')
664
+ parser.add_argument(
665
+ '-p', '--path', type=str, default=None,
666
+ help="The path to the file/folder inside the repo that we'll do certain actions on.\n"
667
+ "Available actions: get_latest_commit_message, download_path_from_branch.")
668
+ parser.add_argument(
669
+ '-t', '--target_directory', type=str, default=None,
670
+ help='The target directory to download the file/folder.'
671
+ )
672
+ parser.add_argument(
673
+ '--pat', type=str, default=None,
674
+ help='The personal access token to the repo.')
675
+ parser.add_argument(
676
+ '-glcm', '--get_latest_commit_message', action='store_true', default=False,
677
+ help='Sets if the latest commit message comment will be printed.')
678
+ parser.add_argument(
679
+ '-glcj', '--get_latest_commit_json', action='store_true', default=False,
680
+ help='Sets if the latest commit json will be printed.')
681
+ parser.add_argument(
682
+ '-db', '--download_branch', action='store_true', default=False,
683
+ help='Sets if the branch will be downloaded. In conjunction with path, only the path will be downloaded.')
684
+
685
+ return parser
686
+
687
+
688
+ def github_wrapper_main(
689
+ repo_url: str,
690
+ branch: str,
691
+ path: str = None,
692
+ target_directory: str = None,
693
+ pat: str = None,
694
+ get_latest_commit_message: bool = False,
695
+ get_latest_commit_json: bool = False,
696
+ download_branch: bool = False
697
+ ):
698
+ """
699
+ This function is the main function for the GitHubWrapper class.
700
+ :param repo_url: str, the repository url.
701
+ Example: https://github.com/{user_name}/{repo_name}
702
+ :param branch: str, the branch name. The specific branch from the repo you want to operate on.
703
+ :param path: str, the path to the file/folder for which the commit message should be retrieved.
704
+ :param target_directory: str, the target directory to download the file/folder.
705
+ :param pat: str, the personal access token to the repo.
706
+ :param get_latest_commit_json: bool, sets if the latest commit json will be printed.
707
+ :param get_latest_commit_message: bool, sets if the latest commit message comment will be printed.
708
+ :param download_branch: bool, sets if the branch will be downloaded. In conjunction with path, only the path will be
709
+ downloaded.
710
+ :return:
711
+ """
712
+
713
+ git_wrapper = GitHubWrapper(repo_url=repo_url, branch=branch, path=path, pat=pat)
714
+
715
+ if get_latest_commit_message:
716
+ commit_comment = git_wrapper.get_latest_commit_message()
717
+ print_api(commit_comment)
718
+ return 0
719
+
720
+ if get_latest_commit_json:
721
+ latest_commit_json = git_wrapper.get_latest_commit()
722
+ print_api(latest_commit_json)
723
+ return 0
724
+
725
+ if download_branch:
726
+ git_wrapper.download_and_extract_branch(
727
+ target_directory=target_directory, download_each_file=False, archive_remove_first_directory=True)
728
+
729
+ return 0
730
+
731
+
732
+ def github_wrapper_main_with_args():
733
+ main_parser = _make_parser()
734
+ args = main_parser.parse_args()
735
+
736
+ return github_wrapper_main(**vars(args))