1import json
2import subprocess
3
4from sphinx.util.console import bold
5import sphinx.util.logging
6
7from .base import PythonMapperBase, SphinxMapperBase
8
9LOGGER = sphinx.util.logging.getLogger(__name__)
10
11
12class GoSphinxMapper(SphinxMapperBase):
13
14    """Auto API domain handler for Go
15
16    Parses directly from Go files.
17
18    :param app: Sphinx application passed in as part of the extension
19    """
20
21    def load(self, patterns, dirs, ignore=None):
22        """
23        Load objects from the filesystem into the ``paths`` dictionary.
24
25        """
26        for _dir in sphinx.util.status_iterator(
27            dirs, bold("[AutoAPI] Loading Data "), "darkgreen", len(dirs)
28        ):
29            data = self.read_file(_dir, ignore=ignore)
30            if data:
31                self.paths[_dir] = data
32
33        return True
34
35    def read_file(self, path, **kwargs):
36        """Read file input into memory, returning deserialized objects
37
38        :param path: Path of file to read
39        :param **kwargs:
40            * ignore (``list``): List of file patterns to ignore
41        """
42        # TODO support JSON here
43        # TODO sphinx way of reporting errors in logs?
44
45        parser_command = ["godocjson"]
46
47        _ignore = kwargs.get("ignore")
48        if _ignore:
49            parser_command.extend(["-e", "{0}".format("|".join(_ignore))])
50
51        parser_command.append(path)
52
53        try:
54            parsed_data = json.loads(subprocess.check_output(parser_command))
55            return parsed_data
56        except IOError:
57            LOGGER.warning(
58                "Error reading file: {0}".format(path),
59                type="autoapi",
60                subtype="not_readable",
61            )
62        except TypeError:
63            LOGGER.warning(
64                "Error reading file: {0}".format(path),
65                type="autoapi",
66                subtype="not_readable",
67            )
68        return None
69
70    def create_class(self, data, options=None, **kwargs):
71        """Return instance of class based on Go data
72
73        Data keys handled here:
74
75            _type
76                Set the object class
77
78            consts, types, vars, funcs, methods
79                Recurse into :py:meth:`create_class` to create child object
80                instances
81
82        :param data: dictionary data from godocjson output
83        """
84        _type = kwargs.get("_type")
85        obj_map = dict((cls.type, cls) for cls in ALL_CLASSES)
86        try:
87            # Contextual type data from children recursion
88            if _type:
89                LOGGER.debug("Forcing Go Type %s" % _type)
90                cls = obj_map[_type]
91            else:
92                cls = obj_map[data["type"]]
93        except KeyError:
94            # this warning intentionally has no (sub-)type
95            LOGGER.warning("Unknown type: %s" % data)
96        else:
97            if cls.inverted_names and "names" in data:
98                # Handle types that have reversed names parameter
99                for name in data["names"]:
100                    data_inv = {}
101                    data_inv.update(data)
102                    data_inv["name"] = name
103                    if "names" in data_inv:
104                        del data_inv["names"]
105                    for obj in self.create_class(data_inv):
106                        yield obj
107            else:
108                # Recurse for children
109                obj = cls(data, jinja_env=self.jinja_env, app=self.app)
110                for child_type in ["consts", "types", "vars", "funcs", "methods"]:
111                    for child_data in data.get(child_type, []):
112                        obj.children += list(
113                            self.create_class(
114                                child_data,
115                                _type=child_type.replace("consts", "const")
116                                .replace("types", "type")
117                                .replace("vars", "variable")
118                                .replace("funcs", "func")
119                                .replace("methods", "method"),
120                            )
121                        )
122                yield obj
123
124
125class GoPythonMapper(PythonMapperBase):
126
127    language = "go"
128    inverted_names = False
129
130    def __init__(self, obj, **kwargs):
131        super(GoPythonMapper, self).__init__(obj, **kwargs)
132        self.name = obj.get("name") or obj.get("packageName")
133        self.id = self.name
134
135        # Second level
136        self.imports = obj.get("imports", [])
137        self.children = []
138        temp_parameters = map(
139            lambda n: {"name": n["name"], "type": n["type"].lstrip("*")},
140            obj.get("parameters", []),
141        )
142        self.parameters = list(temp_parameters)
143        self.results = obj.get("results", [])
144        self.docstring = obj.get("doc", "")
145
146        # Go Specific
147        self.notes = obj.get("notes", {})
148        self.filenames = obj.get("filenames", [])
149        self.bugs = obj.get("bugs", [])
150
151    def __str__(self):
152        return "<{cls} {id}>".format(cls=self.__class__.__name__, id=self.id)
153
154    @property
155    def short_name(self):
156        """Shorten name property"""
157        return self.name.split(".")[-1]
158
159    @property
160    def namespace(self):
161        pieces = self.id.split(".")[:-1]
162        if pieces:
163            return ".".join(pieces)
164        return None
165
166    @property
167    def ref_type(self):
168        return self.type
169
170    @property
171    def ref_directive(self):
172        return self.type
173
174    @property
175    def methods(self):
176        return self.obj.get("methods", [])
177
178
179class GoVariable(GoPythonMapper):
180    type = "var"
181    inverted_names = True
182
183
184class GoMethod(GoPythonMapper):
185    type = "method"
186    ref_directive = "meth"
187
188    def __init__(self, obj, **kwargs):
189        super(GoMethod, self).__init__(obj, **kwargs)
190        self.receiver = obj.get("recv")
191
192
193class GoConstant(GoPythonMapper):
194    type = "const"
195    inverted_names = True
196
197
198class GoFunction(GoPythonMapper):
199    type = "func"
200    ref_type = "function"
201
202
203class GoPackage(GoPythonMapper):
204    type = "package"
205    ref_directive = "pkg"
206    top_level_object = True
207    _RENDER_LOG_LEVEL = "VERBOSE"
208
209
210class GoType(GoPythonMapper):
211    type = "type"
212
213
214ALL_CLASSES = [GoConstant, GoFunction, GoPackage, GoVariable, GoType, GoMethod]
215