Skip to content

Instantly share code, notes, and snippets.

@voznik
Created August 26, 2025 10:43
Show Gist options
  • Select an option

  • Save voznik/61f43bd829f6a5d0988018543d72f06c to your computer and use it in GitHub Desktop.

Select an option

Save voznik/61f43bd829f6a5d0988018543d72f06c to your computer and use it in GitHub Desktop.
Create Fish shell completions for gcloud by processing the completions file from the latest Google Cloud SDK - forked from femnad/fish-gcloud-completions
#!/usr/bin/env python3
import argparse
import http.client
import importlib.util
import logging
import os.path
import re
import tarfile
import uuid
from typing import List
from urllib.parse import urlparse
ARCHIVE_URL = 'https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-{version}-linux-x86_64.tar.gz'
BUFFER_SIZE = 8192
COMPLETIONS_FILE = 'google-cloud-sdk/data/cli/gcloud_completions.py'
DEFAULT_OUTPUT_FILE = 'gcloud.fish'
DEFAULT_OUTPUT_PATH = os.path.expanduser('~/.config/fish/completions')
VERSION_REGEX = re.compile(r'Installing the latest gcloud CLI version \(([0-9]+\.[0-9]+\.[0-9]+)\)')
BASE_COMPLETIONS = """function __gcloud_needs_command
set -l tokens (commandline -opc)
set -e tokens[1]
contains $tokens {root_commands}
and return 1
return 0
end
function __gcloud_starts_with
set -l subcommand $argv
set -l current_cmd (commandline -opc)
string match -r -- "^gcloud $subcommand\$" "$current_cmd"
end
complete -c gcloud -f -n __gcloud_needs_command -a '{root_commands}'
"""
def get_logger():
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter('[%(asctime)s] %(message)s', '%F %T')
ch.setFormatter(formatter)
logger = logging.getLogger('fish-gcloud-completions')
logger.setLevel(logging.DEBUG)
logger.addHandler(ch)
return logger
logger = get_logger()
def generate(root, cmds, preceeding, completions, root_flags):
subcmds = cmds['commands']
subcmd_list = ' '.join(subcmds.keys())
flags = cmds['flags']
starts_with = ' '.join(preceeding)
if root and subcmd_list:
completions.append(f"complete -c gcloud -f -n '__gcloud_starts_with {starts_with}' -a '{subcmd_list}'")
flags.update(root_flags)
flags_list = ' '.join([f'-l {flag[2:]}' for flag in flags])
completions.append(f"complete -c gcloud -f -n '__gcloud_starts_with {starts_with}' {flags_list}")
for sub, subval in subcmds.items():
generate(sub, subval, preceeding + [sub], completions, root_flags)
def get_output_file(output: str) -> str:
if os.path.isdir(output):
return os.path.join(output, DEFAULT_OUTPUT_FILE)
output_dir = os.path.dirname(output)
if not os.path.exists(output_dir):
os.makedirs(output_dir)
return output
def write_completion_file(completions_file: str, output: str, subset: List[str]):
spec = importlib.util.spec_from_file_location('completions', completions_file)
completions_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(completions_module)
commands = completions_module.STATIC_COMPLETION_CLI_TREE
if subset:
commands = {
'commands': {
c: v
for c, v in commands['commands'].items() if c in subset
},
'flags': commands['flags']
}
all_commands = commands['commands']
logger.debug('Generating completions')
root_commands = ' '.join(all_commands)
root_flags = commands['flags']
completions = []
generate(None, commands, [], completions, root_flags)
out = BASE_COMPLETIONS.format(root_commands=root_commands)
out = out + '\n'.join(completions)
output = get_output_file(output)
with open(output, 'w') as o:
o.write(out)
def get_latest_version():
logger.debug('Determining latest version')
client = http.client.HTTPSConnection('cloud.google.com')
client.request('GET', '/sdk/docs/install-sdk')
response = client.getresponse()
match = None
for line in response:
line = line.decode('utf-8')
match = VERSION_REGEX.search(line)
if match:
break
if not match:
raise Exception('Cannot determine latest version')
response.close()
client.close()
return match.group(1)
def download() -> str:
temp_archive = f'/tmp/{str(uuid.uuid4())}'
version = get_latest_version()
sdk_link = ARCHIVE_URL.format(version=version)
url = urlparse(sdk_link)
logger.debug(f'Downloading latest version ({version})')
client = http.client.HTTPSConnection(url.netloc)
client.request('GET', url.path)
response = client.getresponse()
with open(temp_archive, 'wb') as archive:
while True:
buffer = response.read(BUFFER_SIZE)
if not buffer:
break
archive.write(buffer)
response.close()
client.close()
return temp_archive
def extract(tar_file, remove_archive) -> str:
logger.debug('Extracting completions file')
def completion_file(mems):
for ti in mems:
if ti.name == COMPLETIONS_FILE:
yield ti
tar = tarfile.open(tar_file)
temp_completion_dir = f'/tmp/{str(uuid.uuid4())}'
tar.extractall(members=completion_file(tar), path=temp_completion_dir)
if remove_archive:
os.remove(tar_file)
return temp_completion_dir
def process_completion_file(tar_file: str, output: str, subset: List[str]):
remove_archive = tar_file is None
if tar_file is None:
tar_file = download()
completion_dir = extract(tar_file, remove_archive)
completion_file = os.path.join(completion_dir, COMPLETIONS_FILE)
write_completion_file(completion_file, output, subset)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-f', '--sdk', help='gcloud archive file')
parser.add_argument('-o', '--output', help='Output directory or file name', default=DEFAULT_OUTPUT_PATH)
parser.add_argument('-s',
'--subset',
nargs='+',
help='Subset of top-level commands for which to generate completions')
args = parser.parse_args()
process_completion_file(args.sdk, args.output, args.subset)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment