text: """ Module for converting Google Earth Engine (GEE) JavaScripts to Python scripts and Jupyter notebooks.
  
  To convert a GEE JavaScript to Python script:                                       js_to_python(in_file out_file)
  
  To convert all GEE JavaScripts in a folder recursively to Python scripts:           js_to_python_dir(in_dir, out_dir)
  
  To convert a GEE Python script to Jupyter notebook:                                 py_to_ipynb(in_file, template_file, out_file)
  
  To convert all GEE Python scripts in a folder recursively to Jupyter notebooks:     py_to_ipynb_dir(in_dir, template_file, out_dir)
  
  To execute a Jupyter notebook and save output cells:                                execute_notebook(in_file)
  
  To execute all Jupyter notebooks in a folder recursively:                           execute_notebook_dir(in_dir)           
  
  """
  
  
  import os
  import pkg_resources
  import shutil
  import urllib.request
  from collections import deque
  from pathlib import Path
  from .common import *
  
  
  def find_matching_bracket(lines, start_line_index, start_char_index, matching_char="{"):
      """Finds the position of the matching closing bracket from a list of lines.
  
      Args:
          lines (list): The input list of lines.
          start_line_index (int): The line index where the starting bracket is located.
          start_char_index (int): The position index of the starting bracket.
          matching_char (str, optional): The starting bracket to search for. Defaults to '{'.
  
      Returns:
          matching_line_index (int): The line index where the matching closing bracket is located.
          matching_char_index (int): The position index of the matching closing bracket.
      """
      matching_line_index = -1
      matching_char_index = -1
  
      matching_chars = {"{": "}", "(": ")", "[": "]"}
      if matching_char not in matching_chars.keys():
          print(
              "The matching character must be one of the following: {}".format(
                  ", ".join(matching_chars.keys())
              )
          )
          return matching_line_index, matching_char_index
  
      # Create a deque to use it as a stack.
      d = deque()
  
      for line_index in range(start_line_index, len(lines)):
          line = lines[line_index]
          # deal with the line where the starting bracket is located.
          if line_index == start_line_index:
              line = lines[line_index][start_char_index:]
  
          for index, item in enumerate(line):
              # Pops a starting bracket for each closing bracket
              if item == matching_chars[matching_char]:
                  d.popleft()
              # Push all starting brackets
              elif item == matching_char:
                  d.append(matching_char)
  
              # If deque becomes empty
              if not d:
                  matching_line_index = line_index
                  if line_index == start_line_index:
                      matching_char_index = start_char_index + index
                  else:
                      matching_char_index = index
  
                  return matching_line_index, matching_char_index
  
      return matching_line_index, matching_char_index
  
  
  def format_params(line, sep=":"):
      """Formats keys in a dictionary and adds quotes to the keys.
      For example, {min: 0, max: 10} will result in ('min': 0, 'max': 10)
  
      Args:
          line (str): A string.
          sep (str, optional): Separator. Defaults to ':'.
  
      Returns:
          [str]: A string with keys quoted
      """
      # print(line)
      new_line = line
      prefix = ""
      suffix = ""
  
      if line.strip().startswith("for"):  # skip for loop
          return line
  
      # find all occurrences of a substring
      def find_all(a_str, sub):
          start = 0
          while True:
              start = a_str.find(sub, start)
              if start == -1:
                  return
              yield start
              start += len(sub)  # use start += 1 to find overlapping matches
  
      indices = list(find_all(line, sep))
      count = len(indices)
  
      if "{" in line:
          bracket_index = line.index("{")
          if bracket_index < indices[0]:
              prefix = line[: bracket_index + 1]
              line = line[bracket_index + 1 :]
  
      if count > 0:
          items = line.split(sep)
  
          if count == 1:
              for i in range(0, count):
                  item = items[i].strip()
                  if ('"' not in item) and ("'" not in item):
                      new_item = "'" + item + "'"
                      items[i] = items[i].replace(item, new_item)
              new_line = ":".join(items)
          elif count > 1:
              for i in range(0, count):
                  item = items[i]
                  if "," in item:
                      subitems = item.split(",")
                      subitem = subitems[-1]
                      if ('"' not in subitem) and ("'" not in subitem):
                          new_subitem = "'" + subitem.strip() + "'"
                          subitems[-1] = subitems[-1].replace(subitem, new_subitem)
                          items[i] = ", ".join(subitems)
                  else:
                      if ('"' not in item) and ("'" not in item):
                          new_item = "'" + item.strip() + "'"
                          padding = len(item) - len(item.strip())
                          items[i] = " " * padding + item.replace(item, new_item)
  
              new_line = ":".join(items)
  
      return prefix + new_line
  
  
  def use_math(lines):
      """Checks if an Earth Engine uses Math library
  
      Args:
          lines (list): An Earth Engine JavaScript.
  
      Returns:
          [bool]: Returns True if the script contains 'Math.'. For example 'Math.PI', 'Math.pow'
      """
      math_import = False
      for line in lines:
          if "Math." in line:
              math_import = True
  
      return math_import
  
  
  def convert_for_loop(line):
      """Converts JavaScript for loop to Python for loop.
  
      Args:
          line (str): Input JavaScript for loop
  
      Returns:
          str: Converted Python for loop.
      """
      new_line = ""
      if "var " in line:
          line = line.replace("var ", "")
      start_index = line.index("(")
      end_index = line.index(")")
  
      prefix = line[:(start_index)]
      suffix = line[(end_index + 1) :]
  
      params = line[(start_index + 1) : end_index]
  
      if " in " in params and params.count(";") == 0:
          new_line = prefix + "{}:".format(params) + suffix
          return new_line
  
      items = params.split("=")
      param_name = items[0].strip()
      items = params.split(";")
  
      subitems = []
  
      for item in items:
          subitems.append(item.split(" ")[-1])
  
      start = subitems[0]
      end = subitems[1]
      step = subitems[2]
  
      if "++" in step:
          step = 1
      elif "--" in step:
          step = -1
  
      prefix = line[:(start_index)]
      suffix = line[(end_index + 1) :]
      new_line = (
          prefix
          + "{} in range({}, {}, {}):".format(param_name, start, end, step)
          + suffix
      )
  
      return new_line
  
  
  def check_map_functions(input_lines):
      """Extracts Earth Engine map function
  
      Args:
          input_lines (list): List of Earth Engine JavaScrips
  
      Returns:
          list: Output JavaScript with map function
      """
      output_lines = []
      for index, line in enumerate(input_lines):
  
          if (".map(function" in line) or (".map (function") in line:
  
              bracket_index = line.index("{")
              matching_line_index, matching_char_index = find_matching_bracket(
                  input_lines, index, bracket_index
              )
  
              func_start_index = line.index("function")
              func_name = "func_" + random_string()
              func_header = line[func_start_index:].replace(
                  "function", "function " + func_name
              )
              output_lines.append("\n")
              output_lines.append(func_header)
  
              for sub_index, tmp_line in enumerate(
                  input_lines[index + 1 : matching_line_index]
              ):
                  output_lines.append(tmp_line)
                  input_lines[index + 1 + sub_index] = ""
  
              header_line = line[:func_start_index] + func_name
              header_line = header_line.rstrip()
  
              func_footer = input_lines[matching_line_index][: matching_char_index + 1]
              output_lines.append(func_footer)
  
              footer_line = input_lines[matching_line_index][
                  matching_char_index + 1 :
              ].strip()
              if footer_line == ")" or footer_line == ");":
                  header_line = header_line + footer_line
                  footer_line = ""
  
              input_lines[matching_line_index] = footer_line
  
              output_lines.append(header_line)
              output_lines.append(footer_line)
          else:
              output_lines.append(line)
  
      return output_lines
  
  
  def js_to_python(in_file, out_file=None, use_qgis=True, github_repo=None):
      """Converts an Earth Engine JavaScript to Python script.
  
      Args:
          in_file (str): File path of the input JavaScript.
          out_file (str, optional): File path of the output Python script. Defaults to None.
          use_qgis (bool, optional): Whether to add "from ee_plugin import Map \n" to the output script. Defaults to True.
          github_repo (str, optional): GitHub repo url. Defaults to None.
  
      Returns:
          list : Python script
      """
      in_file = os.path.abspath(in_file)
      if out_file is None:
          out_file = in_file.replace(".js", ".py")
  
      root_dir = os.path.dirname(os.path.abspath(__file__))
      if not os.path.isfile(in_file):
          in_file = os.path.join(root_dir, in_file)
      if not os.path.isfile(out_file):
          out_file = os.path.join(root_dir, out_file)
  
      is_python = False
      add_github_url = False
      qgis_import_str = ""
      if use_qgis:
          qgis_import_str = "from ee_plugin import Map \n"
  
      github_url = ""
      if github_repo is not None:
          github_url = "# GitHub URL: " + github_repo + in_file + "\n\n"
  
      math_import = False
      math_import_str = ""
  
      lines = []
      with open(in_file) as f:
          lines = f.readlines()
  
          math_import = use_math(lines)
  
          for line in lines:
              line = line.strip()
              if line == "import ee":
                  is_python = True
  
      if math_import:
          math_import_str = "import math\n"
  
      output = ""
  
      if is_python:  # only update the GitHub URL if it is already a GEE Python script
          output = github_url + "".join(map(str, lines))
      else:  # deal with JavaScript
  
          header = github_url + "import ee \n" + qgis_import_str + math_import_str
          function_defs = []
          output = header + "\n"
  
          with open(in_file) as f:
              lines = f.readlines()
  
              # print('Processing {}'.format(in_file))
              lines = check_map_functions(lines)
  
              for index, line in enumerate(lines):
  
                  if ("/* color" in line) and ("*/" in line):
                      line = (
                          line[: line.index("/*")].lstrip()
                          + line[(line.index("*/") + 2) :]
                      )
  
                  if (
                      ("= function" in line)
                      or ("=function" in line)
                      or line.strip().startswith("function")
                  ):
                      bracket_index = line.index("{")
                      matching_line_index, matching_char_index = find_matching_bracket(
                          lines, index, bracket_index
                      )
  
                      line = line[:bracket_index] + line[bracket_index + 1 :]
                      if matching_line_index == index:
                          line = (
                              line[:matching_char_index] + line[matching_char_index + 1 :]
                          )
                      else:
                          tmp_line = lines[matching_line_index]
                          lines[matching_line_index] = (
                              tmp_line[:matching_char_index]
                              + tmp_line[matching_char_index + 1 :]
                          )
  
                      line = (
                          line.replace(" = function", "")
                          .replace("=function", "")
                          .replace("function ", "")
                      )
                      if line.lstrip().startswith("//"):
                          line = line.replace("//", "").lstrip()
                          line = (
                              " " * (len(line) - len(line.lstrip()))
                              + "# def "
                              + line.strip()
                              + ":"
                          )
                      else:
                          line = (
                              " " * (len(line) - len(line.lstrip()))
                              + "def "
                              + line.strip()
                              + ":"
                          )
                  elif "{" in line:
                      bracket_index = line.index("{")
                      matching_line_index, matching_char_index = find_matching_bracket(
                          lines, index, bracket_index
                      )
                      if (matching_line_index == index) and (":" in line):
                          pass
                      elif ("for (" in line) or ("for(" in line):
                          line = convert_for_loop(line)
                          lines[index] = line
                          bracket_index = line.index("{")
                          (
                              matching_line_index,
                              matching_char_index,
                          ) = find_matching_bracket(lines, index, bracket_index)
                          tmp_line = lines[matching_line_index]
                          lines[matching_line_index] = (
                              tmp_line[:matching_char_index]
                              + tmp_line[matching_char_index + 1 :]
                          )
                          line = line.replace("{", "")
  
                  if line is None:
                      line = ""
  
                  line = line.replace("//", "#")
                  line = line.replace("var ", "", 1)
                  line = line.replace("/*", "#")
                  line = line.replace("*/", "#")
                  line = line.replace("true", "True").replace("false", "False")
                  line = line.replace("null", "{}")
                  line = line.replace(".or", ".Or")
                  line = line.replace(".and", ".And")
                  line = line.replace(".not", ".Not")
                  line = line.replace("visualize({", "visualize(**{")
                  line = line.replace("Math.PI", "math.pi")
                  line = line.replace("Math.", "math.")
                  line = line.replace("= new", "=")
                  line = line.rstrip()
  
                  if line.endswith("+"):
                      line = line + " \\"
                  elif line.endswith(";"):
                      line = line[:-1]
  
                  if line.lstrip().startswith("*"):
                      line = line.replace("*", "#")
  
                  if (
                      (":" in line)
                      and (not line.strip().startswith("#"))
                      and (not line.strip().startswith("def"))
                      and (not line.strip().startswith("."))
                  ):
                      line = format_params(line)
  
                  if (
                      index < (len(lines) - 1)
                      and line.lstrip().startswith("#")
                      and lines[index + 1].lstrip().startswith(".")
                  ):
                      line = ""
  
                  if line.lstrip().startswith("."):
                      if "#" in line:
                          line = line[: line.index("#")]
                      output = output.rstrip() + " " + "\\" + "\n" + line + "\n"
                  else:
                      output += line + "\n"
  
      out_dir = os.path.dirname(out_file)
      if not os.path.exists(out_dir):
          os.makedirs(out_dir)
  
      with open(out_file, "w") as f:
          f.write(output)
  
      return output
  
  
  def create_new_cell(contents, replace=False):
      """Create a new cell in Jupyter notebook based on the contents.
  
      Args:
          contents (str): A string of Python code.
      """
      from IPython.core.getipython import get_ipython
  
      shell = get_ipython()
      shell.set_next_input(contents, replace=replace)
  
  
  def js_snippet_to_py(
      in_js_snippet, add_new_cell=True, import_ee=True, import_geemap=True, show_map=True
  ):
      """Converts an Earth Engine JavaScript snippet wrapped in triple quotes to Python directly on a Jupyter notebook.
  
      Args:
          in_js_snippet (str): Earth Engine JavaScript within triple quotes.
          add_new_cell (bool, optional): Whether add the converted Python to a new cell.
          import_ee (bool, optional): Whether to import ee. Defaults to True.
          import_geemap (bool, optional): Whether to import geemap. Defaults to True.
          show_map (bool, optional): Whether to show the map. Defaults to True.
  
      Returns:
          list: A list of Python script.
      """
      work_dir = os.path.expanduser("~")
      in_js = os.path.join(work_dir, "tmp_js_snippet.js")
      out_py = os.path.join(work_dir, "tmp_py_snippet.py")
  
      try:
          with open(in_js, "w") as f:
              f.write(in_js_snippet)
          js_to_python(in_js, out_file=out_py, use_qgis=False)
  
          out_lines = []
          if import_ee:
              out_lines.append("import ee\n")
          if import_geemap:
              out_lines.append("import geemap\n\n")
              out_lines.append("Map = geemap.Map()\n")
          # if import_ee:
          #     out_lines.append("ee.Initialize()\n")
          with open(out_py) as f:
              lines = f.readlines()
              for index, line in enumerate(lines):
                  if index < (len(lines) - 1):
                      if line.strip() == "import ee":
                          continue
                      # elif import_ee and (line.strip() == 'import ee'):
                      #     out_lines.append(line)
                      #     out_lines.append('ee.Initialize()\n')
                      #     continue
                      next_line = lines[index + 1]
                      if line.strip() == "" and next_line.strip() == "":
                          continue
                      else:
                          out_lines.append(line)
                  elif index == (len(lines) - 1) and lines[index].strip() != "":
                      out_lines.append(line)
  
          if show_map:
              out_lines.append("Map\n")
  
          os.remove(in_js)
          os.remove(out_py)
  
          if add_new_cell:
              contents = "".join(out_lines)
              create_new_cell(contents)
          else:
              return out_lines
  
      except Exception as e:
          print(e)
  
  
  def js_to_python_dir(in_dir, out_dir=None, use_qgis=True, github_repo=None):
      """Converts all Earth Engine JavaScripts in a folder recursively to Python scripts.
  
      Args:
          in_dir (str): The input folder containing Earth Engine JavaScripts.
          out_dir (str, optional): The output folder containing Earth Engine Python scripts. Defaults to None.
          use_qgis (bool, optional): Whether to add "from ee_plugin import Map \n" to the output script. Defaults to True.
          github_repo (str, optional): GitHub repo url. Defaults to None.
      """
      print("Converting Earth Engine JavaScripts to Python scripts...\n")
      in_dir = os.path.abspath(in_dir)
      if out_dir is None:
          out_dir = in_dir
      elif not os.path.exists(out_dir):
          out_dir = os.path.abspath(out_dir)
          os.makedirs(out_dir)
      else:
          out_dir = os.path.abspath(out_dir)
  
      files = list(Path(in_dir).rglob("*.js"))
  
      for index, in_file in enumerate(files):
          print("Processing {}/{}: {}".format(index + 1, len(files), in_file))
          out_file = os.path.splitext(in_file)[0] + "_qgis.py"
          out_file = out_file.replace(in_dir, out_dir)
          js_to_python(in_file, out_file, use_qgis, github_repo)
      # print("Output Python script folder: {}".format(out_dir))
  
  
  # def dict_key_str(line):
  
  #     keys = """asFloat bands bestEffort bias collection color connectedness crs eeObject eightConnected format gain gamma
  #               geometry groupField groupName image iterations kernel labelBand leftField magnitude max maxDistance
  #               maxOffset maxPixels maxSize minBucketWidth min name normalize opacity palette patchWidth
  #               radius reducer referenceImage region rightField scale selectors shown sigma size source
  #               strokeWidth threshold units visParams width""".split()
  #     for key in keys:
  #         if ":" in line and key in line:
  #             line = line.replace(key + ":", "'" + key + "':")
  #     return line
  
  
  def remove_qgis_import(in_file):
      """Removes 'from ee_plugin import Map' from an Earth Engine Python script.
  
      Args:
          in_file (str): Input file path of the Python script.
  
      Returns:
          list: List of lines  'from ee_plugin import Map' removed.
      """
      in_file = os.path.abspath(in_file)
      start_index = 0
      with open(in_file) as f:
          lines = f.readlines()
          for index, line in enumerate(lines):
              if "from ee_plugin import Map" in line:
                  start_index = index
  
                  i = 1
                  while True:
                      line_tmp = lines[start_index + i].strip()
                      if line_tmp != "":
                          return lines[start_index + i :]
                      else:
                          i = i + 1
  
  
  def get_js_examples(out_dir=None):
      """Gets Earth Engine JavaScript examples from the geemap package.
  
      Args:
          out_dir (str, optional): The folder to copy the JavaScript examples to. Defaults to None.
  
      Returns:
          str: The folder containing the JavaScript examples.
      """
      pkg_dir = os.path.dirname(pkg_resources.resource_filename("geemap", "geemap.py"))
      example_dir = os.path.join(pkg_dir, "data")
      js_dir = os.path.join(example_dir, "javascripts")
  
      files = list(Path(js_dir).rglob("*.js"))
      if out_dir is None:
          out_dir = js_dir
      else:
          if not os.path.exists(out_dir):
              os.makedirs(out_dir)
  
          for file in files:
              basename = os.path.basename(file)
              out_path = os.path.join(out_dir, basename)
              shutil.copyfile(file, out_path)
  
      return out_dir
  
  
  def get_nb_template(download_latest=False, out_file=None):
      """Get the Earth Engine Jupyter notebook template.
  
      Args:
          download_latest (bool, optional): If True, downloads the latest notebook template from GitHub. Defaults to False.
          out_file (str, optional): Set the output file path of the notebook template. Defaults to None.
  
      Returns:
          str: The file path of the template.
      """
      pkg_dir = os.path.dirname(pkg_resources.resource_filename("geemap", "geemap.py"))
      example_dir = os.path.join(pkg_dir, "data")
      template_dir = os.path.join(example_dir, "template")
      template_file = os.path.join(template_dir, "template.py")
  
      if out_file is None:
          out_file = template_file
          return out_file
  
      if not out_file.endswith(".py"):
          out_file = out_file + ".py"
  
      if not os.path.exists(os.path.dirname(out_file)):
          os.makedirs(os.path.dirname(out_file))
  
      if download_latest:
          template_url = "https://raw.githubusercontent.com/giswqs/geemap/master/examples/template/template.py"
          print("Downloading the latest notebook template from {}".format(template_url))
          urllib.request.urlretrieve(template_url, out_file)
      elif out_file is not None:
          shutil.copyfile(template_file, out_file)
  
      return out_file
  
  
  def template_header(in_template):
      """Extracts header from the notebook template.
  
      Args:
          in_template (str): Input notebook template file path.
  
      Returns:
          list: List of lines.
      """
      header = []
      template_lines = []
      header_end_index = 0
  
      with open(in_template) as f:
          template_lines = f.readlines()
          for index, line in enumerate(template_lines):
              if "## Add Earth Engine Python script" in line:
                  header_end_index = index + 5
  
      header = template_lines[:header_end_index]
  
      return header
  
  
  def template_footer(in_template):
      """Extracts footer from the notebook template.
  
      Args:
          in_template (str): Input notebook template file path.
  
      Returns:
          list: List of lines.
      """
      footer = []
      template_lines = []
      footer_start_index = 0
  
      with open(in_template) as f:
          template_lines = f.readlines()
          for index, line in enumerate(template_lines):
              if "## Display Earth Engine data layers" in line:
                  footer_start_index = index - 3
  
      footer = ["\n"] + template_lines[footer_start_index:]
  
      return footer
  
  
  def py_to_ipynb(
      in_file, template_file, out_file=None, github_username=None, github_repo=None
  ):
      """Converts Earth Engine Python script to Jupyter notebook.
  
      Args:
          in_file (str): Input Earth Engine Python script.
          template_file (str): Input Jupyter notebook template.
          out_file (str, optional)): Output Jupyter notebook.
          github_username (str, optional): GitHub username. Defaults to None.
          github_repo (str, optional): GitHub repo name. Defaults to None.
      """
      in_file = os.path.abspath(in_file)
      if out_file is None:
          out_file = os.path.splitext(in_file)[0].replace("_qgis", "") + ".ipynb"
  
      out_py_file = os.path.splitext(out_file)[0] + ".py"
  
      out_dir = os.path.dirname(out_file)
      if not os.path.exists(out_dir):
          os.makedirs(out_dir)
      if out_dir == os.path.dirname(in_file):
          out_py_file = os.path.splitext(out_file)[0] + ".py"
  
      content = remove_qgis_import(in_file)
      header = template_header(template_file)
      footer = template_footer(template_file)
  
      if (github_username is not None) and (github_repo is not None):
  
          out_py_path = str(out_file).split("/")
          index = out_py_path.index(github_repo)
          out_py_relative_path = "/".join(out_py_path[index + 1 :])
          out_ipynb_relative_path = out_py_relative_path.replace(".py", ".ipynb")
  
          new_header = []
          for index, line in enumerate(header):
              if index < 9:  # Change Google Colab and binder URLs
                  line = line.replace("giswqs", github_username)
                  line = line.replace("geemap", github_repo)
                  line = line.replace(
                      "examples/template/template.ipynb", out_ipynb_relative_path
                  )
              new_header.append(line)
          header = new_header
  
      if content != None:
          out_text = header + content + footer
      else:
          out_text = header + footer
  
      if not os.path.exists(os.path.dirname(out_py_file)):
          os.makedirs(os.path.dirname(out_py_file))
  
      with open(out_py_file, "w") as f:
          f.writelines(out_text)
  
      try:
          # command = 'ipynb-py-convert ' + out_py_file + ' ' + out_file
          command = 'ipynb-py-convert "{}" "{}"'.format(out_py_file, out_file)
          print(os.popen(command).read().rstrip())
          # os.popen(command)
      except:
          print("Please install ipynb-py-convert using the following command:\n")
          print("pip install ipynb-py-convert")
  
      # os.remove(out_py_file)
  
  
  def py_to_ipynb_dir(
      in_dir, template_file, out_dir=None, github_username=None, github_repo=None
  ):
      """Converts Earth Engine Python scripts in a folder recursively to Jupyter notebooks.
  
      Args:
          in_dir (str): Input folder containing Earth Engine Python scripts.
          template_file (str): Input jupyter notebook template file.
          out_dir str, optional): Output folder. Defaults to None.
          github_username (str, optional): GitHub username. Defaults to None.
          github_repo (str, optional): GitHub repo name. Defaults to None.
      """
      print("Converting Earth Engine Python scripts to Jupyter notebooks ...\n")
  
      in_dir = os.path.abspath(in_dir)
      files = []
      qgis_files = list(Path(in_dir).rglob("*_qgis.py"))
      py_files = list(Path(in_dir).rglob("*.py"))
  
      if len(qgis_files) == len(py_files) / 2:
          files = qgis_files
      else:
          files = py_files
  
      if out_dir is None:
          out_dir = in_dir
      elif not os.path.exists(out_dir):
          out_dir = os.path.abspath(out_dir)
          os.makedirs(out_dir)
      else:
          out_dir = os.path.abspath(out_dir)
  
      for index, file in enumerate(files):
          in_file = str(file)
          out_file = (
              in_file.replace(in_dir, out_dir)
              .replace("_qgis", "")
              .replace(".py", ".ipynb")
          )
          print("Processing {}/{}: {}".format(index + 1, len(files), in_file))
          py_to_ipynb(in_file, template_file, out_file, github_username, github_repo)
  
  
  def execute_notebook(in_file):
      """Executes a Jupyter notebook and save output cells
  
      Args:
          in_file (str): Input Jupyter notebook.
      """
      # command = 'jupyter nbconvert --to notebook --execute ' + in_file + ' --inplace'
      command = 'jupyter nbconvert --to notebook --execute "{}" --inplace'.format(in_file)
      print(os.popen(command).read().rstrip())
      # os.popen(command)
  
  
  def execute_notebook_dir(in_dir):
      """Executes all Jupyter notebooks in the given directory recursively and save output cells.
  
      Args:
          in_dir (str): Input folder containing notebooks.
      """
      print("Executing Earth Engine Jupyter notebooks ...\n")
  
      in_dir = os.path.abspath(in_dir)
      files = list(Path(in_dir).rglob("*.ipynb"))
      count = len(files)
      if files is not None:
          for index, file in enumerate(files):
              in_file = str(file)
              print("Processing {}/{}: {} ...".format(index + 1, count, file))
              execute_notebook(in_file)
  
  
  def update_nb_header(in_file, github_username=None, github_repo=None):
      """Updates notebook header (binder and Google Colab URLs).
  
      Args:
          in_file (str): The input Jupyter notebook.
          github_username (str, optional): GitHub username. Defaults to None.
          github_repo (str, optional): GitHub repo name. Defaults to None.
      """
      if github_username is None:
          github_username = "giswqs"
      if github_repo is None:
          github_repo = "geemap"
  
      index = in_file.index(github_repo)
      file_relative_path = in_file[index + len(github_repo) + 1 :]
  
      output_lines = []
  
      with open(in_file) as f:
          lines = f.readlines()
          start_line_index = 2
          start_char_index = lines[start_line_index].index("{")
          matching_line_index, matching_char_index = find_matching_bracket(
              lines, start_line_index, start_char_index
          )
  
          header = lines[:matching_line_index]
          content = lines[matching_line_index:]
  
          new_header = []
          search_string = ""
          for line in header:
              line = line.replace("giswqs", github_username)
              line = line.replace("geemap", github_repo)
              if "master?filepath=" in line:
                  search_string = "master?filepath="
                  start_index = line.index(search_string) + len(search_string)
                  end_index = line.index(".ipynb") + 6
                  relative_path = line[start_index:end_index]
                  line = line.replace(relative_path, file_relative_path)
              elif "/master/" in line:
                  search_string = "/master/"
                  start_index = line.index(search_string) + len(search_string)
                  end_index = line.index(".ipynb") + 6
                  relative_path = line[start_index:end_index]
                  line = line.replace(relative_path, file_relative_path)
              new_header.append(line)
  
          output_lines = new_header + content
  
          with open(in_file, "w") as f:
              f.writelines(output_lines)
  
  
  def update_nb_header_dir(in_dir, github_username=None, github_repo=None):
      """Updates header (binder and Google Colab URLs) of all notebooks in a folder .
  
      Args:
          in_dir (str): The input directory containing Jupyter notebooks.
          github_username (str, optional): GitHub username. Defaults to None.
          github_repo (str, optional): GitHub repo name. Defaults to None.
      """
      files = list(Path(in_dir).rglob("*.ipynb"))
      for index, file in enumerate(files):
          file = str(file)
          if ".ipynb_checkpoints" in file:
              del files[index]
      count = len(files)
      if files is not None:
          for index, file in enumerate(files):
              in_file = str(file)
              print("Processing {}/{}: {} ...".format(index + 1, count, file))
              update_nb_header(in_file, github_username, github_repo)
  
  
  # def download_from_url(url, out_file_name=None, out_dir='.', unzip=True):
  #     """Download a file from a URL (e.g., https://github.com/giswqs/whitebox/raw/master/examples/testdata.zip)
  
  #     Args:
  #         url (str): The HTTP URL to download.
  #         out_file_name (str, optional): The output file name to use. Defaults to None.
  #         out_dir (str, optional): The output directory to use. Defaults to '.'.
  #         unzip (bool, optional): Whether to unzip the downloaded file if it is a zip file. Defaults to True.
  #     """
  #     in_file_name = os.path.basename(url)
  
  #     if out_file_name is None:
  #         out_file_name = in_file_name
  #     out_file_path = os.path.join(os.path.abspath(out_dir), out_file_name)
  
  #     print('Downloading {} ...'.format(url))
  
  #     try:
  #         urllib.request.urlretrieve(url, out_file_path)
  #     except:
  #         print("The URL is invalid. Please double check the URL.")
  #         return
  
  #     final_path = out_file_path
  
  #     if unzip:
  #         # if it is a zip file
  #         if '.zip' in out_file_name:
  #             print("Unzipping {} ...".format(out_file_name))
  #             with zipfile.ZipFile(out_file_path, "r") as zip_ref:
  #                 zip_ref.extractall(out_dir)
  #             final_path = os.path.join(os.path.abspath(
  #                 out_dir), out_file_name.replace('.zip', ''))
  
  #         # if it is a tar file
  #         if '.tar' in out_file_name:
  #             print("Unzipping {} ...".format(out_file_name))
  #             with tarfile.open(out_file_path, "r") as tar_ref:
  #                 tar_ref.extractall(out_dir)
  #             final_path = os.path.join(os.path.abspath(
  #                 out_dir), out_file_name.replace('.tart', ''))
  
  #     print('Data downloaded to: {}'.format(final_path))
  
  
  def download_gee_app(url, out_file=None):
      """Downloads JavaScript source code from a GEE App
  
      Args:
          url (str): The URL of the GEE App.
          out_file (str, optional): The output file path for the downloaded JavaScript. Defaults to None.
      """
      cwd = os.getcwd()
      out_file_name = os.path.basename(url) + ".js"
      out_file_path = os.path.join(cwd, out_file_name)
      items = url.split("/")
      items[3] = "javascript"
      items[4] = items[4] + "-modules.json"
      json_url = "/".join(items)
      print("The json url: {}".format(json_url))
  
      if out_file is not None:
          out_file_path = out_file
          if not out_file_path.endswith("js"):
              out_file_path += ".js"
  
      out_dir = os.path.dirname(out_file_path)
      if not os.path.exists(out_dir):
          os.makedirs(out_dir)
  
      json_path = out_file_path + "on"
  
      try:
          urllib.request.urlretrieve(json_url, json_path)
      except:
          print("The URL is invalid. Please double check the URL.")
          return
  
      with open(out_file_path, "w") as f1:
  
          with open(json_path) as f2:
              lines = f2.readlines()
              for line in lines:
                  # print(line)
                  items = line.split("\\n")
                  for index, item in enumerate(items):
                      if (index > 0) and (index < (len(items) - 1)):
                          item = item.replace('\\"', '"')
                          item = item.replace(r"\\", "\n")
                          item = item.replace("\\r", "")
                          f1.write(item + "\n")
      os.remove(json_path)
      print("The JavaScript is saved at: {}".format(out_file_path))
  
  
  # # Download file shared via Google Drive
  # def download_from_gdrive(gfile_url, file_name, out_dir='.', unzip=True):
  #     """Download a file shared via Google Drive
  #        (e.g., https://drive.google.com/file/d/18SUo_HcDGltuWYZs1s7PpOmOq_FvFn04/view?usp=sharing)
  
  #     Args:
  #         gfile_url (str): The Google Drive shared file URL
  #         file_name (str): The output file name to use.
  #         out_dir (str, optional): The output directory. Defaults to '.'.
  #         unzip (bool, optional): Whether to unzip the output file if it is a zip file. Defaults to True.
  #     """
  #     try:
  #         from google_drive_downloader import GoogleDriveDownloader as gdd
  #     except ImportError:
  #         print('GoogleDriveDownloader package not installed. Installing ...')
  #         subprocess.check_call(
  #             ["python", '-m', 'pip', 'install', 'googledrivedownloader'])
  #         from google_drive_downloader import GoogleDriveDownloader as gdd
  
  #     file_id = gfile_url.split('/')[5]
  #     print('Google Drive file id: {}'.format(file_id))
  
  #     dest_path = os.path.join(out_dir, file_name)
  #     gdd.download_file_from_google_drive(file_id, dest_path, True, unzip)
  
  
  if __name__ == "__main__":
  
      # Create a temporary working directory
      work_dir = os.path.join(os.path.expanduser("~"), "geemap")
      # Get Earth Engine JavaScript examples. There are five examples in the geemap package data folder.
      # Change js_dir to your own folder containing your Earth Engine JavaScripts, such as js_dir = '/path/to/your/js/folder'
      js_dir = get_js_examples(out_dir=work_dir)
  
      # Convert all Earth Engine JavaScripts in a folder recursively to Python scripts.
      js_to_python_dir(in_dir=js_dir, out_dir=js_dir, use_qgis=True)
      print("Python scripts saved at: {}".format(js_dir))
  
      # Convert all Earth Engine Python scripts in a folder recursively to Jupyter notebooks.
      # Get the notebook template from the package folder.
      nb_template = get_nb_template()
      py_to_ipynb_dir(js_dir, nb_template)
  
      # Execute all Jupyter notebooks in a folder recursively and save the output cells.
      execute_notebook_dir(in_dir=js_dir)
  
      # # # Download a file from a URL.
      # # url = 'https://github.com/giswqs/whitebox/raw/master/examples/testdata.zip'
      # # download_from_url(url)
  
      # # # Download a file shared via Google Drive.
      # # g_url = 'https://drive.google.com/file/d/18SUo_HcDGltuWYZs1s7PpOmOq_FvFn04/view?usp=sharing'
      # # download_from_gdrive(g_url, 'testdata.zip')
  
      # # # parser = argparse.ArgumentParser()
      # # # parser.add_argument('--input', type=str,
      # # #                     help="Path to the input JavaScript file")
      # # # parser.add_argument('--output', type=str,
      # # #                     help="Path to the output Python file")
      # # # args = parser.parse_args()
      # # # js_to_python(args.input, args.output)
  