From 23dc803446d000c30ec4b4fc123cfc3e154e04bc Mon Sep 17 00:00:00 2001 From: Alan Hamlett Date: Sat, 2 Jan 2016 19:35:52 -0800 Subject: [PATCH] detect dependencies from golang files --- tests/samples/codefiles/go.go | 24 +++++++++++ tests/test_dependencies.py | 56 +++++++++++++++++++++++++ wakatime/dependencies/go.py | 77 +++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 tests/samples/codefiles/go.go create mode 100644 wakatime/dependencies/go.py diff --git a/tests/samples/codefiles/go.go b/tests/samples/codefiles/go.go new file mode 100644 index 0000000..4167c63 --- /dev/null +++ b/tests/samples/codefiles/go.go @@ -0,0 +1,24 @@ +package main + +// list every possible way of importing to try and break dependency detection +// http://learngowith.me/alternate-ways-of-importing-packages/ + +import "fmt" +import "compress/gzip" +import "github.com/golang/example/stringutil" +import ( + "log" + "os" +) +import newname "oldname" +import . "direct" +import _ "supress" +import ( + "foobar" + . "math" + _ "image/gif" +) + +func main() { + fmt.Println("Hello, World!") +} diff --git a/tests/test_dependencies.py b/tests/test_dependencies.py index 7ba486d..348cc29 100644 --- a/tests/test_dependencies.py +++ b/tests/test_dependencies.py @@ -439,3 +439,59 @@ class DependenciesTestCase(utils.TestCase): dependencies = self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['dependencies'] self.assertListsEqual(dependencies, expected_dependencies) self.patched['wakatime.offlinequeue.Queue.pop'].assert_not_called() + + def test_go_dependencies_detected(self): + response = Response() + response.status_code = 0 + self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = response + + now = u(int(time.time())) + entity = 'tests/samples/codefiles/go.go' + config = 'tests/samples/configs/good_config.cfg' + + args = ['--file', entity, '--config', config, '--time', now] + + retval = execute(args) + self.assertEquals(retval, 102) + self.assertEquals(sys.stdout.getvalue(), '') + self.assertEquals(sys.stderr.getvalue(), '') + + self.patched['wakatime.session_cache.SessionCache.get'].assert_called_once_with() + self.patched['wakatime.session_cache.SessionCache.delete'].assert_called_once_with() + self.patched['wakatime.session_cache.SessionCache.save'].assert_not_called() + + heartbeat = { + 'language': u('Go'), + 'lines': 24, + 'entity': os.path.realpath(entity), + 'project': u(os.path.basename(os.path.realpath('.'))), + 'dependencies': ANY, + 'branch': os.environ.get('TRAVIS_COMMIT', ANY), + 'time': float(now), + 'type': 'file', + } + stats = { + u('cursorpos'): None, + u('dependencies'): ANY, + u('language'): u('Go'), + u('lineno'): None, + u('lines'): 24, + } + expected_dependencies = [ + '"compress/gzip"', + '"direct"', + '"foobar"', + '"github.com/golang/example/stringutil"', + '"image/gif"', + '"log"', + '"math"', + '"oldname"', + '"os"', + '"supress"', + ] + + self.patched['wakatime.offlinequeue.Queue.push'].assert_called_once_with(heartbeat, ANY, None) + dependencies = self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][0]['dependencies'] + self.assertListsEqual(dependencies, expected_dependencies) + self.assertEquals(stats, json.loads(self.patched['wakatime.offlinequeue.Queue.push'].call_args[0][1])) + self.patched['wakatime.offlinequeue.Queue.pop'].assert_not_called() diff --git a/wakatime/dependencies/go.py b/wakatime/dependencies/go.py new file mode 100644 index 0000000..2231e70 --- /dev/null +++ b/wakatime/dependencies/go.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +""" + wakatime.languages.go + ~~~~~~~~~~~~~~~~~~~~~ + + Parse dependencies from Go code. + + :copyright: (c) 2016 Alan Hamlett. + :license: BSD, see LICENSE for more details. +""" + +from . import TokenParser + + +class GoParser(TokenParser): + state = None + parens = 0 + aliases = 0 + exclude = [ + r'^"fmt"$', + ] + + def parse(self): + for index, token, content in self.tokens: + self._process_token(token, content) + return self.dependencies + + def _process_token(self, token, content): + if self.partial(token) == 'Namespace': + self._process_namespace(token, content) + elif self.partial(token) == 'Punctuation': + self._process_punctuation(token, content) + elif self.partial(token) == 'String': + self._process_string(token, content) + elif self.partial(token) == 'Text': + self._process_text(token, content) + elif self.partial(token) == 'Other': + self._process_other(token, content) + else: + self._process_misc(token, content) + + def _process_namespace(self, token, content): + self.state = content + self.parens = 0 + self.aliases = 0 + + def _process_string(self, token, content): + if self.state == 'import': + self.append(content, truncate=False) + + def _process_punctuation(self, token, content): + if content == '(': + self.parens += 1 + elif content == ')': + self.parens -= 1 + elif content == '.': + self.aliases += 1 + else: + self.state = None + + def _process_text(self, token, content): + if self.state == 'import': + if content == "\n" and self.parens <= 0: + self.state = None + self.parens = 0 + self.aliases = 0 + else: + self.state = None + + def _process_other(self, token, content): + if self.state == 'import': + self.aliases += 1 + else: + self.state = None + + def _process_misc(self, token, content): + self.state = None