/ get_imports.py
get_imports.py
  1  # SPDX-FileCopyrightText: 2021 foamyguy
  2  # SPDX-FileCopyrightText: 2019 Nicholas Tollervey, written for Adafruit Industries
  3  #
  4  # SPDX-License-Identifier: MIT
  5  
  6  """
  7  Get the list of required libraries based on a file's imports
  8  """
  9  
 10  import json
 11  import os
 12  import findimports
 13  import requests
 14  
 15  BUNDLE_DATA = "latest_bundle_data.json"
 16  BUNDLE_TAG = "latest_bundle_tag.json"
 17  
 18  LEARN_GUIDE_REPO = os.environ.get(
 19      "LEARN_GUIDE_REPO", "../Adafruit_Learning_System_Guides/"
 20  )
 21  
 22  SHOWN_FILETYPES = ["py", "mpy", "bmp", "pcf", "bdf", "wav", "mp3", "json", "txt"]
 23  SHOWN_FILETYPES_EXAMPLE = [s for s in SHOWN_FILETYPES if s != "py"]
 24  
 25  
 26  def get_bundle(tag):
 27      """Download the given bundle's data to BUNDLE_DATA"""
 28      url = f"https://adafruit-circuit-python.s3.amazonaws.com/bundles/adafruit/adafruit-circuitpython-bundle-{tag}.json"  # pylint: disable=line-too-long
 29      print(f"get bundle metadata from {url}")
 30      r = requests.get(url)
 31      with open(BUNDLE_DATA, "wb") as bundle_file:
 32          bundle_file.write(r.content)
 33  
 34  
 35  LATEST_BUNDLE_VERSION = ""
 36  
 37  
 38  def get_latest_release_from_url(url):
 39      """
 40      Find the tag name of the latest release by using HTTP HEAD and decoding the redirect.
 41  
 42      :return: The most recent tag value for the release.
 43      """
 44  
 45      print(f"Requesting redirect information: {url}")
 46      response = requests.head(url)
 47      responseurl = response.url
 48      if response.is_redirect:
 49          responseurl = response.headers["Location"]
 50      tag = responseurl.rsplit("/", 1)[-1]
 51      print(f"Tag: {tag!r}")
 52      return tag
 53  
 54  
 55  def get_latest_tag():
 56      """
 57      Find the value of the latest tag for the Adafruit CircuitPython library
 58      bundle.
 59      :return: The most recent tag value for the project.
 60      """
 61      global LATEST_BUNDLE_VERSION  # pylint: disable=global-statement
 62      if LATEST_BUNDLE_VERSION == "":
 63          LATEST_BUNDLE_VERSION = get_latest_release_from_url(
 64              "https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/latest"
 65          )
 66      return LATEST_BUNDLE_VERSION
 67  
 68  
 69  def ensure_latest_bundle():
 70      """
 71      Ensure that there's a copy of the latest library bundle available so circup
 72      can check the metadata contained therein.
 73      """
 74      print("Checking for library updates.")
 75      tag = get_latest_tag()
 76      old_tag = "0"
 77      if os.path.isfile(BUNDLE_TAG):
 78          with open(BUNDLE_TAG, encoding="utf-8") as data:
 79              try:
 80                  old_tag = json.load(data)["tag"]
 81              except json.decoder.JSONDecodeError as _:
 82                  # Sometimes (why?) the JSON file becomes corrupt. In which case
 83                  # log it and carry on as if setting up for first time.
 84                  print(f"Could not parse {BUNDLE_TAG:r}")
 85      if tag > old_tag:
 86          print(f"New version available {tag}.")
 87          try:
 88              get_bundle(tag)
 89              with open(BUNDLE_TAG, "w", encoding="utf-8") as data:
 90                  json.dump({"tag": tag}, data)
 91          except requests.exceptions.HTTPError as _:
 92              # See #20 for reason this this
 93              print(
 94                  (
 95                      "There was a problem downloading the bundle. "
 96                      "Please try again in a moment."
 97                  ),
 98              )
 99              raise
100      else:
101          print(f"Current library bundle up to date {tag}")
102  
103  
104  ensure_latest_bundle()
105  
106  with open("latest_bundle_data.json", "r") as f:
107      bundle_data = json.load(f)
108  
109  
110  def get_files_for_project(project_name):
111      """Get the set of files for a learn project"""
112      found_files = set()
113      project_dir = "{}/{}/".format(LEARN_GUIDE_REPO, project_name)
114      for file in os.listdir(project_dir):
115          if "." in file:
116              cur_extension = file.split(".")[-1]
117              if cur_extension in SHOWN_FILETYPES:
118                  # print(file)
119                  found_files.add(file)
120          else:
121              # add dir
122              found_files.add(file)
123      return found_files
124  
125  
126  def get_libs_for_project(project_name):
127      """Get the set of libraries for a learn project"""
128      found_libs = set()
129      found_imports = []
130      project_dir = "{}{}/".format(LEARN_GUIDE_REPO, project_name)
131      for file in os.listdir(project_dir):
132          if file.endswith(".py"):
133  
134              found_imports = findimports.find_imports("{}{}".format(project_dir, file))
135  
136              for cur_import in found_imports:
137                  cur_lib = cur_import.name.split(".")[0]
138                  if cur_lib in bundle_data:
139                      found_libs.add(cur_lib)
140  
141      return found_libs
142  
143  
144  def get_files_for_example(example_path):
145      """Get the set of files for a library example"""
146      found_files = set(("code.py",))
147      example_dir = os.path.dirname(example_path)
148      for file in os.listdir(example_dir):
149          if "." in file:
150              cur_extension = file.split(".")[-1]
151              if cur_extension in SHOWN_FILETYPES_EXAMPLE:
152                  # print(file)
153                  found_files.add(file)
154          else:
155              # add dir
156              found_files.add(file)
157      return found_files
158  
159  
160  def get_libs_for_example(example_path):
161      """Get the set of libraries for a library example"""
162      found_libs = set()
163      found_imports = []
164      found_imports = findimports.find_imports(example_path)
165  
166      for cur_import in found_imports:
167          cur_lib = cur_import.name.split(".")[0]
168          if cur_lib in bundle_data:
169              found_libs.add(cur_lib)
170  
171      return found_libs
172  
173  
174  def get_learn_guide_cp_projects():
175      """Get the list of all circuitpython projects, according to some heuristics"""
176      for dirpath, dirnames, filenames in os.walk(LEARN_GUIDE_REPO):
177          # Don't consider hidden directories
178          dirnames[:] = [d for d in dirnames if not d.startswith(".")]
179  
180          # The top-level needs special treatment
181          if dirpath == LEARN_GUIDE_REPO:
182              continue
183  
184          # Skip this folder and all subfolders
185          if ".circuitpython.skip" in filenames:
186              del dirnames[:]
187              continue
188          # Skip files in this folder, but handle sub-folders
189          if ".circuitpython.skip-here" in filenames:
190              continue
191          # Do not reurse, but handle files in this folder
192          if ".circuitpython.skip-sub" in filenames:
193              del dirnames[:]
194  
195          if any(f for f in filenames if f.endswith(".py")):
196              yield os.path.relpath(dirpath, LEARN_GUIDE_REPO)
197  
198  
199  if __name__ == "__main__":
200      for p in get_learn_guide_cp_projects():
201          print("PROJECT", p)