scripts: add github_helpers.py

This is meant to be a place where we can store generic zephyr wrappers
around the de-facto standard github API for python.

The first 'customer' will be a script that snapshots our open bugs at a
particular point in time, which will be added in a later patch.

I think it's useful to factor this file out of there from the
beginning just to keep things clean, even though I don't have a second
customer in mind at the moment.

Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
Signed-off-by: Stephanos Ioannidis <root@stephanos.io>
diff --git a/scripts/github_helpers.py b/scripts/github_helpers.py
new file mode 100644
index 0000000..9ef77ea
--- /dev/null
+++ b/scripts/github_helpers.py
@@ -0,0 +1,81 @@
+# Copyright (c) 2022 Nordic Semiconductor ASA
+#
+# SPDX-License-Identifier: Apache-2.0
+
+'''
+Generic GitHub helper routines which may be useful to other scripts.
+
+This file is not meant to be run directly, but rather to be imported
+as a module from other scripts.
+'''
+
+# Note that the type annotations are not currently checked by mypy.
+# Unless that changes, they serve as documentation, rather than
+# guarantees from a type checker.
+
+# stdlib
+import getpass
+import os
+import netrc
+import sys
+from typing import Dict
+
+# third party
+import github
+
+def get_github_credentials(ask: bool = True) -> Dict[str, str]:
+    '''Get credentials for constructing a github.Github object.
+
+    This function tries to get github.com credentials from these
+    places, in order:
+
+    1. a ~/.netrc file, if one exists
+    2. a GITHUB_TOKEN environment variable
+    3. if the 'ask' kwarg is truthy, from the user on the
+       at the command line.
+
+    On failure, RuntimeError is raised.
+
+    Scripts often need credentials because anonym access to
+    api.github.com is rate limited more aggressively than
+    authenticated access. Scripts which use anonymous access are
+    therefore more likely to fail due to rate limiting.
+
+    The return value is a dict which can be passed to the
+    github.Github constructor as **kwargs.
+
+    :param ask: if truthy, the user will be prompted for credentials
+                if none are found from other sources
+    '''
+
+    try:
+        nrc = netrc.netrc()
+    except (FileNotFoundError, netrc.NetrcParseError):
+        nrc = None
+
+    if nrc is not None:
+        auth = nrc.authenticators('github.com')
+        if auth is not None:
+            return {'login_or_token': auth[0], 'password': auth[2]}
+
+    token = os.environ.get('GITHUB_TOKEN')
+    if token:
+        return {'login_or_token': token}
+
+    if ask:
+        print('Missing GitHub credentials:\n'
+              '~/.netrc file not found or has no github.com credentials, '
+              'and GITHUB_TOKEN is not set in the environment. '
+              'Please give your GitHub token.',
+              file=sys.stderr)
+        token = getpass.getpass('token: ')
+        return {'login_or_token': token}
+
+    raise RuntimeError('no credentials found')
+
+def get_github_object(ask: bool = True) -> github.Github:
+    '''Get a github.Github object, created with credentials.
+
+    :param ask: passed to get_github_credentials()
+    '''
+    return github.Github(**get_github_credentials())