golangconfig: Initial implementation of Package Control dependency

Documentation within docs/development.md explains how to install for
development and testing purposes. Once fully setup, the API is
accessible by running "import golangconfig" in the Sublime Text
console.

Change-Id: I314a4b6aa4c7f951116322e0abd163d379af1f36
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dfaf73f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.pyc
+__pycache__
+dev/coverage_reports/
+dev/mock_fs/
\ No newline at end of file
diff --git a/.pep8 b/.pep8
new file mode 100644
index 0000000..79a16af
--- /dev/null
+++ b/.pep8
@@ -0,0 +1,2 @@
+[flake8]
+max-line-length = 120
\ No newline at end of file
diff --git a/.sublime-dependency b/.sublime-dependency
new file mode 100644
index 0000000..9a03714
--- /dev/null
+++ b/.sublime-dependency
@@ -0,0 +1 @@
+10
\ No newline at end of file
diff --git a/Main.sublime-menu b/Main.sublime-menu
new file mode 100644
index 0000000..df36dd0
--- /dev/null
+++ b/Main.sublime-menu
@@ -0,0 +1,29 @@
+[
+    {
+        "id": "preferences",
+        "children":
+        [
+            {
+                "caption": "Package Settings",
+                "mnemonic": "P",
+                "id": "package-settings",
+                "children":
+                [
+                    {
+                        "caption": "Golang Config",
+                        "children":
+                        [
+                            {
+                                "command": "open_file", "args":
+                                {
+                                    "file": "${packages}/User/golang.sublime-settings"
+                                },
+                                "caption": "Settings – User"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+]
diff --git a/all/golangconfig.py b/all/golangconfig.py
new file mode 100644
index 0000000..a59386d
--- /dev/null
+++ b/all/golangconfig.py
@@ -0,0 +1,618 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import os
+import threading
+import sys
+import shellenv
+import sublime
+
+if sys.version_info < (3,):
+    str_cls = unicode  # noqa
+    py2 = True
+else:
+    str_cls = str
+    py2 = False
+
+
+__version__ = '1.0.0-beta'
+__version_info__ = (1, 0, 0, 'beta')
+
+
+# The sublime.platform() function will not be available in ST3 upon initial
+# import, so we determine the platform via the sys.platform value. We cache
+# the value here to prevent extra IPC calls between plugin_host and
+# sublime_text in ST3.
+_platform = {
+    'win32': 'windows',
+    'darwin': 'osx'
+}.get(sys.platform, 'linux')
+
+
+# A special value object to detect if a setting was not found, versus a setting
+# explicitly being set to null/None in a settings file. We can't use a Python
+# object here because the value is serialized to json via the ST API. Byte
+# strings end up turning into an array of integers in ST3.
+_NO_VALUE = '\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F'
+
+
+class EnvVarError(EnvironmentError):
+
+    """
+    An error occurred finding one or more required environment variables
+    """
+
+    missing = None
+
+
+class ExecutableError(EnvironmentError):
+
+    """
+    An error occurred locating the executable requested
+    """
+
+    name = None
+    dirs = None
+
+
+def debug_enabled():
+    """
+    Checks to see if the "debug" setting is true
+
+    :raises:
+        RuntimeError
+            When the function is called from any thread but the UI thread
+
+    :return:
+        A boolean - if debug is enabled
+    """
+
+    # The Sublime Text API is not threadsafe in ST2, so we
+    # double check here to prevent crashes
+    if not isinstance(threading.current_thread(), threading._MainThread):
+        raise RuntimeError('golangconfig.setting_value() must be called from the main thread')
+
+    value = sublime.load_settings('golang.sublime-settings').get('debug')
+    return False if value == '0' else bool(value)
+
+
+def subprocess_info(executable_name, required_vars, optional_vars=None, view=None, window=None):
+    """
+    Gathers and formats information necessary to use subprocess.Popen() to
+    run one of the go executables, with details pulled from setting_value() and
+    executable_path().
+
+    Ensures that the executable path and env dictionary are properly encoded for
+    Sublime Text 2, where byte strings are necessary.
+
+    :param executable_name:
+        A unicode string of the executable to locate, e.g. "go" or "gofmt"
+
+    :param required_vars:
+        A list of unicode strings of the environment variables that are
+        required, e.g. "GOPATH". Obtains values from setting_value().
+
+    :param optional_vars:
+        A list of unicode strings of the environment variables that are
+        optional, but should be pulled from setting_value() if available - e.g.
+        "GOOS", "GOARCH". Obtains values from setting_value().
+
+    :param view:
+        A sublime.View object to use in finding project-specific settings. This
+        should be passed whenever available.
+
+    :param window:
+        A sublime.Window object to use in finding project-specific settings.
+        This should be passed whenever available.
+
+    :raises:
+        RuntimeError
+            When the function is called from any thread but the UI thread
+        TypeError
+            When any of the parameters are of the wrong type
+        golangconfig.ExecutableError
+            When the executable requested could not be located. The .name
+            attribute contains the name of the executable that could not be
+            located. The .dirs attribute contains a list of unicode strings
+            of the directories searched.
+        golangconfig.EnvVarError
+            When one or more required_vars are not available. The .missing
+            attribute will be a list of the names of missing environment
+            variables.
+
+    :return:
+        A two-element tuple.
+
+         - [0] A unicode string (byte string for ST2) of the path to the executable
+         - [1] A dict to pass to the env parameter of subprocess.Popen()
+    """
+
+    path, _ = executable_path(executable_name, view=view, window=window)
+    if path is None:
+        name = executable_name
+        if sys.platform == 'win32':
+            name += '.exe'
+        dirs = []
+        settings_path, _ = _get_most_specific_setting('PATH', view=view, window=window)
+        if settings_path and settings_path != _NO_VALUE:
+            dirs.extend(settings_path.split(os.pathsep))
+        _, shell_dirs = shellenv.get_path()
+        for shell_dir in shell_dirs:
+            if shell_dir not in dirs:
+                dirs.append(shell_dir)
+        exception = ExecutableError(
+            'The executable "%s" could not be located in any of the following locations: "%s"' %
+            (
+                name,
+                '", "'.join(dirs)
+            )
+        )
+        exception.name = name
+        exception.dirs = dirs
+        raise exception
+
+    path = shellenv.path_encode(path)
+
+    _, env = shellenv.get_env(for_subprocess=True)
+
+    var_groups = [required_vars]
+    if optional_vars:
+        var_groups.append(optional_vars)
+
+    missing_vars = []
+
+    for var_names in var_groups:
+        for var_name in var_names:
+            value, _ = setting_value(var_name, view=view, window=window)
+            var_key = var_name
+
+            if value is not None:
+                value = str_cls(value)
+                value = shellenv.env_encode(value)
+            var_key = shellenv.env_encode(var_key)
+
+            if value is None:
+                if var_key in env:
+                    del env[var_key]
+                continue
+
+            env[var_key] = value
+
+    for required_var in required_vars:
+        var_key = shellenv.env_encode(required_var)
+        if var_key not in env:
+            missing_vars.append(required_var)
+
+    if missing_vars:
+        missing_vars = sorted(missing_vars, key=lambda s: s.lower())
+        exception = EnvVarError(
+            'The following environment variable%s currently unset: %s' %
+            (
+                's are' if len(missing_vars) > 1 else ' is',
+                ', '.join(missing_vars)
+            )
+        )
+        exception.missing = missing_vars
+        raise exception
+
+    encoded_goroot = shellenv.env_encode('GOROOT')
+    if encoded_goroot in env:
+        unicode_sep = shellenv.path_decode(os.sep)
+        name = executable_name
+        if sys.platform == 'win32':
+            name += '.exe'
+        relative_executable_path = shellenv.path_encode('bin%s%s' % (unicode_sep, name))
+        goroot_executable_path = os.path.join(env[encoded_goroot], relative_executable_path)
+        if goroot_executable_path != path:
+            print(
+                'golangconfig: warning - binary %s was found at "%s", which is not inside of the GOROOT "%s"' %
+                (
+                    executable_name,
+                    path,
+                    shellenv.path_decode(env[encoded_goroot])
+                )
+            )
+
+    return (path, env)
+
+
+def setting_value(setting_name, view=None, window=None):
+    """
+    Returns the user's setting for a specific variable, such as GOPATH or
+    GOROOT. Supports global and per-platform settings. Finds settings by
+    looking in:
+
+    1. If a project is open, the project settings
+    2. The global golang.sublime-settings file
+    3. The user's environment variables, as defined by their login shell
+
+    If the setting is a known name, e.g. GOPATH or GOROOT, the value will be
+    checked to ensure the path exists.
+
+    :param setting_name:
+        A unicode string of the setting to retrieve
+
+    :param view:
+        A sublime.View object to use in finding project-specific settings. This
+        should be passed whenever available.
+
+    :param window:
+        A sublime.Window object to use in finding project-specific settings.
+        This should be passed whenever available.
+
+    :raises:
+        RuntimeError
+            When the function is called from any thread but the UI thread
+        TypeError
+            When any of the parameters are of the wrong type
+
+    :return:
+        A two-element tuple.
+
+        If no setting was found, the return value will be:
+
+         - [0] None
+         - [1] None
+
+        If a setting was found, the return value will be:
+
+         - [0] The setting value
+         - [1] The source of the setting, a unicode string:
+           - "project file (os-specific)"
+           - "golang.sublime-settings (os-specific)"
+           - "project file"
+           - "golang.sublime-settings"
+           - A unicode string of the path to the user's login shell
+
+        The second element of the tuple is intended to be used in the display
+        of debugging information to end users.
+    """
+
+    _require_unicode('setting_name', setting_name)
+    _check_view_window(view, window)
+
+    setting, source = _get_most_specific_setting(setting_name, view, window)
+
+    if setting == _NO_VALUE:
+        setting = None
+        source = None
+
+        shell, env = shellenv.get_env()
+        if setting_name in env:
+            source = shell
+            setting = env[setting_name]
+
+    if setting_name not in set(['GOPATH', 'GOROOT']):
+        return (setting, source)
+
+    # We add some extra processing here for known settings to improve the
+    # user experience, especially around debugging
+    is_str = isinstance(setting, str_cls)
+    if is_str:
+        if os.path.exists(setting):
+            return (setting, source)
+
+        if debug_enabled():
+            print(
+                'golangconfig: the value for %s from %s - "%s" - does not exist on the filesystem' %
+                (
+                    setting_name,
+                    source,
+                    setting
+                )
+            )
+
+    elif debug_enabled():
+        _debug_unicode_string(setting_name, setting, source)
+
+    return (None, None)
+
+
+def executable_path(executable_name, view=None, window=None):
+    """
+    Uses the user's Sublime Text settings and then PATH environment variable
+    as set by their login shell to find a go executable
+
+    :param name:
+        The name of the binary to find - a unicode string of "go", "gofmt" or
+        "godoc"
+
+    :param view:
+        A sublime.View object to use in finding project-specific settings. This
+        should be passed whenever available.
+
+    :param window:
+        A sublime.Window object to use in finding project-specific settings.
+        This should be passed whenever available.
+
+    :raises:
+        RuntimeError
+            When the function is called from any thread but the UI thread
+        TypeError
+            When any of the parameters are of the wrong type
+
+    :return:
+        A 2-element tuple.
+
+        If the executable was not found, the return value will be:
+
+         - [0] None
+         - [1] None
+
+        If the exeutable was found, the return value will be:
+
+         - [0] A unicode string of the full path to the executable
+         - [1] A unicode string of the source of the PATH value
+           - "project file (os-specific)"
+           - "golang.sublime-settings (os-specific)"
+           - "project file"
+           - "golang.sublime-settings"
+           - A unicode string of the path to the user's login shell
+
+        The second element of the tuple is intended to be used in the display
+        of debugging information to end users.
+    """
+
+    _require_unicode('executable_name', executable_name)
+    _check_view_window(view, window)
+
+    executable_suffix = '.exe' if sys.platform == 'win32' else ''
+    suffixed_name = executable_name + executable_suffix
+
+    setting, source = _get_most_specific_setting('PATH', view, window)
+    if setting is not _NO_VALUE:
+        is_str = isinstance(setting, str_cls)
+        if not is_str:
+            if debug_enabled():
+                _debug_unicode_string('PATH', setting, source)
+        else:
+            for dir_ in setting.split(os.pathsep):
+                possible_executable_path = os.path.join(dir_, suffixed_name)
+                if _check_executable(possible_executable_path, source, setting):
+                    return (possible_executable_path, source)
+
+            if debug_enabled():
+                print(
+                    'golangconfig: binary %s not found in PATH from %s - "%s"' %
+                    (
+                        executable_name,
+                        source,
+                        setting
+                    )
+                )
+
+    shell, path_dirs = shellenv.get_path()
+    for dir_ in path_dirs:
+        possible_executable_path = os.path.join(dir_, suffixed_name)
+        if _check_executable(possible_executable_path, shell, os.pathsep.join(path_dirs)):
+            return (possible_executable_path, shell)
+
+    if debug_enabled():
+        print(
+            'golangconfig: binary %s not found in PATH from %s - "%s"' %
+            (
+                executable_name,
+                shell,
+                os.pathsep.join(path_dirs)
+            )
+        )
+
+    return (None, None)
+
+
+def _get_most_specific_setting(name, view, window):
+    """
+    Looks up a setting in the following order:
+
+    1. View settings, looking inside of the "osx", "windows" or "linux" key
+       based on the OS that Sublime Text is running on. These settings are from
+       a project file.
+    2. Window settings (ST3 only), looking inside of the "osx", "windows" or
+       "linux" key based on the OS that Sublime Text is running on. These
+       settings are from a project file.
+    3. golang.sublime-settings, looking inside of the "osx", "windows" or
+       "linux" key based on the OS that Sublime Text is running on.
+    4. The view settings. These settings are from a project file.
+    5. The window settings (ST3 only). These settings are from a project file.
+    6. golang.sublime-settings
+
+    :param name:
+        A unicode string of the setting to fetch
+
+    :param view:
+        A sublime.View object to use in finding project-specific settings. This
+        should be passed whenever available.
+
+    :param window:
+        A sublime.Window object to use in finding project-specific settings.
+        This should be passed whenever available.
+
+    :return:
+        A two-element tuple.
+
+        If no setting was found, the return value will be:
+
+         - [0] golangconfig._NO_VALUE
+         - [1] None
+
+        If a setting was found, the return value will be:
+
+         - [0] The setting value
+         - [1] A unicode string of the source:
+           - "project file (os-specific)"
+           - "golang.sublime-settings (os-specific)"
+           - "project file"
+           - "golang.sublime-settings"
+    """
+
+    # The Sublime Text API is not threadsafe in ST2, so we
+    # double check here to prevent crashes
+    if not isinstance(threading.current_thread(), threading._MainThread):
+        raise RuntimeError('golangconfig.setting_value() must be called from the main thread')
+
+    if view is not None and not isinstance(view, sublime.View):
+        raise TypeError('view must be an instance of sublime.View, not %s' % _type_name(view))
+
+    if window is not None and not isinstance(window, sublime.Window):
+        raise TypeError('window must be an instance of sublime.Window, not %s' % _type_name(window))
+
+    st_settings = sublime.load_settings('golang.sublime-settings')
+
+    view_settings = view.settings().get('golang', {}) if view else {}
+
+    if view and not window:
+        window = view.window()
+
+    window_settings = {}
+    if window:
+        if sys.version_info >= (3,):
+            window_settings = window.project_data().get('settings', {}).get('golang', {})
+        elif not view and window.active_view():
+            window_settings = window.active_view().settings().get('golang', {})
+
+    settings_objects = [
+        (view_settings, 'project file'),
+        (window_settings, 'project file'),
+        (st_settings, 'golang.sublime-settings'),
+    ]
+
+    for settings_object, source in settings_objects:
+        platform_settings = settings_object.get(_platform, _NO_VALUE)
+        if platform_settings == _NO_VALUE:
+            continue
+        if not isinstance(platform_settings, dict):
+            continue
+        if platform_settings.get(name, _NO_VALUE) != _NO_VALUE:
+            return (platform_settings.get(name), source + ' (os-specific)')
+
+    for settings_object, source in settings_objects:
+        result = settings_object.get(name, _NO_VALUE)
+        if result != _NO_VALUE:
+            return (settings_object.get(name), source)
+
+    return (_NO_VALUE, None)
+
+
+def _require_unicode(name, value):
+    """
+    Requires that a parameter be a unicode string
+
+    :param name:
+        A unicode string of the parameter name
+
+    :param value:
+        The parameter value
+
+    :raises:
+        TypeError
+            When the value is not a unicode string
+    """
+
+    if not isinstance(value, str_cls):
+        raise TypeError('%s must be a unicode string, not %s' % (name, _type_name(value)))
+
+
+def _check_view_window(view, window):
+    """
+    Ensures that the view and window parameters to a function are suitable
+    objects for our purposes. There is not a strict check for type to allow for
+    mocking during testing.
+
+    :param view:
+        The view parameter to check
+
+    :param window:
+        The window parameter to check
+
+    :raises:
+        TypeError
+            When the view or window parameters are not of the appropriate type
+    """
+
+    if view is not None:
+        if not isinstance(view, sublime.View):
+            raise TypeError('view must be an instance of sublime.View, not %s' % _type_name(view))
+
+    if window is not None:
+        if not isinstance(window, sublime.Window) and sys.version_info >= (3,):
+            raise TypeError('window must be an instance of sublime.Window, not %s' % _type_name(window))
+
+
+def _type_name(value):
+    """
+    :param value:
+        The value to get the type name of
+
+    :return:
+        A unicode string of the name of the value's type
+    """
+
+    value_cls = value.__class__
+    value_module = value_cls.__module__
+    if value_module in set(['builtins', '__builtin__']):
+        return value_cls.__name__
+
+    return '%s.%s' % (value_module, value_cls.__name__)
+
+
+def _debug_unicode_string(name, value, source):
+    """
+    Displays a debug message to the console if the value is not a unicode string
+
+    :param name:
+        A unicode string of the name of the setting
+
+    :param value:
+        The setting value to check
+
+    :param source:
+        A unicode string of the source of the setting
+    """
+
+    if value is not None and not isinstance(value, str_cls):
+        print(
+            'golangconfig: the value for %s from %s is not a string, but instead a %s' %
+            (
+                name,
+                source,
+                _type_name(value)
+            )
+        )
+
+
+def _check_executable(possible_executable_path, source, setting):
+    """
+    Checks to see if a path to an executable exists and that it is, in fact,
+    executable. Will display debug info if the path exists, but is not
+    executable.
+
+    :param possible_executable_path:
+        A unicode string of the full file path to the executable
+
+    :param source:
+        A unicode string of the source of the setting
+
+    :param setting:
+        A unicode string of the PATH value that the executable was found in
+
+    :return:
+        A boolean - if the possible_executable_path is a file that is executable
+    """
+
+    if os.path.exists(possible_executable_path):
+        is_executable = os.path.isfile(possible_executable_path) and os.access(possible_executable_path, os.X_OK)
+        if is_executable:
+            return True
+
+        if debug_enabled():
+            executable_name = os.path.basename(possible_executable_path)
+            print(
+                'golangconfig: binary %s found in PATH from %s - "%s" - is not executable' %
+                (
+                    executable_name,
+                    source,
+                    setting
+                )
+            )
+
+    return False
diff --git a/dev/__init__.py b/dev/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dev/__init__.py
diff --git a/dev/api_docs.py b/dev/api_docs.py
new file mode 100644
index 0000000..430bf4b
--- /dev/null
+++ b/dev/api_docs.py
@@ -0,0 +1,429 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+"""
+This script is used to extract markdown API docs from Python docstrings of code
+that is part of the golangconfig library.
+"""
+
+# This file is Copyright 2015, Will Bond <will@wbond.net>, licensed to Google
+# under the terms of the Google Inbound Service Agreement, signed August 15 2015
+
+import re
+import os
+import ast
+import _ast
+import textwrap
+
+import CommonMark
+from collections import OrderedDict
+
+
+cur_dir = os.path.dirname(__file__)
+project_dir = os.path.abspath(os.path.join(cur_dir, '..'))
+docs_dir = os.path.join(project_dir, 'docs')
+module_name = 'golangconfig'
+
+
+# Maps a markdown document to a Python source file to look in for
+# class/method/function docstrings
+MD_SOURCE_MAP = {
+    'docs/package_developer.md': ['all/golangconfig.py'],
+}
+
+# A search/replace dictionary to modify docstring contents before generating
+# markdown from them
+definition_replacements = {}
+
+
+def _get_func_info(docstring, def_lineno, code_lines, prefix):
+    """
+    Extracts the function signature and description of a Python function
+
+    :param docstring:
+        A unicode string of the docstring for the function
+
+    :param def_lineno:
+        An integer line number that function was defined on
+
+    :param code_lines:
+        A list of unicode string lines from the source file the function was
+        defined in
+
+    :param prefix:
+        A prefix to prepend to all output lines
+
+    :return:
+        A 2-element tuple:
+
+         - [0] A unicode string of the function signature with a docstring of
+               parameter info
+         - [1] A markdown snippet of the function description
+    """
+
+    definition = code_lines[def_lineno - 1]
+    definition = definition.strip().rstrip(':')
+
+    description = ''
+    found_colon = False
+
+    params = ''
+
+    for line in docstring.splitlines():
+        if line and line[0] == ':':
+            found_colon = True
+        if not found_colon:
+            if description:
+                description += '\n'
+            description += line
+        else:
+            if params:
+                params += '\n'
+            params += line
+
+    description = description.strip()
+    description_md = ''
+    if description:
+        description_md = "%s%s" % (prefix, description.replace("\n", "\n" + prefix))
+        description_md = re.sub('\n>(\\s+)\n', '\n>\n', description_md)
+
+    params = params.strip()
+    if params:
+        definition += (':\n%s    """\n%s    ' % (prefix, prefix))
+        definition += params.replace('\n', '\n%s    ' % prefix)
+        definition += ('\n%s    """' % prefix)
+        definition = re.sub('\n>(\\s+)\n', '\n>\n', definition)
+
+    for search, replace in definition_replacements.items():
+        definition = definition.replace(search, replace)
+
+    return (definition, description_md)
+
+
+def _find_sections(md_ast, sections, last, last_class, total_lines=None):
+    """
+    Walks through a CommonMark AST to find section headers that delineate
+    content that should be updated by this script
+
+    :param md_ast:
+        The AST of the markdown document
+
+    :param sections:
+        A dict to store the start and end lines of a section. The key will be
+        a two-element tuple of the section type ("class", "function",
+        "method" or "attribute") and identifier. The values are a two-element
+        tuple of the start and end line number in the markdown document of the
+        section.
+
+    :param last:
+        A dict containing information about the last section header seen.
+        Includes the keys "type_name", "identifier", "start_line".
+
+    :param last_class:
+        A unicode string of the name of the last class found - used when
+        processing methods and attributes.
+
+    :param total_lines:
+        An integer of the total number of lines in the markdown document -
+        used to work around a bug in the API of the Python port of CommonMark
+    """
+
+    for child in md_ast.children:
+        if child.t == 'ATXHeader':
+
+            if child.level in set([3, 5]) and len(child.inline_content) == 2:
+                first = child.inline_content[0]
+                second = child.inline_content[1]
+                if first.t != 'Code':
+                    continue
+                if second.t != 'Str':
+                    continue
+                type_name = second.c.strip()
+                identifier = first.c.strip().replace('()', '').lstrip('.')
+
+                if last:
+                    sections[(last['type_name'], last['identifier'])] = (last['start_line'], child.start_line - 1)
+                    last.clear()
+
+                if type_name == 'function':
+                    if child.level != 3:
+                        continue
+
+                if type_name == 'class':
+                    if child.level != 3:
+                        continue
+                    last_class.append(identifier)
+
+                if type_name in set(['method', 'attribute']):
+                    if child.level != 5:
+                        continue
+                    identifier = last_class[-1] + '.' + identifier
+
+                last.update({
+                    'type_name': type_name,
+                    'identifier': identifier,
+                    'start_line': child.start_line,
+                })
+
+        elif child.t == 'BlockQuote':
+            find_sections(child, sections, last, last_class)
+
+    if last:
+        sections[(last['type_name'], last['identifier'])] = (last['start_line'], total_lines)
+
+find_sections = _find_sections
+
+
+def walk_ast(node, code_lines, sections, md_chunks):
+    """
+    A callback used to walk the Python AST looking for classes, functions,
+    methods and attributes. Generates chunks of markdown markup to replace
+    the existing content.
+
+    :param node:
+        An _ast module node object
+
+    :param code_lines:
+        A list of unicode strings - the source lines of the Python file
+
+    :param sections:
+        A dict of markdown document sections that need to be updated. The key
+        will be a two-element tuple of the section type ("class", "function",
+        "method" or "attribute") and identifier. The values are a two-element
+        tuple of the start and end line number in the markdown document of the
+        section.
+
+    :param md_chunks:
+        A dict with keys from the sections param and the values being a unicode
+        string containing a chunk of markdown markup.
+    """
+
+    if isinstance(node, _ast.FunctionDef):
+        key = ('function', node.name)
+        if key not in sections:
+            return
+
+        docstring = ast.get_docstring(node)
+        def_lineno = node.lineno + len(node.decorator_list)
+
+        definition, description_md = _get_func_info(docstring, def_lineno, code_lines, '> ')
+
+        md_chunk = textwrap.dedent("""
+            ### `%s()` function
+
+            > ```python
+            > %s
+            > ```
+            >
+            %s
+        """).strip() % (
+            node.name,
+            definition,
+            description_md
+        ) + "\n"
+
+        md_chunks[key] = md_chunk
+
+    elif isinstance(node, _ast.ClassDef):
+        if ('class', node.name) not in sections:
+            return
+
+        for subnode in node.body:
+            if isinstance(subnode, _ast.FunctionDef):
+                node_id = node.name + '.' + subnode.name
+
+                method_key = ('method', node_id)
+                is_method = method_key in sections
+
+                attribute_key = ('attribute', node_id)
+                is_attribute = attribute_key in sections
+
+                is_constructor = subnode.name == '__init__'
+
+                if not is_constructor and not is_attribute and not is_method:
+                    continue
+
+                docstring = ast.get_docstring(subnode)
+                def_lineno = subnode.lineno + len(subnode.decorator_list)
+
+                if not docstring:
+                    continue
+
+                if is_method or is_constructor:
+                    definition, description_md = _get_func_info(docstring, def_lineno, code_lines, '> > ')
+
+                    if is_constructor:
+                        key = ('class', node.name)
+
+                        class_docstring = ast.get_docstring(node)
+                        class_description = textwrap.dedent(class_docstring).strip()
+                        if class_description:
+                            class_description_md = "> %s\n>" % (class_description.replace("\n", "\n> "))
+                        else:
+                            class_description_md = ''
+
+                        md_chunk = textwrap.dedent("""
+                            ### `%s()` class
+
+                            %s
+                            > ##### constructor
+                            >
+                            > > ```python
+                            > > %s
+                            > > ```
+                            > >
+                            %s
+                        """).strip() % (
+                            node.name,
+                            class_description_md,
+                            definition,
+                            description_md
+                        )
+
+                        md_chunk = md_chunk.replace('\n\n\n', '\n\n')
+
+                    else:
+                        key = method_key
+
+                        md_chunk = textwrap.dedent("""
+                            >
+                            > ##### `.%s()` method
+                            >
+                            > > ```python
+                            > > %s
+                            > > ```
+                            > >
+                            %s
+                        """).strip() % (
+                            subnode.name,
+                            definition,
+                            description_md
+                        )
+
+                    if md_chunk[-5:] == '\n> >\n':
+                        md_chunk = md_chunk[0:-5]
+
+                else:
+                    key = attribute_key
+
+                    description = textwrap.dedent(docstring).strip()
+                    description_md = "> > %s" % (description.replace("\n", "\n> > "))
+
+                    md_chunk = textwrap.dedent("""
+                        >
+                        > ##### `.%s` attribute
+                        >
+                        %s
+                    """).strip() % (
+                        subnode.name,
+                        description_md
+                    )
+
+                md_chunks[key] = md_chunk.rstrip()
+
+    elif isinstance(node, _ast.If):
+        for subast in node.body:
+            walk_ast(subast, code_lines, sections, md_chunks)
+        for subast in node.orelse:
+            walk_ast(subast, code_lines, sections, md_chunks)
+
+
+def run():
+    """
+    Looks through the docs/ dir and parses each markdown document, looking for
+    sections to update from Python docstrings. Looks for section headers in
+    the format:
+
+     - ### `ClassName()` class
+     - ##### `.method_name()` method
+     - ##### `.attribute_name` attribute
+     - ### `function_name()` function
+
+    The markdown content following these section headers up until the next
+    section header will be replaced by new markdown generated from the Python
+    docstrings of the associated source files.
+
+    By default maps docs/{name}.md to {modulename}/{name}.py. Allows for
+    custom mapping via the MD_SOURCE_MAP variable.
+    """
+
+    print('Updating API docs...')
+
+    md_files = []
+    for root, _, filenames in os.walk(docs_dir):
+        for filename in filenames:
+            if not filename.endswith('.md'):
+                continue
+            md_files.append(os.path.join(root, filename))
+
+    parser = CommonMark.DocParser()
+
+    for md_file in md_files:
+        md_file_relative = md_file[len(project_dir) + 1:]
+        if md_file_relative in MD_SOURCE_MAP:
+            py_files = MD_SOURCE_MAP[md_file_relative]
+            py_paths = [os.path.join(project_dir, py_file) for py_file in py_files]
+        else:
+            py_files = [os.path.basename(md_file).replace('.md', '.py')]
+            py_paths = [os.path.join(project_dir, module_name, py_files[0])]
+
+            if not os.path.exists(py_paths[0]):
+                continue
+
+        with open(md_file, 'rb') as f:
+            markdown = f.read().decode('utf-8')
+
+        original_markdown = markdown
+        md_lines = list(markdown.splitlines())
+        md_ast = parser.parse(markdown)
+
+        last_class = []
+        last = {}
+        sections = OrderedDict()
+        find_sections(md_ast, sections, last, last_class, markdown.count("\n") + 1)
+
+        md_chunks = {}
+
+        for index, py_file in enumerate(py_files):
+            py_path = py_paths[index]
+
+            with open(os.path.join(py_path), 'rb') as f:
+                code = f.read().decode('utf-8')
+                module_ast = ast.parse(code, filename=py_file)
+                code_lines = list(code.splitlines())
+
+            for node in ast.iter_child_nodes(module_ast):
+                walk_ast(node, code_lines, sections, md_chunks)
+
+        added_lines = 0
+
+        def _replace_md(key, sections, md_chunk, md_lines, added_lines):
+            start, end = sections[key]
+            start -= 1
+            start += added_lines
+            end += added_lines
+            new_lines = md_chunk.split('\n')
+            added_lines += len(new_lines) - (end - start)
+
+            # Ensure a newline above each class header
+            if start > 0 and md_lines[start][0:4] == '### ' and md_lines[start - 1][0:1] == '>':
+                added_lines += 1
+                new_lines.insert(0, '')
+
+            md_lines[start:end] = new_lines
+            return added_lines
+
+        for key in sections:
+            if key not in md_chunks:
+                raise ValueError('No documentation found for %s' % key[1])
+            added_lines = _replace_md(key, sections, md_chunks[key], md_lines, added_lines)
+
+        markdown = '\n'.join(md_lines).strip() + '\n'
+
+        if original_markdown != markdown:
+            with open(md_file, 'wb') as f:
+                f.write(markdown.encode('utf-8'))
+
+
+if __name__ == '__main__':
+    run()
diff --git a/dev/mocks.py b/dev/mocks.py
new file mode 100644
index 0000000..3085c70
--- /dev/null
+++ b/dev/mocks.py
@@ -0,0 +1,241 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import os
+import sys
+import shutil
+import locale
+import stat
+
+import golangconfig
+
+if sys.version_info < (3,):
+    from cStringIO import StringIO
+    str_cls = unicode  # noqa
+else:
+    from io import StringIO
+    str_cls = str
+
+
+class SublimeViewMock():
+
+    _settings = None
+    _context = None
+
+    def __init__(self, settings, context):
+        self._settings = settings
+        self._context = context
+
+    def settings(self):
+        if self.window():
+            # In Sublime Text, View objects inherit settings from the window/project
+            # unless they are explicitly set on the view, so we replicate that here
+            merged_golang_settings = self.window().project_data().get('settings', {}).get('golang', {}).copy()
+            merged_golang_settings.update(self._settings)
+        elif self._settings:
+            merged_golang_settings = self._settings.copy()
+        else:
+            merged_golang_settings = {}
+        return {'golang': merged_golang_settings}
+
+    def window(self):
+        return self._context.window
+
+
+class SublimeWindowMock():
+
+    _settings = None
+    _context = None
+
+    def __init__(self, settings, context):
+        self._settings = settings
+        self._context = context
+
+    def project_data(self):
+        return {'settings': {'golang': self._settings}}
+
+    def active_view(self):
+        if self._context.view:
+            return self._context.view
+        return SublimeViewMock({}, self._context)
+
+
+class ShellenvMock():
+
+    _env_encoding = locale.getpreferredencoding() if sys.platform == 'win32' else 'utf-8'
+    _fs_encoding = 'mbcs' if sys.platform == 'win32' else 'utf-8'
+
+    _shell = None
+    _data = None
+
+    def __init__(self, shell, data):
+        self._shell = shell
+        self._data = data
+
+    def get_env(self, for_subprocess=False):
+        if not for_subprocess or sys.version_info >= (3,):
+            return (self._shell, self._data)
+
+        shell = self._shell.encode(self._fs_encoding)
+        env = {}
+        for name, value in self._data.items():
+            env[name.encode(self._env_encoding)] = value.encode(self._env_encoding)
+
+        return (shell, env)
+
+    def get_path(self):
+        return (self._shell, self._data.get('PATH', '').split(os.pathsep))
+
+    def env_encode(self, value):
+        if sys.version_info >= (3,):
+            return value
+        return value.encode(self._env_encoding)
+
+    def path_encode(self, value):
+        if sys.version_info >= (3,):
+            return value
+        return value.encode(self._fs_encoding)
+
+    def path_decode(self, value):
+        if sys.version_info >= (3,):
+            return value
+        return value.decode(self._fs_encoding)
+
+
+class SublimeSettingsMock():
+
+    _values = None
+
+    def __init__(self, values):
+        self._values = values
+
+    def get(self, name, default=None):
+        return self._values.get(name, default)
+
+
+class SublimeMock():
+
+    _settings = None
+    View = SublimeViewMock
+    Window = SublimeWindowMock
+
+    def __init__(self, settings):
+        self._settings = SublimeSettingsMock(settings)
+
+    def load_settings(self, basename):
+        return self._settings
+
+
+class GolangConfigMock():
+
+    _shellenv = None
+    _sublime = None
+    _stdout = None
+
+    _tempdir = None
+
+    _shell = None
+    _env = None
+    _view_settings = None
+    _window_settings = None
+    _sublime_settings = None
+
+    def __init__(self, shell, env, view_settings, window_settings, sublime_settings):
+        self._shell = shell
+        self._env = env
+        self._view_settings = view_settings
+        self._window_settings = window_settings
+        self._sublime_settings = sublime_settings
+        self._tempdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'mock_fs')
+        if not os.path.exists(self._tempdir):
+            os.mkdir(self._tempdir)
+
+    def replace_tempdir_env(self):
+        for key in self._env:
+            self._env[key] = self._env[key].replace(
+                '{tempdir}',
+                self.tempdir + os.sep
+            )
+
+    def replace_tempdir_view_settings(self):
+        self._replace_tempdir_settings(self._view_settings)
+
+    def replace_tempdir_window_settings(self):
+        self._replace_tempdir_settings(self._window_settings)
+
+    def replace_tempdir_sublime_settings(self):
+        self._replace_tempdir_settings(self._sublime_settings)
+
+    def _replace_tempdir_settings(self, settings_dict):
+        if settings_dict:
+            for key in settings_dict:
+                if isinstance(settings_dict[key], str_cls):
+                    settings_dict[key] = settings_dict[key].replace(
+                        '{tempdir}',
+                        self.tempdir + os.sep
+                    )
+            for platform in ['osx', 'windows', 'linux']:
+                if platform not in settings_dict:
+                    continue
+                for key in settings_dict[platform]:
+                    if isinstance(settings_dict[platform][key], str_cls):
+                        settings_dict[platform][key] = settings_dict[platform][key].replace(
+                            '{tempdir}',
+                            self.tempdir + os.sep
+                        )
+
+    def make_executable_files(self, executable_temp_files):
+        self.make_files(executable_temp_files)
+        for temp_file in executable_temp_files:
+            temp_file_path = os.path.join(self.tempdir, temp_file)
+            st = os.stat(temp_file_path)
+            os.chmod(temp_file_path, st.st_mode | stat.S_IEXEC)
+
+    def make_files(self, temp_files):
+        for temp_file in temp_files:
+            temp_file_path = os.path.join(self.tempdir, temp_file)
+            temp_file_dir = os.path.dirname(temp_file_path)
+            if not os.path.exists(temp_file_dir):
+                os.makedirs(temp_file_dir)
+            with open(temp_file_path, 'a'):
+                pass
+
+    def make_dirs(self, temp_dirs):
+        for temp_dir in temp_dirs:
+            temp_dir_path = os.path.join(self.tempdir, temp_dir)
+            if not os.path.exists(temp_dir_path):
+                os.makedirs(temp_dir_path)
+
+    @property
+    def view(self):
+        if self._view_settings is None:
+            return None
+        return SublimeViewMock(self._view_settings, self)
+
+    @property
+    def window(self):
+        if self._window_settings is None:
+            return None
+        return SublimeWindowMock(self._window_settings, self)
+
+    @property
+    def tempdir(self):
+        return self._tempdir
+
+    def __enter__(self):
+        self._shellenv = golangconfig.shellenv
+        golangconfig.shellenv = ShellenvMock(self._shell, self._env)
+        self._sublime = golangconfig.sublime
+        golangconfig.sublime = SublimeMock(self._sublime_settings)
+        self._stdout = sys.stdout
+        sys.stdout = StringIO()
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        golangconfig.shellenv = self._shellenv
+        golangconfig.sublime = self._sublime
+        temp_stdout = sys.stdout
+        sys.stdout = self._stdout
+        print(temp_stdout.getvalue(), end='')
+        if self._tempdir and os.path.exists(self._tempdir):
+            shutil.rmtree(self._tempdir)
diff --git a/dev/reloader.py b/dev/reloader.py
new file mode 100644
index 0000000..69f9f9d
--- /dev/null
+++ b/dev/reloader.py
@@ -0,0 +1,13 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import sys
+
+
+if sys.version_info >= (3,):
+    from imp import reload
+
+if 'golangconfig.dev.mocks' in sys.modules:
+    reload(sys.modules['golangconfig.dev.mocks'])
+if 'golangconfig' in sys.modules:
+    reload(sys.modules['golangconfig'])
diff --git a/dev/tests.py b/dev/tests.py
new file mode 100644
index 0000000..0e6e3aa
--- /dev/null
+++ b/dev/tests.py
@@ -0,0 +1,576 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import unittest
+
+import sys
+import os
+
+if sys.version_info < (3,):
+    str_cls = unicode  # noqa
+else:
+    str_cls = str
+
+import shellenv
+import golangconfig
+from .mocks import GolangConfigMock
+from .unittest_data import data, data_class
+
+
+class CustomString():
+
+    value = None
+
+    def __init__(self, value):
+        self.value = value
+
+    def __str__(self):
+        return self.value
+
+    def __unicode__(self):
+        return self.__str__()
+
+
+@data_class
+class GolangconfigTests(unittest.TestCase):
+
+    @staticmethod
+    def subprocess_info_data():
+        return (
+            (
+                'basic_shell',
+                '/bin/bash',
+                {
+                    'PATH': '{tempdir}bin:{tempdir}usr/bin',
+                    'GOPATH': '{tempdir}gopath',
+                },
+                None,
+                None,
+                {'debug': True},
+                ['usr/bin/go'],
+                ['gopath/'],
+                'go',
+                ['GOPATH'],
+                None,
+                (
+                    '{tempdir}usr/bin/go',
+                    {
+                        'PATH': '{tempdir}bin:{tempdir}usr/bin',
+                        'GOPATH': '{tempdir}gopath',
+                    }
+                ),
+            ),
+            (
+                'view_setting_override',
+                '/bin/bash',
+                {
+                    'PATH': '{tempdir}bin:{tempdir}usr/bin',
+                    'GOPATH': '{tempdir}gopath',
+                },
+                {'GOPATH': '{tempdir}custom/gopath', 'GOOS': 'windows'},
+                None,
+                {'debug': True},
+                ['usr/bin/go'],
+                ['custom/gopath/'],
+                'go',
+                ['GOPATH'],
+                None,
+                (
+                    '{tempdir}usr/bin/go',
+                    {
+                        'PATH': '{tempdir}bin:{tempdir}usr/bin',
+                        'GOPATH': '{tempdir}custom/gopath',
+                    }
+                ),
+            ),
+            (
+                'view_setting_override_optional_missing',
+                '/bin/bash',
+                {
+                    'PATH': '{tempdir}bin:{tempdir}usr/bin',
+                    'GOPATH': '{tempdir}gopath',
+                },
+                {'GOPATH': '{tempdir}custom/gopath'},
+                None,
+                {'debug': True},
+                ['usr/bin/go'],
+                ['custom/gopath/'],
+                'go',
+                ['GOPATH'],
+                ['GOOS'],
+                (
+                    '{tempdir}usr/bin/go',
+                    {
+                        'PATH': '{tempdir}bin:{tempdir}usr/bin',
+                        'GOPATH': '{tempdir}custom/gopath',
+                    }
+                ),
+            ),
+            (
+                'view_setting_override_optional_present',
+                '/bin/bash',
+                {
+                    'PATH': '{tempdir}bin:{tempdir}usr/bin',
+                    'GOPATH': '{tempdir}gopath',
+                },
+                {'GOPATH': '{tempdir}custom/gopath', 'GOOS': 'windows'},
+                None,
+                {'debug': True},
+                ['usr/bin/go'],
+                ['custom/gopath/'],
+                'go',
+                ['GOPATH'],
+                ['GOOS'],
+                (
+                    '{tempdir}usr/bin/go',
+                    {
+                        'PATH': '{tempdir}bin:{tempdir}usr/bin',
+                        'GOPATH': '{tempdir}custom/gopath',
+                        'GOOS': 'windows',
+                    }
+                ),
+            ),
+            (
+                'view_setting_unset',
+                '/bin/bash',
+                {
+                    'PATH': '{tempdir}bin:{tempdir}usr/bin',
+                    'GOPATH': '{tempdir}gopath',
+                    'GOOS': 'windows'
+                },
+                {'GOPATH': '{tempdir}custom/gopath', 'GOOS': None},
+                None,
+                {'debug': True},
+                ['usr/bin/go'],
+                ['custom/gopath/'],
+                'go',
+                ['GOPATH'],
+                ['GOOS'],
+                (
+                    '{tempdir}usr/bin/go',
+                    {
+                        'PATH': '{tempdir}bin:{tempdir}usr/bin',
+                        'GOPATH': '{tempdir}custom/gopath',
+                    }
+                ),
+            ),
+            (
+                'no_executable',
+                '/bin/bash',
+                {
+                    'PATH': '{tempdir}bin:{tempdir}usr/bin',
+                    'GOPATH': '{tempdir}gopath',
+                },
+                {'GOPATH': '{tempdir}custom/gopath'},
+                None,
+                {'debug': True, 'PATH': '{tempdir}usr/local/bin'},
+                [],
+                ['custom/gopath/'],
+                'go',
+                ['GOPATH'],
+                None,
+                golangconfig.ExecutableError
+            ),
+            (
+                'env_var_missing',
+                '/bin/bash',
+                {
+                    'PATH': '{tempdir}bin:{tempdir}usr/bin',
+                    'GOPATH': '{tempdir}gopath',
+                },
+                {'GOPATH': '{tempdir}custom/gopath'},
+                None,
+                {'debug': True},
+                ['bin/go'],
+                ['custom/gopath/'],
+                'go',
+                ['GOPATH', 'GOROOT'],
+                None,
+                golangconfig.EnvVarError
+            ),
+        )
+
+    @data('subprocess_info_data', True)
+    def subprocess_info(self, shell, env, view_settings, window_settings, sublime_settings,
+                        executable_temp_files, temp_dirs, executable_name, required_vars, optional_vars,
+                        expected_result):
+
+        with GolangConfigMock(shell, env, view_settings, window_settings, sublime_settings) as mock_context:
+
+            mock_context.replace_tempdir_env()
+            mock_context.replace_tempdir_view_settings()
+            mock_context.replace_tempdir_window_settings()
+            mock_context.replace_tempdir_sublime_settings()
+
+            mock_context.make_executable_files(executable_temp_files)
+            mock_context.make_dirs(temp_dirs)
+
+            if isinstance(expected_result, tuple):
+                tempdir = mock_context.tempdir + os.sep
+                executable_path = expected_result[0].replace('{tempdir}', tempdir)
+                executable_path = shellenv.path_encode(executable_path)
+
+                env_vars = {}
+                for name, value in expected_result[1].items():
+                    value = value.replace('{tempdir}', tempdir)
+                    name = shellenv.env_encode(name)
+                    value = shellenv.env_encode(value)
+                    env_vars[name] = value
+
+                expected_result = (executable_path, env_vars)
+
+                self.assertEquals(
+                    expected_result,
+                    golangconfig.subprocess_info(
+                        executable_name,
+                        required_vars,
+                        optional_vars=optional_vars,
+                        view=mock_context.view,
+                        window=mock_context.window
+                    )
+                )
+                self.assertEqual('', sys.stdout.getvalue())
+
+            else:
+                def do_test():
+                    golangconfig.subprocess_info(
+                        executable_name,
+                        required_vars,
+                        optional_vars=optional_vars,
+                        view=mock_context.view,
+                        window=mock_context.window
+                    )
+                self.assertRaises(expected_result, do_test)
+
+    @staticmethod
+    def executable_path_data():
+        return (
+            (
+                'basic_shell',
+                '/bin/bash',
+                {
+                    'PATH': '{tempdir}bin:{tempdir}usr/bin'
+                },
+                None,
+                None,
+                {'debug': True},
+                ['bin/go'],
+                [],
+                None,
+                ('{tempdir}bin/go', '/bin/bash'),
+            ),
+            (
+                'basic_view_settings',
+                '/bin/bash',
+                {
+                    'PATH': '{tempdir}bin'
+                },
+                {'PATH': '{tempdir}usr/bin:{tempdir}usr/local/bin'},
+                {},
+                {},
+                ['usr/local/bin/go'],
+                ['usr/bin/go'],
+                None,
+                ('{tempdir}usr/local/bin/go', 'project file'),
+            ),
+            (
+                'basic_view_settings_debug',
+                '/bin/bash',
+                {
+                    'PATH': '{tempdir}bin'
+                },
+                {'PATH': '{tempdir}usr/bin:{tempdir}usr/local/bin'},
+                {},
+                {'debug': True},
+                ['usr/local/bin/go'],
+                ['usr/bin/go'],
+                'is not executable',
+                ('{tempdir}usr/local/bin/go', 'project file'),
+            ),
+            (
+                'basic_view_settings_none_found',
+                '/bin/bash',
+                {
+                    'PATH': '{tempdir}bin'
+                },
+                {'PATH': '{tempdir}usr/bin:{tempdir}usr/local/bin'},
+                {},
+                {'debug': True},
+                [],
+                ['usr/bin/go'],
+                'is not executable',
+                (None, None),
+            ),
+        )
+
+    @data('executable_path_data', True)
+    def executable_path(self, shell, env, view_settings, window_settings, sublime_settings,
+                        executable_temp_files, non_executable_temp_files, expected_debug, expected_result):
+
+        with GolangConfigMock(shell, env, view_settings, window_settings, sublime_settings) as mock_context:
+
+            mock_context.replace_tempdir_env()
+            mock_context.replace_tempdir_view_settings()
+            mock_context.replace_tempdir_window_settings()
+
+            mock_context.make_executable_files(executable_temp_files)
+            mock_context.make_files(non_executable_temp_files)
+
+            if expected_result[0]:
+                tempdir = mock_context.tempdir + os.sep
+                expected_result = (expected_result[0].replace('{tempdir}', tempdir), expected_result[1])
+
+            self.assertEquals(
+                expected_result,
+                golangconfig.executable_path('go', mock_context.view, mock_context.window)
+            )
+            if expected_debug is None:
+                self.assertEqual('', sys.stdout.getvalue())
+            else:
+                self.assertTrue(expected_debug in sys.stdout.getvalue())
+
+    def test_executable_path_path_not_string(self):
+        shell = '/bin/bash'
+        env = {
+            'PATH': '/bin'
+        }
+        view_settings = {
+            'PATH': 1
+        }
+        with GolangConfigMock(shell, env, view_settings, None, {'debug': True}) as mock_context:
+            self.assertEquals((None, None), golangconfig.executable_path('go', mock_context.view, mock_context.window))
+            self.assertTrue('is not a string' in sys.stdout.getvalue())
+
+    @staticmethod
+    def setting_value_gopath_data():
+        return (
+            (
+                'basic_shell',
+                '/bin/bash',
+                {
+                    'PATH': '/bin',
+                    'GOPATH': os.path.expanduser('~'),
+                },
+                None,
+                None,
+                {},
+                'GOPATH',
+                (os.path.expanduser('~'), '/bin/bash'),
+            ),
+            (
+                'basic_shell_2',
+                '/bin/bash',
+                {
+                    'PATH': '/bin'
+                },
+                None,
+                None,
+                {},
+                'PATH',
+                ('/bin', '/bin/bash'),
+            ),
+            (
+                'basic_view_settings',
+                '/bin/bash',
+                {
+                    'PATH': '/bin',
+                    'GOPATH': os.path.expanduser('~'),
+                },
+                {'GOPATH': '/usr/bin'},
+                None,
+                {},
+                'GOPATH',
+                ('/usr/bin', 'project file'),
+            ),
+            (
+                'basic_window_settings',
+                '/bin/bash',
+                {
+                    'PATH': '/bin',
+                    'GOPATH': os.path.expanduser('~'),
+                },
+                None,
+                {'GOPATH': '/usr/bin'},
+                {},
+                'GOPATH',
+                ('/usr/bin', 'project file'),
+            ),
+            (
+                'basic_sublime_settings',
+                '/bin/bash',
+                {
+                    'PATH': '/bin',
+                    'GOPATH': os.path.expanduser('~'),
+                },
+                {},
+                {},
+                {'GOPATH': '/usr/local/bin'},
+                'GOPATH',
+                ('/usr/local/bin', 'golang.sublime-settings'),
+            ),
+            (
+                'os_view_settings',
+                '/bin/bash',
+                {
+                    'PATH': '/bin',
+                    'GOPATH': os.path.expanduser('~'),
+                },
+                {
+                    'osx': {'GOPATH': '/usr/bin'},
+                    'windows': {'GOPATH': '/usr/bin'},
+                    'linux': {'GOPATH': '/usr/bin'},
+                },
+                {},
+                {},
+                'GOPATH',
+                ('/usr/bin', 'project file (os-specific)'),
+            ),
+            (
+                'os_window_settings',
+                '/bin/bash',
+                {
+                    'PATH': '/bin',
+                    'GOPATH': os.path.expanduser('~'),
+                },
+                None,
+                {
+                    'osx': {'GOPATH': '/usr/bin'},
+                    'windows': {'GOPATH': '/usr/bin'},
+                    'linux': {'GOPATH': '/usr/bin'},
+                },
+                {},
+                'GOPATH',
+                ('/usr/bin', 'project file (os-specific)'),
+            ),
+            (
+                'os_sublime_settings',
+                '/bin/bash',
+                {
+                    'PATH': '/bin',
+                    'GOPATH': os.path.expanduser('~'),
+                },
+                {
+                    'GOPATH': '/foo/bar'
+                },
+                {},
+                {
+                    'osx': {'GOPATH': '/usr/local/bin'},
+                    'windows': {'GOPATH': '/usr/local/bin'},
+                    'linux': {'GOPATH': '/usr/local/bin'},
+                },
+                'GOPATH',
+                ('/usr/local/bin', 'golang.sublime-settings (os-specific)'),
+            ),
+            (
+                'os_sublime_settings_wrong_type',
+                '/bin/bash',
+                {
+                    'PATH': '/bin',
+                    'GOPATH': os.path.expanduser('~'),
+                },
+                {},
+                {},
+                {
+                    'osx': 1,
+                    'windows': 1,
+                    'linux': 1,
+                },
+                'GOPATH',
+                (os.path.expanduser('~'), '/bin/bash'),
+            ),
+        )
+
+    @data('setting_value_gopath_data', True)
+    def setting_value_gopath(self, shell, env, view_settings, window_settings, sublime_settings, setting, result):
+
+        with GolangConfigMock(shell, env, view_settings, window_settings, sublime_settings) as mock_context:
+            self.assertEquals(result, golangconfig.setting_value(setting, mock_context.view, mock_context.window))
+            self.assertEqual('', sys.stdout.getvalue())
+
+    def test_setting_value_bytes_name(self):
+        shell = '/bin/bash'
+        env = {
+            'GOPATH': os.path.expanduser('~')
+        }
+        with GolangConfigMock(shell, env, None, None, {'debug': True}) as mock_context:
+            def do_test():
+                golangconfig.setting_value(b'GOPATH', mock_context.view, mock_context.window)
+            self.assertRaises(TypeError, do_test)
+
+    def test_setting_value_custom_type(self):
+        shell = '/bin/bash'
+        env = {
+            'GOPATH': os.path.expanduser('~')
+        }
+        with GolangConfigMock(shell, env, None, None, {'debug': True}) as mock_context:
+            def do_test():
+                golangconfig.setting_value(CustomString('GOPATH'), mock_context.view, mock_context.window)
+            self.assertRaises(TypeError, do_test)
+
+    def test_setting_value_incorrect_view_type(self):
+        shell = '/bin/bash'
+        env = {
+            'GOPATH': os.path.expanduser('~')
+        }
+        with GolangConfigMock(shell, env, None, None, {'debug': True}) as mock_context:
+            def do_test():
+                golangconfig.setting_value('GOPATH', True, mock_context.window)
+            self.assertRaises(TypeError, do_test)
+
+    def test_setting_value_incorrect_window_type(self):
+        shell = '/bin/bash'
+        env = {
+            'GOPATH': os.path.expanduser('~')
+        }
+        with GolangConfigMock(shell, env, None, None, {'debug': True}) as mock_context:
+            def do_test():
+                golangconfig.setting_value('GOPATH', mock_context.view, True)
+            self.assertRaises(TypeError, do_test)
+
+    def test_setting_value_gopath_not_existing(self):
+        shell = '/bin/bash'
+        env = {
+            'GOPATH': os.path.join(os.path.expanduser('~'), 'hdjsahkjzhkjzhiashs7hdsuybyusbguycas')
+        }
+        with GolangConfigMock(shell, env, None, None, {'debug': True}) as mock_context:
+            self.assertEquals(
+                (None, None),
+                golangconfig.setting_value('GOPATH', mock_context.view, mock_context.window)
+            )
+            self.assertTrue('does not exist on the filesystem' in sys.stdout.getvalue())
+
+    def test_setting_value_gopath_not_string(self):
+        shell = '/bin/bash'
+        env = {
+            'GOPATH': 1
+        }
+        with GolangConfigMock(shell, env, None, None, {'debug': True}) as mock_context:
+            self.assertEquals(
+                (None, None),
+                golangconfig.setting_value('GOPATH', mock_context.view, mock_context.window)
+            )
+            self.assertTrue('is not a string' in sys.stdout.getvalue())
+
+    def test_subprocess_info_goroot_executable_not_inside(self):
+        shell = '/bin/bash'
+        env = {
+            'PATH': '{tempdir}bin:{tempdir}go/bin',
+            'GOPATH': '{tempdir}workspace',
+            'GOROOT': '{tempdir}go'
+        }
+        with GolangConfigMock(shell, env, None, None, {}) as mock_context:
+            mock_context.replace_tempdir_env()
+            mock_context.replace_tempdir_view_settings()
+            mock_context.replace_tempdir_window_settings()
+
+            mock_context.make_executable_files(['bin/go', 'go/bin/go'])
+            mock_context.make_dirs(['workspace'])
+
+            golangconfig.subprocess_info(
+                'go',
+                ['GOPATH'],
+                optional_vars=['GOROOT'],
+                view=mock_context.view,
+                window=mock_context.window
+            )
+            self.assertTrue('which is not inside of the GOROOT' in sys.stdout.getvalue())
diff --git a/dev/unittest_data.py b/dev/unittest_data.py
new file mode 100644
index 0000000..90d4eb5
--- /dev/null
+++ b/dev/unittest_data.py
@@ -0,0 +1,55 @@
+# This file is Copyright 2015, Will Bond <will@wbond.net>, licensed to Google
+# under the terms of the Google Inbound Service Agreement, signed August 15 2015
+
+
+def data(provider_method, first_param_name_suffix=False):
+    """
+    A method decorator for unittest.TestCase classes that configured a
+    static method to be used to provide multiple sets of test data to a single
+    test
+
+    :param provider_method:
+        The name of the staticmethod of the class to use as the data provider
+
+    :param first_param_name_suffix:
+        If the first parameter for each set should be appended to the method
+        name to generate the name of the test. Otherwise integers are used.
+
+    :return:
+        The decorated function
+    """
+
+    def test_func_decorator(test_func):
+        test_func._provider_method = provider_method
+        test_func._provider_name_suffix = first_param_name_suffix
+        return test_func
+    return test_func_decorator
+
+
+def data_class(cls):
+    """
+    A class decorator that works with the @provider decorator to generate test
+    method from a data provider
+    """
+
+    def generate_test_func(name, original_function, num, params):
+        if original_function._provider_name_suffix:
+            data_name = params[0]
+            params = params[1:]
+        else:
+            data_name = num
+        expanded_name = 'test_%s_%s' % (name, data_name)
+        # We used expanded variable names here since this line is present in
+        # backtraces that are generated from test failures.
+        generated_test_function = lambda self: original_function(self, *params)
+        setattr(cls, expanded_name, generated_test_function)
+
+    for name in dir(cls):
+        func = getattr(cls, name)
+        if hasattr(func, '_provider_method'):
+            num = 1
+            for params in getattr(cls, func._provider_method)():
+                generate_test_func(name, func, num, params)
+                num += 1
+
+    return cls
diff --git a/docs/changelog.md b/docs/changelog.md
new file mode 100644
index 0000000..015d74e
--- /dev/null
+++ b/docs/changelog.md
@@ -0,0 +1,5 @@
+# golangconfig Changelog
+
+## 1.0.0
+
+ - Initial release
diff --git a/docs/design.md b/docs/design.md
new file mode 100644
index 0000000..66fd381
--- /dev/null
+++ b/docs/design.md
@@ -0,0 +1,30 @@
+# golangconfig Design
+
+The `golangconfig` Sublime Text dependency was designed based on the following
+ideas:
+
+ - Settings should be supported coming from the following sources, in order:
+   - Sublime Text project files under the `golang` key
+   - Any `golang.sublime-settings` files
+   - The user's shell environment, as defined by invoking their login shell
+ - The project and global Sublime Text settings will also allow for placing
+   settings in a platform-specific sub-dictionary to allow users to easily
+   work across different operating systems. The keys for these are used by
+   other ST packages to allow for platform-specific functionality:
+   - "osx"
+   - "windows"
+   - "linux"
+ - Platform-specific settings are always higher priority than
+   non-platform-specific, no matter what source they are pulled from
+ - Setting names that are core to Go configuration preserve the uppercase style
+   of environment variables. Thus the settings are named `GOPATH`, `GOROOT`,
+   `PATH`, etc.
+
+
+ - When returning results, the value requested is returned along with a
+   user-friendly source description that can be used when displaying
+   configuration details to the user
+ - The API eschews duck-typing in an attempt to prevent various edge-case bugs.
+   This is largely due to the weak-typing issues of strings in Python 2 where
+   byte strings and unicode strings are often mixed, only to cause exceptions
+   at runtime on user machines where errors are harder to capture.
diff --git a/docs/development.md b/docs/development.md
new file mode 100644
index 0000000..bcf5e0c
--- /dev/null
+++ b/docs/development.md
@@ -0,0 +1,43 @@
+# golangconfig Development
+
+## Setup
+
+ - Install [Package Coverage](https://packagecontrol.io/packages/Package%20Coverage)
+   to run tests
+ - Install the [shellenv](https://github.com/codexns/shellenv) dependency by
+   executing `git clone --branch 1.4.1 https://github.com/codexns/shellenv`
+   inside of your `Packages/` folder
+ - Install this dependency by
+   executing `git clone https://go.googlesource.com/sublime-config golangconfig`
+   inside of your `Packages/` folder
+ - Use the Package Control command "Install Local Dependency" to install
+   `shellenv` and then `golangconfig` so they are available to the Python plugin
+   environment
+
+## General Notes
+
+ - All code must pass the checks of the Sublime Text package
+   [Python Flake8 Lint](https://packagecontrol.io/packages/Python%20Flake8%20Lint).
+   The `python_interpreter` setting should be set to `internal`.
+ - Tests and coverage measurement must be run in the UI thread since the package
+   utilizes the `sublime` API, which is not thread safe on ST2
+ - Sublime Text 2 and 3 must be supported, on Windows, OS X and Linux
+ - In public-facing functions, types should be strictly checked to help reduce
+   edge-case bugs
+ - All functions must include a full docstring with parameter and return types
+   and a list of exceptions raised
+ - All code should use a consistent Python header
+
+```python
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+```
+
+ - Markdown-based API documentation can be automatically copied from the source
+   code by executing `dev/api_docs.py` with a Python installation containing
+   the `CommonMark` package
+
+```bash
+pip install CommonMark
+python dev/api_docs.py
+```
diff --git a/docs/package_developer.md b/docs/package_developer.md
new file mode 100644
index 0000000..faef4bb
--- /dev/null
+++ b/docs/package_developer.md
@@ -0,0 +1,405 @@
+# golangconfig Package Developer Documentation
+
+`golangconfig` is an API for package developers to obtain configuration
+information about a user's Go environment. It is distributed as a Package
+Control dependency, and thus automatically installed when a package requiring
+it is installed by an end-user.
+
+ - [Overview](#overview)
+ - [Example](#example)
+ - [API Documentation](#api-documentation)
+
+## Overview
+
+### subprocess_info()
+
+`subprocess_info()` is the primary function that will be used in packages.
+It accepts five parameters. The first two are positional:
+
+ 1. the name of the requested executable, e.g. "go", "gofmt", "godoc"
+ 2. a list of required environment variables
+
+The three remaining parameters are keyword arguments:
+
+ - optional_vars: a list of vars that will be pulled from project or Sublime
+   Text settings and used as environment variables
+ - view: a `sublime.View` object, if available
+ - window: a `sublime.Window` object, if available
+
+The function returns a two-element tuple containing the path to the requested
+executable and a `dict` to pass via the `env=` arg of `subprocess.Popen()`.
+
+The `sublime.View` and/or `sublime.Window` objects are used to obtain
+project-specific settings. These objects are available via attributes of the
+`sublime_plugin.WindowCommand` and `sublime_plugin.TextCommand` classes.
+
+The `golangconfig` package interacts with Sublime Text's settings API, which
+means that all calls must occur within the UI thread for compatiblity with
+Sublime Text 2.
+
+### setting_value()
+
+The function `setting_value()` is intended for use when fetching environment
+variables for non-subprocess usage, or for getting settings that are not
+environment variables. Obtaining the value of an individual environment variable
+may be useful when printing debug information, or using the value in an
+interactive manner with the user.
+
+The function accepts three parameters. The first is position:
+
+ 1. a unicode string of the name of the setting or environment variable
+
+The two other parameters are keyword arguments:
+
+ - view: a `sublime.View` object, if available
+ - window: a `sublime.Window` object, if available
+
+The function returns a two-element tuple containing the value of the setting
+requested, and a unicode string describing the source of the setting.
+
+If no value was found for the setting, the tuple `(None, None)` will be
+returned.
+
+If a value is found for the setting, the second element of the tuple will
+contain one of the following unicode strings:
+
+ - "project file"
+ - "project file (os-specific)"
+ - "golang.sublime-settings"
+ - "golang.sublime-settings (os-specific)"
+ - a unicode string of the path to the user's login shell
+
+This value is intended for display to the user for help in debugging.
+
+### Errors
+
+If the executable can not be found, a `golangconfig.ExecutableError()` will be
+raised. It has two attributes: `.name` which is the name of the executable that
+could not be found, and `.dirs` which is a list of the dirs searched by first
+looking at the `PATH` from the Sublime Text settings, and then looking at the
+shell `PATH` value.
+
+If one of the required environment variables is not set, an
+`golangconfig.EnvVarError()` will be raised. It has one attribute: `.missing`
+which is a list of all required environment variables that could not be
+found in the Sublime Text settings, or the shell environment.
+
+### Requiring the Dependency
+
+When developing a package to utilize `golangconfig`, Package Control needs to be
+told to ensure that `golangconfig` is installed. To accomplish this, a file
+named `dependencies.json` needs to be placed in the root of the package. The
+file should contain the following specification:
+
+```json
+{
+    "*": {
+        "*": [
+            "shellenv",
+            "golangconfig"
+        ]
+    }
+}
+```
+
+This specification indicates that for all operating systems (the outer `*`) and
+all versions of Sublime Text (the nested `*`), the dependencies named `shellenv`
+and `golangconfig` are required.
+
+## Example
+
+The following snippet of Python show basic usage of `golangconfig` from within
+command classes derived from `sublime_plugin.WindowCommand` and
+`sublime_plugin.TextCommand`.
+
+```python
+# coding: utf-8
+from __future__ import unicode_literals
+
+import sublime
+import sublime_plugin
+
+import golangconfig
+
+
+class MyWindowCommand(sublime_plugin.WindowCommand):
+    def run(self):
+        try:
+            go_executable_path, env_dict = golangconfig.subprocess_info(
+                'go',
+                ['GOPATH'],
+                [
+                    'GOROOT',
+                    'GOROOT_FINAL',
+                    'GOBIN',
+                    'GOOS',
+                    'GOARCH',
+                    'GORACE',
+                    'GOARM',
+                    'GO386',
+                    'GOHOSTOS',
+                    'GOHOSTARCH',
+                ],
+                window=self.window
+            )
+
+            # Launch thread to execute subprocess.Popen() ...
+
+        except (golangconfig.ExecutableError) as e:
+            error_message = '''
+                My Package
+
+                The %s executable could not be found. Please ensure it is
+                installed and available via your PATH.
+
+                Would you like to view documentation for setting the PATH?
+            '''
+
+            prompt = error_message % e.name
+
+            if sublime.ok_cancel_dialog(prompt, 'Open Documentation'):
+                self.window.run_command(
+                    'open_url',
+                    {'url': 'http://example.com/documentation'}
+                )
+
+        except (golangconfig.EnvVarError) as e:
+            error_message = '''
+                My Package
+
+                The setting%s %s could not be found in your Sublime Text
+                settings or your shell environment.
+
+                Would you like to view the configuration documentation?
+            '''
+
+            plural = 's' if len(e.missing) > 1 else ''
+            setting_names = ', '.join(e.missing)
+            prompt = error_message % (plural, setting_names)
+
+            if sublime.ok_cancel_dialog(prompt, 'Open Documentation'):
+                self.window.run_command(
+                    'open_url',
+                    {'url': 'http://example.com/documentation'}
+                )
+
+
+class MyTextCommand(sublime_plugin.TextCommand):
+    def run(self):
+        # This example omits exception handling for brevity
+        gofmt_executable_path, env = golangconfig.subprocess_info(
+            'gofmt',
+            ['GOPATH'],
+            # GOOS, GOARCH, GO386 and GOARM are omitted from optional_vars in
+            # this example with the intent they would be provided through user
+            # interaction.
+            [
+                'GOROOT',
+                'GOROOT_FINAL',
+                'GOBIN',
+                'GORACE',
+                'GOHOSTOS',
+                'GOHOSTARCH',
+            ],
+            view=self.view
+        )
+
+        goos_setting = golangconfig.setting_value('GOOS', view=self.view)
+        goarch_setting = golangconfig.setting_value('GOARCH', view=self.view)
+
+        # Use the sublime API to show the user OS and ARCH options, with their
+        # values from the settings selected by default
+
+```
+
+Since the `golangconfig` functions must be called in the UI thread, commands
+will normally look up any necessary information before firing off a thread to
+perform a task in the background.
+
+## API Documentation
+
+The public API consists of the following functions:
+
+ - [`subprocess_info()`](#subprocess_info-function)
+ - [`setting_value()`](#setting_value-function)
+ - [`executable_path()`](#executable_path-function)
+ - [`debug_enabled()`](#debug_enabled-function)
+
+### `subprocess_info()` function
+
+> ```python
+> def subprocess_info(executable_name, required_vars, optional_vars=None, view=None, window=None):
+>     """
+>     :param executable_name:
+>         A unicode string of the executable to locate, e.g. "go" or "gofmt"
+>
+>     :param required_vars:
+>         A list of unicode strings of the environment variables that are
+>         required, e.g. "GOPATH". Obtains values from setting_value().
+>
+>     :param optional_vars:
+>         A list of unicode strings of the environment variables that are
+>         optional, but should be pulled from setting_value() if available - e.g.
+>         "GOOS", "GOARCH". Obtains values from setting_value().
+>
+>     :param view:
+>         A sublime.View object to use in finding project-specific settings. This
+>         should be passed whenever available.
+>
+>     :param window:
+>         A sublime.Window object to use in finding project-specific settings.
+>         This will only work for Sublime Text 3, and should only be passed if
+>         no sublime.View object is available to pass via the view parameter.
+>
+>     :raises:
+>         RuntimeError
+>             When the function is called from any thread but the UI thread
+>         TypeError
+>             When any of the parameters are of the wrong type
+>         golangconfig.ExecutableError
+>             When the executable requested could not be located. The .name
+>             attribute contains the name of the executable that could not be
+>             located. The .dirs attribute contains a list of unicode strings
+>             of the directories searched.
+>         golangconfig.EnvVarError
+>             When one or more required_vars are not available. The .missing
+>             attribute will be a list of the names of missing environment
+>             variables.
+>
+>     :return:
+>         A two-element tuple.
+>
+>          - [0] A unicode string (byte string for ST2) of the path to the executable
+>          - [1] A dict to pass to the env parameter of subprocess.Popen()
+>     """
+> ```
+>
+> Gathers and formats information necessary to use subprocess.Popen() to
+> run one of the go executables, with details pulled from setting_value() and
+> executable_path().
+>
+> Ensures that the executable path and env dictionary are properly encoded for
+> Sublime Text 2, where byte strings are necessary.
+
+### `setting_value()` function
+
+> ```python
+> def setting_value(setting_name, view=None, window=None):
+>     """
+>     :param setting_name:
+>         A unicode string of the setting to retrieve
+>
+>     :param view:
+>         A sublime.View object to use in finding project-specific settings. This
+>         should be passed whenever available.
+>
+>     :param window:
+>         A sublime.Window object to use in finding project-specific settings.
+>         This will only work for Sublime Text 3, and should only be passed if
+>         no sublime.View object is available to pass via the view parameter.
+>
+>     :raises:
+>         RuntimeError
+>             When the function is called from any thread but the UI thread
+>         TypeError
+>             When any of the parameters are of the wrong type
+>
+>     :return:
+>         A two-element tuple.
+>
+>         If no setting was found, the return value will be:
+>
+>          - [0] None
+>          - [1] None
+>
+>         If a setting was found, the return value will be:
+>
+>          - [0] The setting value
+>          - [1] The source of the setting, a unicode string:
+>            - "project file (os-specific)"
+>            - "golang.sublime-settings (os-specific)"
+>            - "project file"
+>            - "golang.sublime-settings"
+>            - A unicode string of the path to the user's login shell
+>
+>         The second element of the tuple is intended to be used in the display
+>         of debugging information to end users.
+>     """
+> ```
+>
+> Returns the user's setting for a specific variable, such as GOPATH or
+> GOROOT. Supports global and per-platform settings. Finds settings by
+> looking in:
+>
+> 1. If a project is open, the project settings
+> 2. The global golang.sublime-settings file
+> 3. The user's environment variables, as defined by their login shell
+>
+> If the setting is a known name, e.g. GOPATH or GOROOT, the value will be
+> checked to ensure the path exists.
+
+### `executable_path()` function
+
+> ```python
+> def executable_path(executable_name, view=None, window=None):
+>     """
+>     :param name:
+>         The name of the binary to find - a unicode string of "go", "gofmt" or
+>         "godoc"
+>
+>     :param view:
+>         A sublime.View object to use in finding project-specific settings. This
+>         should be passed whenever available.
+>
+>     :param window:
+>         A sublime.Window object to use in finding project-specific settings.
+>         This will only work for Sublime Text 3, and should only be passed if
+>         no sublime.View object is available to pass via the view parameter.
+>
+>     :raises:
+>         RuntimeError
+>             When the function is called from any thread but the UI thread
+>         TypeError
+>             When any of the parameters are of the wrong type
+>
+>     :return:
+>         A 2-element tuple.
+>
+>         If the executable was not found, the return value will be:
+>
+>          - [0] None
+>          - [1] None
+>
+>         If the exeutable was found, the return value will be:
+>
+>          - [0] A unicode string of the full path to the executable
+>          - [1] A unicode string of the source of the PATH value
+>            - "project file (os-specific)"
+>            - "golang.sublime-settings (os-specific)"
+>            - "project file"
+>            - "golang.sublime-settings"
+>            - A unicode string of the path to the user's login shell
+>
+>         The second element of the tuple is intended to be used in the display
+>         of debugging information to end users.
+>     """
+> ```
+>
+> Uses the user's Sublime Text settings and then PATH environment variable
+> as set by their login shell to find a go executable
+
+### `debug_enabled()` function
+
+> ```python
+> def debug_enabled():
+>     """
+>     :raises:
+>         RuntimeError
+>             When the function is called from any thread but the UI thread
+>
+>     :return:
+>         A boolean - if debug is enabled
+>     """
+> ```
+>
+> Checks to see if the "debug" setting is true
diff --git a/docs/readme.md b/docs/readme.md
new file mode 100644
index 0000000..74ee043
--- /dev/null
+++ b/docs/readme.md
@@ -0,0 +1,14 @@
+# golangconfig Documentation
+
+The documentation for the package is split into two audiences:
+
+ - [User documentation](user.md) describing how to configure Sublime Text
+   to properly work with your Go environment
+ - [Package developer documentation](package_developer.md) containing API
+   documentation and instructions on how to require `golangconfig` for your
+   package
+
+Other documentation:
+
+ - [Design](design.md)
+ - [Development](development.md)
diff --git a/docs/user.md b/docs/user.md
new file mode 100644
index 0000000..973d04f
--- /dev/null
+++ b/docs/user.md
@@ -0,0 +1,138 @@
+# golangconfig User Documentation
+
+The `golangconfig` package is a reusable library that Go-related Sublime Text
+packages can use to obtain information about your Go environment.
+
+This documentation details how you can set OS-specific, per-project and global
+Sublime Text configuration for all packages that utilize `golangconfig`.
+
+ - [Environment Autodetection](#environment-autodetection)
+ - [Overriding the Environment](#overriding-the-environment)
+   - [Global Sublime Text Settings](#global-sublime-text-settings)
+   - [OS-Specific Settings](#os-specific-settings)
+   - [Project-Specific Settings](#project-specific-settings)
+
+## Environment Autodetection
+
+By default `golangconfig` tries to detect all of your Go configuration by
+invoking your login shell. It will pull in your `PATH`, `GOPATH`, and any other
+environment variables you have set.
+
+## Overriding the Environment
+
+Generally, autodetecting the shell environment is sufficient for most users
+with a homogenous Go environment. If your Go configuration is more complex,
+Sublime Text settings may be used to handle it, via:
+
+ - [Global Sublime Text Settings](#global-sublime-text-settings)
+ - [OS-Specific Settings](#os-specific-settings)
+ - [Project-Specific Settings](#project-specific-settings)
+
+Settings are loading using the following precedence, from most-to-least
+specific:
+
+ - OS-specific project settings
+ - OS-specific global Sublime Text settings
+ - Project settings
+ - Global Sublime Text settings
+ - Shell environment
+
+### Global Sublime Text Settings
+
+To set variables for use in Sublime Text windows, you will want to edit your
+`golang.sublime-settings` file. This can be accessed via the menu:
+
+ 1. Preferences
+ 2. Package Settings
+ 3. Golang Config
+ 3. Settings - User
+
+Settings are placed in a json structure. Common settings include:
+
+ - `PATH` - a list of directories to search for executables within. On Windows
+   these are separated by `;`. OS X and Linux use `:` as a directory separator.
+ - `GOPATH` - a string of the path to the root of your Go environment
+
+Other Go settings may, or may not, be supported by the packages using these
+settings. Examples include: `GOOS`, `GOARCH`, `GOROOT`.
+
+```json
+{
+    "PATH": "/Users/jsmith/go/bin",
+    "GOPATH": "/Users/jsmith/go"
+}
+```
+
+### OS-Specific Settings
+
+For users that are working on different operating systems, it may be necessary
+to segement settings per OS. All settings may be nested under a key of one of
+the following strings:
+
+ - "osx"
+ - "windows"
+ - "linux"
+
+```json
+{
+    "osx": {
+        "PATH": "/Users/jsmith/go/bin",
+        "GOPATH": "/Users/jsmith/go"
+    },
+    "windows": {
+        "PATH": "C:\\Users\\jsmith\\go\\bin",
+        "GOPATH": "C:\\Users\\jsmith\\go"
+    },
+    "linux": {
+        "PATH": "/home/jsmith/go/bin",
+        "GOPATH": "/home/jsmith/go"
+    },
+}
+```
+
+### Project-Specific Settings
+
+When working on Go projects that use different environments, it may be
+necessary to define settings in a
+[Sublime Text project](http://docs.sublimetext.info/en/latest/file_management/file_management.html#projects)
+file. The *Project* menu in Sublime Text provides the interface to create and
+edit project files.
+
+Within projects, all Go settings are placed under the `"settings"` key and then
+further under a subkey named `"golang"`.
+
+```json
+{
+    "folders": {
+        "/Users/jsmith/projects/myproj"
+    },
+    "settings": {
+        "golang": {
+            "PATH": "/Users/jsmith/projects/myproj/env/bin",
+            "GOPATH": "/Users/jsmith/projects/myproj/env"
+        }
+    }
+}
+```
+
+Project-specific settings may also utilize the OS-specific settings feature.
+
+```json
+{
+    "folders": {
+        "/Users/jsmith/projects/myproj"
+    },
+    "settings": {
+        "golang": {
+            "osx": {
+                "PATH": "/Users/jsmith/projects/myproj/env/bin",
+                "GOPATH": "/Users/jsmith/projects/myproj/env"
+            },
+            "linux": {
+                "PATH": "/home/jsmith/projects/myproj/env/bin",
+                "GOPATH": "/home/jsmith/projects/myproj/env"
+            }
+        }
+    }
+}
+```
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..0c9caf7
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,21 @@
+# golangconfig
+
+`golangconfig` is a Sublime Text dependency designed to be a common API for
+configuration of Go environment variables. It is intended to be used by any and
+all Go-related Sublime Text packages in an effort to help reduce duplication
+of user configuration.
+
+The documentation for the package is split into two audiences:
+
+ - [User documentation](docs/user.md) describing how to configure Sublime Text
+   to properly work with your Go environment
+ - [Package developer documentation](docs/package_developer.md) containing API
+   documentation and instructions on how to require `golangconfig` for your
+   package
+
+Other documentation:
+
+ - [Changelog](docs/changelog.md)
+ - [License](LICENSE)
+ - [Design](docs/design.md)
+ - [Development](docs/development.md)