diff --git a/.gitignore b/.gitignore index d2d6f36..1ce8652 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ nosetests.xml .mr.developer.cfg .project .pydevproject +.ropeproject diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..1acf731 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,8 @@ +v0.1 +---- + +Initial version: + +- query using simple questions + +- query with optional stackoverflow tags diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..80ec6c2 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (C) 2012 Chakib (sp4ke) Benziane (chakib.benz@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the + +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index 8f0e2a4..0000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -howto -===== - -quick code answers from stackoverflow \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..c92b13d --- /dev/null +++ b/README.rst @@ -0,0 +1,11 @@ +HowTo - StackOverflow Code Search Tool +===================================== + +Quick code answers from StackOverflow API, Just ask a question with an optional +tag parameter to get quick code answers + +USAGE: +===== + +:: + ./howto 'filter dicts' -t python diff --git a/dependencies.txt b/dependencies.txt new file mode 100644 index 0000000..3156a0e --- /dev/null +++ b/dependencies.txt @@ -0,0 +1,6 @@ +cssselect==0.7.1 +ipython==0.13.1 +lxml==3.1beta1 +py-stackexchange==1.1-4 +pyquery==1.2.4 +wsgiref==0.1.2 diff --git a/howto.py b/howto.py deleted file mode 100644 index c99928f..0000000 --- a/howto.py +++ /dev/null @@ -1,29 +0,0 @@ -from stackexchange import * -from pyquery import PyQuery as pq -import sys - -if __name__ == '__main__': - tag = sys.argv[2] - query = sys.argv[1] - so = StackOverflow() - questions = so.search(order='desc', sort='votes', tagged=tag, intitle=query, filter='!-u2CTCMX') - [q.fetch() for q in questions] - - answers = [answer for sublist in - [answer_list for answer_list in - [filter(lambda x: x.accepted, q.answers) for q in questions] - ] - for answer in sublist - ] - - answers_wbody = so.answers([a.id for a in answers],order='desc',sort='votes', body='true', filter='!-u2CTCMX') - answers_wbody = sorted(answers_wbody, key=lambda ans: ans.score, reverse=True) - - - answers_wbody.reverse() - for a in answers_wbody: - html = pq(a.body) - el = html('code') - print el.text() - print '================\n' - diff --git a/howto/__init__.py b/howto/__init__.py new file mode 100644 index 0000000..11d27f8 --- /dev/null +++ b/howto/__init__.py @@ -0,0 +1 @@ +__version__ = '0.1' diff --git a/howto/howto.py b/howto/howto.py new file mode 100755 index 0000000..dd063da --- /dev/null +++ b/howto/howto.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python + + +import argparse +from stackexchange import * +from pyquery import PyQuery as pq +import sys + + +class NoResult(Exception): + pass + +class SOSearch(Site): + def __init__(self, qs, tags=None, **kw): + self.qs = qs + self.tags = '' if tags == None else tags + self.domain = 'api.stackoverflow.com' + super(SOSearch, self).__init__(self.domain, **kw) + self.qs = self.search(order=DESC, sort='votes', tagged=self.tags, + intitle=self.qs, filter='!-u2CTCMX') + if self.qs.total == 0: + raise NoResult + + def first_q(self): + self._populate_one_question(self.qs[0]) + return self.qs[0] + + def has_code(self, answer): + return '' in answer.body + + def _find_best_answer(self, question): + if hasattr(question, 'is_accepted_id'): + for a in question.answers: + if not self.has_code(a): + continue + if a.id == question.is_accepted_id: + question.best_answer = a + else: + for a in question.answers: + if self.has_code(a): + question.best_answer = question.answers[0] + else: + continue + + + def _order_answers(self, qs): + ordered = sorted(qs.answers, key=lambda a: a.score, reverse=True) + qs.answers = ordered + + def _populate_one_question(self, qs): + qs.fetch() + qs.answers = self.answers([a.id for a in qs.answers], order=DESC, body='true') + for a in qs.answers: + html = pq(a.body) + el = html('code') + a.code = el.text() + self._order_answers(qs) + self._find_best_answer(qs) + + + + + +def main(args): + """docstring for main""" + try: + args.query = ' '.join(args.query).replace('?', '') + so = SOSearch(args.query, args.tags) + result = so.first_q().best_answer.code + if result != None: + print result + else: + print("Sorry I can't find your answer, try adding tags") + except NoResult, e: + print("Sorry I can't find your answer, try adding tags") + + +def cli_run(): + """docstring for argparse""" + parser = argparse.ArgumentParser(description='Stupidly simple code answers from StackOverflow') + parser.add_argument('query', help="What's the problem ?", type=str, nargs='+') + parser.add_argument('-t','--tags', help='semicolon separated tags -> python;lambda') + args = parser.parse_args() + main(args) + + + +if __name__ == '__main__': + cli_run() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d67eaef --- /dev/null +++ b/setup.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python + +from setuptools import setup, find_packages +import howto +import os + + +def read(*names): + values = dict() + extensions = ['.txt', '.rst'] + for name in names: + value = '' + for extension in extensions: + filename = name + extension + if os.path.isfile(filename): + value = open(name + extension).read() + break + values[name] = value + return values + +long_description = """ +%(README)s + +CHANGELOG +========= + +%(CHANGES)s + +""" % read('README', 'CHANGES') + +setup( + name='howto', + version=howto.__version__, + description='stackoverflow code search tool', + long_description=long_description, + classifiers=[ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "Intended Audience :: Developers", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Topic :: Documentation", + ], + keywords='howto help console code stackoverflow', + author='Chakib Benziane', + author_email='chakib.benz@gmail.com', + maintainer='Chakib Benziane', + maintainer_email='chakib.benz@gmail.com', + url='https://github.com/sp4ke/howto', + license='MIT', + packages=find_packages(), + entry_points={ + 'console_scripts': [ + 'howto = howto.howto:cli_run', + ] + }, + install_requires=[ + 'pyquery', + 'py-stackexchange', + ], +) diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..2871271 --- /dev/null +++ b/tests.py @@ -0,0 +1,63 @@ +import unittest +from stackexchange import StackOverflow +import stackexchange +from pyquery import PyQuery as pq +from howto.howto import SOSearch +from random import choice + + +class SOSearchTests(unittest.TestCase): + '''Test case for Stackoverflow searches''' + + def setUp(self): + self.so_search = SOSearch('singleton pattern', 'java') + + def test_issubclass_of_stackexchangeSite(self): + self.assertIsInstance(self.so_search, stackexchange.Site) + + def test_init_values(self): + with self.assertRaises(Exception): + so = SOSearch() + + def test_fist_question(self): + first = self.so_search.first_q() + self.assertEqual(first, self.so_search.qs[0]) + + def test_questions_ordered_by_votes(self): + for (qs, next) in zip(self.so_search.qs[:-2], self.so_search.qs[1:]): + self.assertGreaterEqual(qs.score, + next.score) + + def test_extracted_code(self): + question = self.so_search.first_q() + answer = choice(question.answers) + html = pq(answer.body) + el = html('code') + code = el.text() + self.assertEqual(code, answer.code) + + def test_has_code(self): + q = self.so_search.first_q() + a = choice(q.answers) + a.body = 'This is a testMagic' + self.assertEqual(self.so_search.has_code(a), True) + a.body = 'This is a test' + self.assertEqual(self.so_search.has_code(a), False) + + def test_best_answer(self): + question = self.so_search.first_q() + best = question.best_answer + for answer in question.answers: + self.assertGreaterEqual(best.score, answer.score) + + + def test_get_next_answer(self): + self.failUnless(False) + + +def main(): + """docstring for main""" + unittest.main() + +if __name__ == '__main__': + main()