Merge the latest version of `develop`: bitshift v1.0 (beta).tags/v1.0^0
@@ -1,4 +1,10 @@ | |||||
static/css/* | |||||
!lib | |||||
*.swp | |||||
.sass-cache | .sass-cache | ||||
.DS_Store | |||||
.my.cnf | |||||
# github premade rules | # github premade rules | ||||
*.py[cod] | *.py[cod] | ||||
@@ -18,7 +24,6 @@ var | |||||
sdist | sdist | ||||
develop-eggs | develop-eggs | ||||
.installed.cfg | .installed.cfg | ||||
lib | |||||
lib64 | lib64 | ||||
__pycache__ | __pycache__ | ||||
@@ -37,3 +42,15 @@ nosetests.xml | |||||
.mr.developer.cfg | .mr.developer.cfg | ||||
.project | .project | ||||
.pydevproject | .pydevproject | ||||
# Maven | |||||
target | |||||
# Ruby | |||||
!parsers/ruby/lib | |||||
# Ctags | |||||
*/tags | |||||
logs | |||||
Gemfile.lock | |||||
parsing.jar |
@@ -1,6 +1,6 @@ | |||||
The MIT License (MIT) | The MIT License (MIT) | ||||
Copyright (c) 2014 Ben Kurtovic | |||||
Copyright (c) 2014 Benjamin Attal, Ben Kurtovic, Severyn Kozak | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
of this software and associated documentation files (the "Software"), to deal | of this software and associated documentation files (the "Software"), to deal | ||||
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 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 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
SOFTWARE. | |||||
SOFTWARE. |
@@ -1,4 +1,44 @@ | |||||
bitshift | bitshift | ||||
======== | ======== | ||||
bitshift is an online code snippet exchange. | |||||
bitshift is a semantic search engine for source code developed by Benjamin | |||||
Attal, Ben Kurtovic, and Severyn Kozak. This README is intended for developers | |||||
only. For a user overview of the project: | |||||
* read our [about page](http://bitshift.it/) | |||||
* watch our [demo video](https://vimeo.com/98697078) | |||||
Branches | |||||
-------- | |||||
- `master`: working, tested, version-numbered code - no direct commits; should | |||||
only accept merges from `develop` when ready to release | |||||
- `develop`: integration branch with unreleased but mostly functional code - | |||||
direct commits allowed but should be minor | |||||
- `feature/*`: individual components of the project with untested, likely | |||||
horribly broken code - branch off from and merge into `develop` when done | |||||
Style | |||||
----- | |||||
bitshift uses [SASS][SASS] for styling; compile the stylesheets to CSS with | |||||
`sass --watch static/sass/:static/css`. | |||||
Documentation | |||||
------------- | |||||
To build documentation, run `make html` from the `docs` subdirectory. You can | |||||
then browse from `docs/build/html/index.html`. | |||||
To automatically update the API documentation structure (necessary when adding | |||||
new modules or packages, but *not* when adding functions or changing | |||||
docstrings), run `sphinx-apidoc -fo docs/source/api bitshift` from the project | |||||
root. Note that this will revert any custom changes made to the files in | |||||
`docs/source/api`, so you might want to update them by hand instead. | |||||
[SASS]: http://sass-lang.com/guide | |||||
Releasing | |||||
--------- | |||||
- Update `__version__` in `bitshift/__init__.py`, `version` in `setup.py`, and | |||||
`version` and `release` in `docs/conf.py`. |
@@ -2,21 +2,67 @@ | |||||
Module to contain all the project's Flask server plumbing. | Module to contain all the project's Flask server plumbing. | ||||
""" | """ | ||||
from flask import Flask | |||||
from flask import render_template, session | |||||
from json import dumps | |||||
from bitshift import * | |||||
from flask import Flask, make_response, render_template, request | |||||
app = Flask(__name__) | |||||
from bitshift import assets | |||||
from bitshift.database import Database | |||||
from bitshift.languages import LANGS | |||||
from bitshift.query import parse_query, QueryParseException | |||||
app = Flask(__name__, static_folder="static", static_url_path="") | |||||
app.config.from_object("bitshift.config") | app.config.from_object("bitshift.config") | ||||
app_env = app.jinja_env | app_env = app.jinja_env | ||||
app_env.line_statement_prefix = "=" | app_env.line_statement_prefix = "=" | ||||
app_env.globals.update(assets = assets) | |||||
app_env.globals.update(assets=assets) | |||||
database = Database() | |||||
@app.route("/") | @app.route("/") | ||||
def index(): | def index(): | ||||
return render_template("index.html") | |||||
return render_template("index.html", autocomplete_languages=LANGS) | |||||
@app.route("/search.json") | |||||
def search(): | |||||
def reply(json): | |||||
resp = make_response(dumps(json)) | |||||
resp.mimetype = "application/json" | |||||
return resp | |||||
query = request.args.get("q") | |||||
if not query: | |||||
return reply({"error": "No query given"}) | |||||
try: | |||||
tree = parse_query(query) | |||||
except QueryParseException as exc: | |||||
return reply({"error": exc.args[0]}) | |||||
page = request.args.get("p", 1) | |||||
try: | |||||
page = int(page) | |||||
except ValueError: | |||||
return reply({"error": u"Invalid page number: %s" % page}) | |||||
highlight = request.args.get("hl", "0") | |||||
highlight = highlight.lower() not in ["0", "false", "no"] | |||||
count, codelets = database.search(tree, page) | |||||
results = [clt.serialize(highlight) for clt in codelets] | |||||
return reply({"count": count, "results": results}) | |||||
@app.route("/about") | |||||
def about(): | |||||
return render_template("about.html") | |||||
@app.route("/docs") | |||||
def docs(): | |||||
return render_template("docs.html") | |||||
@app.errorhandler(404) | |||||
def error404(error): | |||||
return render_template("error404.html"), 404 | |||||
if __name__ == "__main__": | if __name__ == "__main__": | ||||
app.run() | |||||
app.run(debug=True) |
@@ -1 +1,8 @@ | |||||
__all__ = ["config", "assets"] | |||||
# -*- coding: utf-8 -*- | |||||
__author__ = "Benjamin Attal, Ben Kurtovic, Severyn Kozak" | |||||
__copyright__ = "Copyright (c) 2014 Benjamin Attal, Ben Kurtovic, Severyn Kozak" | |||||
__license__ = "MIT License" | |||||
__version__ = "0.1.dev" | |||||
from . import assets, codelet, config, crawler, database, parser, query |
@@ -1,22 +1,46 @@ | |||||
""" | """ | ||||
Module contains helper functions to be used inside the project's Jinja | |||||
templates. | |||||
:synopsis: Helper functions for use inside the project's Jinja templates. | |||||
""" | """ | ||||
import re | |||||
from flask import Markup | from flask import Markup | ||||
ASSET_HTML_TEMPLATES = { | ASSET_HTML_TEMPLATES = { | ||||
'css': "<link rel='stylesheet' type='text/css' href='/static/css/%s'>", | |||||
'js': "<script src='/static/js/%s'></script>" | |||||
'css': "<link rel='stylesheet' type='text/css' href='/css/%s'>", | |||||
'js': "<script src='/js/%s'></script>" | |||||
} | } | ||||
def tag(filename): | def tag(filename): | ||||
""" | """ | ||||
Return HTML tag for asset named filename. | |||||
Generate an HTML tag for a CSS/JS asset, based on its file extension. | |||||
:param filename: The filename of the asset to create a tag for. | |||||
:type filename: str | |||||
Return either a <script> or <link> tag to the file named filename, | |||||
based on its extension. | |||||
:return: A string containing a `<source>` tag for JS files, and a `<link>` | |||||
for CSS files. | |||||
:rtype: str | |||||
""" | """ | ||||
file_ext = filename.split(".")[-1] | file_ext = filename.split(".")[-1] | ||||
return Markup(ASSET_HTML_TEMPLATES[file_ext] % filename) | return Markup(ASSET_HTML_TEMPLATES[file_ext] % filename) | ||||
def syntax_highlight(msg): | |||||
""" | |||||
Inserts HTML `<span>` elements into a string, for symbol/word styling. | |||||
Args: | |||||
msg : (str) A message. | |||||
""" | |||||
msg.replace("<", "&;lt") | |||||
msg.replace(">", "&;gt") | |||||
font_size = 16.0 / len(msg) | |||||
msg = re.sub('([!()"%])', '<span class="dark">\\1</span>', msg) | |||||
msg = re.sub('([:.;,])', '<span class="red">\\1</span>', msg) | |||||
msg = msg.replace("404", '<span class="red">404</span>') | |||||
return "<span class='light' style='font-size: %fem'>%s</span>" % ( | |||||
font_size, msg) |
@@ -0,0 +1,103 @@ | |||||
from operator import concat | |||||
from pygments import highlight | |||||
from pygments.lexers import find_lexer_class, get_lexer_by_name | |||||
from pygments.formatters.html import HtmlFormatter | |||||
from .languages import LANGS | |||||
__all__ = ["Codelet"] | |||||
class Codelet(object): | |||||
""" | |||||
A source-code object with code metadata and composition analysis. | |||||
:ivar name: (str) A suitable name for the codelet. | |||||
:ivar code: (str) A containing the raw source code. | |||||
:ivar filename: (str, or None) The filename of the snippet. | |||||
:ivar language: (int, or None) The inferred language of `code`. | |||||
:ivar authors: (array of tuples (str, str or None)) An array of tuples | |||||
containing an author's name and profile URL (on the service the code | |||||
was pulled from). | |||||
:ivar url: (str) The url of the (page containing the) source code. | |||||
:ivar date_created: (:class:`datetime.datetime`, or None) The date the code | |||||
was published. | |||||
:ivar date_modified: (:class:`datetime.datetime`, or None) The date the | |||||
code was last modified. | |||||
:ivar rank: (float) A quanitification of the source code's quality, as | |||||
per available ratings (stars, forks, upvotes, etc.). | |||||
:ivar symbols: (dict) Dictionary containing dictionaries of functions, | |||||
classes, variable definitions, etc. | |||||
:ivar origin: (tuple) 2-tuple of (site_name, site_url), as added by the | |||||
database. | |||||
""" | |||||
def __init__(self, name, code, filename, language, authors, url, | |||||
date_created, date_modified, rank, symbols=None, origin=None): | |||||
""" | |||||
Create a Codelet instance. | |||||
:param name: see :attr:`self.name` | |||||
:param code: see :attr:`self.code` | |||||
:param filename: see :attr:`self.filename` | |||||
:param language: see :attr:`self.language` | |||||
:param authors: see :attr:`self.authors` | |||||
:param url: see :attr:`self.url` | |||||
:param date_created: see :attr:`self.date_created` | |||||
:param date_modified: see :attr:`self.date_modified` | |||||
:param rank: see :attr:`self.rank` | |||||
:param symbols: see :attr:`self.symbols` | |||||
:param origin: see :attr:`self.origin` | |||||
:type name: see :attr:`self.name` | |||||
:type code: see :attr:`self.code` | |||||
:type filename: see :attr:`self.filename` | |||||
:type language: see :attr:`self.language` | |||||
:type authors: see :attr:`self.authors` | |||||
:type url: see :attr:`self.url` | |||||
:type date_created: see :attr:`self.date_created` | |||||
:type date_modified: see :attr:`self.date_modified` | |||||
:type rank: see :attr:`self.rank` | |||||
:type symbols: see :attr:`self.symbols` | |||||
:type origin: see :attr:`self.origin` | |||||
""" | |||||
self.name = name | |||||
self.code = code | |||||
self.filename = filename | |||||
self.language = language | |||||
self.authors = authors | |||||
self.url = url | |||||
self.date_created = date_created | |||||
self.date_modified = date_modified | |||||
self.rank = rank | |||||
self.symbols = symbols or {} | |||||
self.origin = origin or (None, None) | |||||
def serialize(self, highlight_code=False): | |||||
""" | |||||
Convert the codelet into a dictionary that can be sent as JSON. | |||||
:param highlight_code: Whether to return code as pygments-highlighted | |||||
HTML or as plain source. | |||||
:type highlight_code: bool | |||||
:return: The codelet as a dictionary. | |||||
:rtype: str | |||||
""" | |||||
lang = LANGS[self.language] | |||||
code = self.code | |||||
if highlight_code: | |||||
lexer = find_lexer_class(lang)() or get_lexer_by_name("text") | |||||
symbols = reduce(concat, self.symbols.values(), []) | |||||
lines = reduce(concat, [[loc[0] for loc in sym[1] + sym[2]] | |||||
for sym in symbols], []) | |||||
formatter = HtmlFormatter(linenos=True, hl_lines=lines) | |||||
code = highlight(code, lexer, formatter) | |||||
return { | |||||
"name": self.name, "code": code, "lang": lang, | |||||
"authors": self.authors, "url": self.url, | |||||
"created": self.date_created.isoformat(), | |||||
"modified": self.date_modified.isoformat(), "origin": self.origin | |||||
} |
@@ -0,0 +1,94 @@ | |||||
""" | |||||
:synopsis: Parent crawler module, which supervises all crawlers. | |||||
Contains functions for initializing all subsidiary, threaded crawlers. | |||||
""" | |||||
import logging | |||||
import logging.handlers | |||||
import os | |||||
import Queue | |||||
import sys | |||||
import time | |||||
from threading import Event | |||||
from .crawler import GitHubCrawler, BitbucketCrawler | |||||
from .indexer import GitIndexer, GitRepository | |||||
__all__ = ["crawl"] | |||||
MAX_URL_QUEUE_SIZE = 5e3 | |||||
def crawl(): | |||||
""" | |||||
Initialize all crawlers (and indexers). | |||||
Start the: | |||||
1. GitHub crawler, :class:`crawler.GitHubCrawler`. | |||||
2. Bitbucket crawler, :class:`crawler.BitbucketCrawler`. | |||||
3. Git indexer, :class:`bitshift.crawler.indexer.GitIndexer`. | |||||
""" | |||||
_configure_logging() | |||||
time.sleep(5) | |||||
repo_clone_queue = Queue.Queue(maxsize=MAX_URL_QUEUE_SIZE) | |||||
run_event = Event() | |||||
run_event.set() | |||||
threads = [GitIndexer(repo_clone_queue, run_event)] | |||||
if sys.argv[1:]: | |||||
names = sys.argv[1:] | |||||
ranks = GitHubCrawler.get_ranks(names) | |||||
for name in names: | |||||
repo = GitRepository("https://github.com/" + name, name, "GitHub", | |||||
ranks[name]) | |||||
repo_clone_queue.put(repo) | |||||
else: | |||||
threads += [GitHubCrawler(repo_clone_queue, run_event), | |||||
BitbucketCrawler(repo_clone_queue, run_event)] | |||||
for thread in threads: | |||||
thread.start() | |||||
try: | |||||
while 1: | |||||
time.sleep(0.1) | |||||
except KeyboardInterrupt: | |||||
run_event.clear() | |||||
with repo_clone_queue.mutex: | |||||
repo_clone_queue.queue.clear() | |||||
for thread in threads: | |||||
thread.join() | |||||
def _configure_logging(): | |||||
# This isn't ideal, since it means the bitshift python package must be kept | |||||
# inside the app, but it works for now: | |||||
root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) | |||||
log_dir = os.path.join(root, "logs") | |||||
if not os.path.exists(log_dir): | |||||
os.mkdir(log_dir) | |||||
logging.getLogger("requests").setLevel(logging.WARNING) | |||||
logging.getLogger("urllib3").setLevel(logging.WARNING) | |||||
formatter = logging.Formatter( | |||||
fmt=("%(asctime)s %(levelname)s %(name)s %(message)s"), | |||||
datefmt="%y-%m-%d %H:%M:%S") | |||||
file_handler = logging.handlers.TimedRotatingFileHandler( | |||||
"%s/%s" % (log_dir, "app.log"), when="H", interval=1, | |||||
backupCount=20) | |||||
stream_handler = logging.StreamHandler() | |||||
file_handler.setFormatter(formatter) | |||||
stream_handler.setFormatter(formatter) | |||||
root_logger = logging.getLogger() | |||||
root_logger.handlers = [] | |||||
root_logger.addHandler(file_handler) | |||||
root_logger.addHandler(stream_handler) | |||||
root_logger.setLevel(logging.NOTSET) | |||||
if __name__ == "__main__": | |||||
crawl() |
@@ -0,0 +1,243 @@ | |||||
""" | |||||
:synopsis: Main crawler module, to oversee all site-specific crawlers. | |||||
Contains all website/framework-specific Class crawlers. | |||||
""" | |||||
import logging | |||||
import math | |||||
import time | |||||
import threading | |||||
import requests | |||||
from . import indexer | |||||
class GitHubCrawler(threading.Thread): | |||||
""" | |||||
Crawler that retrieves links to all of GitHub's public repositories. | |||||
GitHubCrawler is a threaded singleton that queries GitHub's API for urls | |||||
to its public repositories, which it inserts into a :class:`Queue.Queue` | |||||
shared with :class:`indexer.GitIndexer`. | |||||
:ivar clone_queue: (:class:`Queue.Queue`) Contains :class:`GitRepository` | |||||
with repository metadata retrieved by :class:`GitHubCrawler`, and other Git | |||||
crawlers, to be processed by :class:`indexer.GitIndexer`. | |||||
:ivar _logger: (:class:`logging.Logger`) A class-specific logger object. | |||||
""" | |||||
AUTHENTICATION = { | |||||
"client_id" : "436cb884ae09be7f2a4e", | |||||
"client_secret" : "8deeefbc2439409c5b7a092fd086772fe8b1f24e" | |||||
} | |||||
def __init__(self, clone_queue, run_event): | |||||
""" | |||||
Create an instance of the singleton `GitHubCrawler`. | |||||
:param clone_queue: see :attr:`self.clone_queue` | |||||
:type clone_queue: see :attr:`self.clone_queue` | |||||
""" | |||||
self.clone_queue = clone_queue | |||||
self.run_event = run_event | |||||
self._logger = logging.getLogger("%s.%s" % | |||||
(__name__, self.__class__.__name__)) | |||||
self._logger.info("Starting.") | |||||
super(GitHubCrawler, self).__init__(name=self.__class__.__name__) | |||||
def run(self): | |||||
""" | |||||
Query the GitHub API for data about every public repository. | |||||
Pull all of GitHub's repositories by making calls to its API in a loop, | |||||
accessing a subsequent page of results via the "next" URL returned in an | |||||
API response header. Uses Severyn Kozak's (sevko) authentication | |||||
credentials. For every new repository, a :class:`GitRepository` is | |||||
inserted into :attr:`self.clone_queue`. | |||||
""" | |||||
next_api_url = "https://api.github.com/repositories" | |||||
api_request_interval = 5e3 / 60 ** 2 | |||||
while next_api_url and self.run_event.is_set(): | |||||
start_time = time.time() | |||||
try: | |||||
resp = requests.get(next_api_url, params=self.AUTHENTICATION) | |||||
except requests.ConnectionError: | |||||
self._logger.exception("API %s call failed:" % next_api_url) | |||||
time.sleep(0.5) | |||||
continue | |||||
queue_percent_full = (float(self.clone_queue.qsize()) / | |||||
self.clone_queue.maxsize) * 100 | |||||
self._logger.info("API call made. Queue size: %d/%d, %d%%." % | |||||
((self.clone_queue.qsize(), self.clone_queue.maxsize, | |||||
queue_percent_full))) | |||||
repo_names = [repo["full_name"] for repo in resp.json()] | |||||
repo_ranks = self.get_ranks(repo_names) | |||||
for repo in resp.json(): | |||||
while self.clone_queue.full(): | |||||
time.sleep(1) | |||||
self.clone_queue.put(indexer.GitRepository( | |||||
repo["html_url"], repo["full_name"], "GitHub", | |||||
repo_ranks[repo["full_name"]])) | |||||
if int(resp.headers["x-ratelimit-remaining"]) == 0: | |||||
time.sleep(int(resp.headers["x-ratelimit-reset"]) - | |||||
time.time()) | |||||
next_api_url = resp.headers["link"].split(">")[0][1:] | |||||
sleep_time = api_request_interval - (time.time() - start_time) | |||||
if sleep_time > 0: | |||||
time.sleep(sleep_time) | |||||
@classmethod | |||||
def get_ranks(cls, repo_names): | |||||
""" | |||||
Return the ranks for several repositories. | |||||
Queries the GitHub API for the number of stargazers for any given | |||||
repositories, and blocks if the query limit is exceeded. The rank is | |||||
calculated using these numbers. | |||||
:param repo_names: An array of repository names, in | |||||
`username/repository_name` format. | |||||
:type repo_names: str | |||||
:return: A dictionary mapping repository names to ranks. | |||||
Example dictionary: | |||||
.. code-block:: python | |||||
{ | |||||
"user/repository" : 0.2564949357461537 | |||||
} | |||||
:rtype: dictionary | |||||
""" | |||||
API_URL = "https://api.github.com/search/repositories" | |||||
REPOS_PER_QUERY = 25 | |||||
repo_ranks = {} | |||||
for names in [repo_names[ind:ind + REPOS_PER_QUERY] for ind in | |||||
xrange(0, len(repo_names), REPOS_PER_QUERY)]: | |||||
query_url = "%s?q=%s" % (API_URL, | |||||
"+".join("repo:%s" % name for name in names)) | |||||
params = cls.AUTHENTICATION | |||||
resp = requests.get(query_url, | |||||
params=params, | |||||
headers={ | |||||
"Accept" : "application/vnd.github.preview" | |||||
}) | |||||
if int(resp.headers["x-ratelimit-remaining"]) == 0: | |||||
sleep_time = int(resp.headers["x-ratelimit-reset"]) - \ | |||||
time.time() + 1 | |||||
if sleep_time > 0: | |||||
logging.info("API quota exceeded. Sleep time: %d." % | |||||
sleep_time) | |||||
time.sleep(sleep_time) | |||||
for repo in resp.json()["items"]: | |||||
stars = repo["stargazers_count"] | |||||
rank = min(math.log(max(stars, 1), 5000), 1.0) | |||||
repo_ranks[repo["full_name"]] = rank | |||||
for name in repo_names: | |||||
if name not in repo_ranks: | |||||
repo_ranks[name] = 0.1 | |||||
return repo_ranks | |||||
class BitbucketCrawler(threading.Thread): | |||||
""" | |||||
Crawler that retrieves links to all of Bitbucket's public repositories. | |||||
BitbucketCrawler is a threaded singleton that queries Bitbucket's API for | |||||
urls to its public repositories, and inserts them as | |||||
:class:`indexer.GitRepository` into a :class:`Queue.Queue` shared with | |||||
:class:`indexer.GitIndexer`. | |||||
:ivar clone_queue: (:class:`Queue.Queue`) The shared queue to insert | |||||
:class:`indexer.GitRepository` repository urls into. | |||||
:ivar _logger: (:class:`logging.Logger`) A class-specific logger object. | |||||
""" | |||||
def __init__(self, clone_queue, run_event): | |||||
""" | |||||
Create an instance of the singleton `BitbucketCrawler`. | |||||
:param clone_queue: see :attr:`self.clone_queue` | |||||
:type clone_queue: see :attr:`self.clone_queue` | |||||
""" | |||||
self.clone_queue = clone_queue | |||||
self.run_event = run_event | |||||
self._logger = logging.getLogger("%s.%s" % | |||||
(__name__, self.__class__.__name__)) | |||||
self._logger.info("Starting.") | |||||
super(BitbucketCrawler, self).__init__(name=self.__class__.__name__) | |||||
def run(self): | |||||
""" | |||||
Query the Bitbucket API for data about every public repository. | |||||
Query the Bitbucket API's "/repositories" endpoint and read its | |||||
paginated responses in a loop; any "git" repositories have their | |||||
clone-urls and names inserted into a :class:`indexer.GitRepository` in | |||||
:attr:`self.clone_queue`. | |||||
""" | |||||
next_api_url = "https://api.bitbucket.org/2.0/repositories" | |||||
while self.run_event.is_set(): | |||||
try: | |||||
response = requests.get(next_api_url).json() | |||||
except requests.ConnectionError: | |||||
self._logger.exception("API %s call failed:", next_api_url) | |||||
time.sleep(0.5) | |||||
continue | |||||
queue_percent_full = (float(self.clone_queue.qsize()) / | |||||
self.clone_queue.maxsize) * 100 | |||||
self._logger.info("API call made. Queue size: %d/%d, %d%%." % | |||||
((self.clone_queue.qsize(), self.clone_queue.maxsize, | |||||
queue_percent_full))) | |||||
for repo in response["values"]: | |||||
if repo["scm"] == "git": | |||||
while self.clone_queue.full(): | |||||
time.sleep(1) | |||||
clone_links = repo["links"]["clone"] | |||||
clone_url = (clone_links[0]["href"] if | |||||
clone_links[0]["name"] == "https" else | |||||
clone_links[1]["href"]) | |||||
try: | |||||
watchers = requests.get( | |||||
repo["links"]["watchers"]["href"]) | |||||
num = len(watchers.json()["values"]) | |||||
rank = min(math.log(max(num, 1), 500), 1.0) | |||||
except requests.ConnectionError: | |||||
err = "API %s call failed:" % next_api_url | |||||
self._logger.exception(err) | |||||
time.sleep(0.5) | |||||
continue | |||||
self.clone_queue.put(indexer.GitRepository( | |||||
clone_url, repo["full_name"], "Bitbucket"), rank) | |||||
next_api_url = response["next"] | |||||
time.sleep(0.2) |
@@ -0,0 +1,348 @@ | |||||
""" | |||||
:synopsis: Contains a singleton GitIndexer class, which clones and indexes git | |||||
repositories. | |||||
""" | |||||
from datetime import datetime | |||||
import logging | |||||
import os | |||||
import Queue | |||||
import shutil | |||||
import string | |||||
import time | |||||
import threading | |||||
from bs4 import UnicodeDammit | |||||
import git | |||||
from ..database import Database | |||||
from ..parser import parse, UnsupportedFileError | |||||
from ..codelet import Codelet | |||||
GIT_CLONE_DIR = "/tmp/bitshift" | |||||
THREAD_QUEUE_SLEEP = 0.5 | |||||
MAX_INDEX_QUEUE_SIZE = 10 | |||||
class GitRepository(object): | |||||
""" | |||||
A representation of a Git repository's metadata. | |||||
:ivar url: (str) The repository's url. | |||||
:ivar name: (str) The name of the repository. | |||||
:ivar framework_name: (str) The name of the online Git framework that the | |||||
repository belongs to (eg, GitHub, BitBucket). | |||||
:ivar rank: (float) The rank of the repository, as assigned by | |||||
:class:`crawler.GitHubCrawler`. | |||||
:ivar path: (str) The repository's on-disk directory path. | |||||
:ivar repo: (git.Repo) A git.Repo representation of the repository. | |||||
""" | |||||
def __init__(self, url, name, framework_name, rank): | |||||
""" | |||||
Create a GitRepository instance. | |||||
:param url: see :attr:`GitRepository.url` | |||||
:param name: see :attr:`GitRepository.name` | |||||
:param framework_name: see :attr:`GitRepository.framework_name` | |||||
:param rank: see :attr:`GitRepository.rank` | |||||
:type url: str | |||||
:type name: str | |||||
:type framework_name: str | |||||
:type rank: float | |||||
""" | |||||
self.url = url | |||||
self.name = name | |||||
self.framework_name = framework_name | |||||
self.rank = rank | |||||
dirname = name.replace("/", "-") + "-" + str(int(time.time())) | |||||
self.path = os.path.join(GIT_CLONE_DIR, dirname) | |||||
self.repo = None | |||||
class GitIndexer(threading.Thread): | |||||
""" | |||||
A singleton Git repository indexer. | |||||
:class:`GitIndexer` indexes the repositories cloned by the | |||||
:class:`_GitCloner` singleton. | |||||
:ivar index_queue: (:class:`Queue.Queue`) A queue containing | |||||
:class:`GitRepository` objects for every new repository succesfully | |||||
cloned by :class:`_GitCloner`, which are to be indexed. | |||||
:ivar git_cloner: (:class:`_GitCloner`) The corresponding repository | |||||
cloner, which feeds :class:`GitIndexer`. | |||||
:ivar _logger: (:class:`logging.Logger`) A class-specific logger object. | |||||
""" | |||||
def __init__(self, clone_queue, run_event): | |||||
""" | |||||
Create an instance of the singleton `GitIndexer`. | |||||
:param clone_queue: see :attr:`self.index_queue` | |||||
:type index_queue: see :attr:`self.index_queue` | |||||
""" | |||||
self.index_queue = Queue.Queue(maxsize=MAX_INDEX_QUEUE_SIZE) | |||||
self.run_event = run_event | |||||
self.git_cloner = _GitCloner(clone_queue, self.index_queue, run_event) | |||||
self.git_cloner.start() | |||||
self.database = Database() | |||||
self._logger = logging.getLogger("%s.%s" % | |||||
(__name__, self.__class__.__name__)) | |||||
self._logger.info("Starting.") | |||||
if not os.path.exists(GIT_CLONE_DIR): | |||||
os.makedirs(GIT_CLONE_DIR) | |||||
super(GitIndexer, self).__init__(name=self.__class__.__name__) | |||||
def run(self): | |||||
""" | |||||
Retrieve metadata about newly cloned repositories and index them. | |||||
Blocks until new repositories appear in :attr:`self.index_queue`, then | |||||
retrieves one, and attempts indexing it. Should any errors occur, the | |||||
new repository will be discarded and the indexer will index the next in | |||||
the queue. | |||||
""" | |||||
while True: | |||||
while self.index_queue.empty() and self.run_event.is_set(): | |||||
time.sleep(THREAD_QUEUE_SLEEP) | |||||
if not self.run_event.is_set(): | |||||
break | |||||
repo = self.index_queue.get() | |||||
self.index_queue.task_done() | |||||
self._index_repository(repo) | |||||
def _index_repository(self, repo): | |||||
""" | |||||
Clone and index (create and insert Codeletes for) a Git repository. | |||||
`git clone` the Git repository located at **repo.url**, call | |||||
`_insert_repository_codelets()`, then remove said repository. | |||||
:param repo: The metadata of the repository to be indexed. | |||||
:type repo: :class:`GitRepository` | |||||
""" | |||||
self._logger.info(u"Indexing repo: %s", repo.name) | |||||
try: | |||||
self._insert_repository_codelets(repo) | |||||
except Exception: | |||||
self._logger.exception("Exception raised while indexing:") | |||||
finally: | |||||
if os.path.isdir(repo.path): | |||||
shutil.rmtree(repo.path) | |||||
def _insert_repository_codelets(self, repo): | |||||
""" | |||||
Create and insert a Codelet for the files inside a Git repository. | |||||
Create a new Codelet, and insert it into the Database singleton, for | |||||
every file inside the current working directory's default branch | |||||
(usually *master*). | |||||
:param repo_url: The metadata of the repository to be indexed. | |||||
:type repo_url: :class:`GitRepository` | |||||
""" | |||||
file_meta = self._get_file_metadata(repo.repo) | |||||
if file_meta is None: | |||||
return | |||||
for filename, data in file_meta.iteritems(): | |||||
name = ("%s: %s" % (repo.name, filename)).encode("utf8") | |||||
authors = [(author, None) for author in data["authors"]] | |||||
encoded_source = data["blob"].data_stream.read() | |||||
source = UnicodeDammit(encoded_source).unicode_markup | |||||
url = self._generate_file_url(filename, repo) | |||||
codelet = Codelet(name, source, filename, None, authors, url, | |||||
data["time_created"], data["time_last_modified"], | |||||
repo.rank) | |||||
self._logger.debug("Indexing file: %s", codelet.name) | |||||
try: | |||||
parse(codelet) | |||||
except UnsupportedFileError: | |||||
continue | |||||
except Exception: | |||||
self._logger.exception("Exception raised while parsing:") | |||||
self.database.insert(codelet) | |||||
def _generate_file_url(self, filename, repo): | |||||
""" | |||||
Return a url for a filename from a Git wrapper framework. | |||||
:param filename: The path of the file. | |||||
:param repo: The git repo. | |||||
:type filename: str | |||||
:type repo: :class:`GitRepository` | |||||
:return: The file's full url on the given framework, if successfully | |||||
derived. | |||||
:rtype: str, or None | |||||
""" | |||||
if repo.framework_name == "GitHub": | |||||
default_branch = repo.repo.active_branch.name | |||||
parts = [repo.url, "blob", default_branch, filename] | |||||
elif repo.framework_name == "Bitbucket": | |||||
try: | |||||
commit_hash = repo.repo.head.commit.hexsha | |||||
except ValueError: # No commits | |||||
return None | |||||
parts = [repo.url, "src", commit_hash, filename] | |||||
return "/".join(s.strip("/") for s in parts) | |||||
def _get_file_metadata(self, repo): | |||||
""" | |||||
Return a dictionary containing every valuable tracked file's metadata. | |||||
:return: A dictionary with author names, time of creation, and time of | |||||
last modification for every filename key. | |||||
.. code-block:: python | |||||
sample_returned_dict = { | |||||
"my_file" : { | |||||
"blob": (GitPython Blob) <object>, | |||||
"authors" : (str list) ["author1", "author2"], | |||||
"time_created" : (`datetime.datetime`) <object>, | |||||
"time_last_modified" : (`datetime.datetime`) <object> | |||||
} | |||||
} | |||||
:rtype: dictionary of dictionaries | |||||
""" | |||||
try: | |||||
tree = repo.head.commit.tree | |||||
except ValueError: # No commits | |||||
return {} | |||||
files = {} | |||||
self._logger.debug("Building file metadata") | |||||
for item in tree.traverse(): | |||||
if item.type != "blob" or not self._is_ascii(item.data_stream): | |||||
continue | |||||
log = repo.git.log("--follow", '--format=%an %ct', "--", item.path) | |||||
lines = log.splitlines() | |||||
authors = {line.rsplit(" ", 1)[0].decode("utf8") for line in lines} | |||||
last_mod = int(lines[0].rsplit(" ", 1)[1]) | |||||
created = int(lines[-1].rsplit(" ", 1)[1]) | |||||
files[item.path] = { | |||||
"blob": item, | |||||
"authors" : authors, | |||||
"time_last_modified": datetime.fromtimestamp(last_mod), | |||||
"time_created": datetime.fromtimestamp(created) | |||||
} | |||||
return files | |||||
def _is_ascii(self, source): | |||||
""" | |||||
Heuristically determine whether a file is ASCII text or binary. | |||||
If a portion of the file contains null bytes, or the percentage of bytes | |||||
that aren't ASCII is greater than 30%, then the file is concluded to be | |||||
binary. This heuristic is used by the `file` utility, Perl's inbuilt `-T` | |||||
operator, and is the de-facto method for in : passdetermining whether a | |||||
file is ASCII. | |||||
:param source: The file object to test. | |||||
:type source: `file` | |||||
:return: Whether the file is probably ASCII. | |||||
:rtype: Boolean | |||||
""" | |||||
file_snippet = source.read(512) | |||||
if not file_snippet: | |||||
return True | |||||
ascii_characters = "".join(map(chr, range(32, 127)) + | |||||
list("\n\r\t\b")) | |||||
null_trans = string.maketrans("", "") | |||||
if "\0" in file_snippet: | |||||
return False | |||||
non_ascii = file_snippet.translate(null_trans, ascii_characters) | |||||
return not float(len(non_ascii)) / len(file_snippet) > 0.30 | |||||
class _GitCloner(threading.Thread): | |||||
""" | |||||
A singleton Git repository cloner. | |||||
Clones the repositories crawled by :class:`crawler.GitHubCrawler` for | |||||
:class:`GitIndexer` to index. | |||||
:ivar clone_queue: (:class:`Queue.Queue`) see | |||||
:attr:`crawler.GitHubCrawler.clone_queue`. | |||||
:ivar index_queue: (:class:`Queue.Queue`) see | |||||
:attr:`GitIndexer.index_queue`. | |||||
:ivar _logger: (:class:`logging.Logger`) A class-specific logger object. | |||||
""" | |||||
def __init__(self, clone_queue, index_queue, run_event): | |||||
""" | |||||
Create an instance of the singleton :class:`_GitCloner`. | |||||
:param clone_queue: see :attr:`self.clone_queue` | |||||
:param index_queue: see :attr:`self.index_queue` | |||||
:type clone_queue: see :attr:`self.clone_queue` | |||||
:type index_queue: see :attr:`self.index_queue` | |||||
""" | |||||
self.clone_queue = clone_queue | |||||
self.index_queue = index_queue | |||||
self.run_event = run_event | |||||
self._logger = logging.getLogger("%s.%s" % | |||||
(__name__, self.__class__.__name__)) | |||||
self._logger.info("Starting.") | |||||
super(_GitCloner, self).__init__(name=self.__class__.__name__) | |||||
def run(self): | |||||
""" | |||||
Retrieve metadata about newly crawled repositories and clone them. | |||||
Blocks until new :class:`GitRepository` appear in | |||||
:attr:`self.clone_queue`, then attempts cloning them. If | |||||
succcessful, the cloned repository is added to :attr:`self.index_queue` | |||||
for the `GitIndexer` to clone; otherwise, it is discarded. | |||||
""" | |||||
while True: | |||||
while self.clone_queue.empty() and self.run_event.is_set(): | |||||
time.sleep(THREAD_QUEUE_SLEEP) | |||||
if not self.run_event.is_set(): | |||||
break | |||||
repo = self.clone_queue.get() | |||||
self.clone_queue.task_done() | |||||
try: | |||||
self._clone_repository(repo) | |||||
except Exception: | |||||
self._logger.exception("Exception raised while cloning:") | |||||
def _clone_repository(self, repo): | |||||
""" | |||||
Attempt cloning a Git repository. | |||||
:param repo: Metadata about the repository to clone. | |||||
:type repo: :class:`GitRepository` | |||||
""" | |||||
self._logger.info("Cloning repo: %s", repo.url) | |||||
repo.repo = git.Repo.clone_from(repo.url, to_path=repo.path, bare=True, | |||||
single_branch=True) | |||||
while self.index_queue.full() and self.run_event.is_set(): | |||||
time.sleep(THREAD_QUEUE_SLEEP) | |||||
if self.run_event.is_set(): | |||||
self.index_queue.put(repo) |
@@ -0,0 +1,239 @@ | |||||
""" | |||||
Subpackage with classes and functions to handle communication with the MySQL | |||||
database backend, which manages the search index. | |||||
""" | |||||
import codecs | |||||
import os | |||||
import mmh3 | |||||
import oursql | |||||
from .migration import VERSION, MIGRATIONS | |||||
from ..codelet import Codelet | |||||
from ..query.nodes import (String, Regex, Text, Language, Author, Date, Symbol, | |||||
BinaryOp, UnaryOp) | |||||
__all__ = ["Database"] | |||||
class Database(object): | |||||
"""Represents the MySQL database.""" | |||||
def __init__(self, migrate=False): | |||||
self._conn = self._connect() | |||||
self._check_version(migrate) | |||||
def _connect(self): | |||||
"""Establish a connection to the database.""" | |||||
try: | |||||
codecs.lookup("utf8mb4") | |||||
except LookupError: | |||||
utf8 = codecs.lookup("utf8") | |||||
codecs.register(lambda name: utf8 if name == "utf8mb4" else None) | |||||
root = os.path.dirname(os.path.abspath(__file__)) | |||||
default_file = os.path.join(root, ".my.cnf") | |||||
return oursql.connect( | |||||
db="bitshift", read_default_file=default_file, autoping=True, | |||||
autoreconnect=True, charset="utf8mb4") | |||||
def _migrate(self, cursor, current): | |||||
"""Migrate the database to the latest schema version.""" | |||||
for version in xrange(current, VERSION): | |||||
print "Migrating to %d..." % (version + 1) | |||||
for query in MIGRATIONS[version - 1]: | |||||
cursor.execute(query) | |||||
cursor.execute("UPDATE version SET version = ?", (version + 1,)) | |||||
def _check_version(self, migrate): | |||||
"""Check the database schema version and respond accordingly. | |||||
If the schema is out of date, migrate if *migrate* is True, else raise | |||||
an exception. | |||||
""" | |||||
with self._conn.cursor() as cursor: | |||||
cursor.execute("SELECT version FROM version") | |||||
version = cursor.fetchone()[0] | |||||
if version < VERSION: | |||||
if migrate: | |||||
self._migrate(cursor, version) | |||||
else: | |||||
err = "Database schema out of date. " \ | |||||
"Run `python -m bitshift.database.migration`." | |||||
raise RuntimeError(err) | |||||
def _search_with_query(self, cursor, tree, page): | |||||
"""Execute an SQL query based on a query tree, and return results. | |||||
The returned data is a 2-tuple of (list of codelet IDs, estimated | |||||
number of total results). | |||||
""" | |||||
query, args = tree.build_query(page) | |||||
cursor.execute(query, args) | |||||
ids = [cid for cid, _ in cursor.fetchall()] | |||||
num_results = len(ids) # TODO: This is not entirely correct | |||||
return ids, num_results | |||||
def _get_authors_for_codelet(self, cursor, codelet_id): | |||||
"""Return a list of authors for a given codelet.""" | |||||
query = """SELECT author_name, author_url | |||||
FROM authors | |||||
WHERE author_codelet = ?""" | |||||
cursor.execute(query, (codelet_id,)) | |||||
return cursor.fetchall() | |||||
def _get_symbols_for_code(self, cursor, code_id, tree): | |||||
"""Return a list of symbols for a given codelet.""" | |||||
query = """SELECT symbol_type, symbol_name, sloc_type, sloc_row, | |||||
sloc_col, sloc_end_row, sloc_end_col | |||||
FROM symbols | |||||
INNER JOIN symbol_locations ON sloc_symbol = symbol_id | |||||
WHERE symbol_code = ? AND (%s)""" | |||||
conds, args = [], [code_id] | |||||
for node in tree.walk(Symbol): | |||||
node_cond, node_args, _, _ = node.parameterize(set()) | |||||
conds.append(node_cond) | |||||
args += node_args | |||||
if not conds: | |||||
return {} | |||||
cond = " OR ".join(conds) | |||||
symbols = {type_: {} for type_ in Symbol.TYPES} | |||||
cursor.execute(query % cond, tuple(args)) | |||||
for type_, name, loc_type, row, col, erow, ecol in cursor.fetchall(): | |||||
sdict = symbols[Symbol.TYPES[type_]] | |||||
if name not in sdict: | |||||
sdict[name] = ([], []) | |||||
sdict[name][loc_type].append((row, col, erow, ecol)) | |||||
for type_, sdict in symbols.items(): | |||||
symbols[type_] = [(n, d, u) for n, (d, u) in sdict.iteritems()] | |||||
return symbols | |||||
def _get_codelets_from_ids(self, cursor, ids, tree): | |||||
"""Return a list of Codelet objects given a list of codelet IDs.""" | |||||
query = """SELECT * | |||||
FROM codelets | |||||
INNER JOIN code ON codelet_code_id = code_id | |||||
INNER JOIN origins ON codelet_origin = origin_id | |||||
WHERE codelet_id = ?""" | |||||
with self._conn.cursor(oursql.DictCursor) as dict_cursor: | |||||
for codelet_id in ids: | |||||
dict_cursor.execute(query, (codelet_id,)) | |||||
row = dict_cursor.fetchall()[0] | |||||
code_id = row["code_id"] | |||||
if row["origin_url_base"]: | |||||
url = row["origin_url_base"] + row["codelet_url"] | |||||
else: | |||||
url = row["codelet_url"] | |||||
origin = (row["origin_name"], row["origin_url"]) | |||||
authors = self._get_authors_for_codelet(cursor, codelet_id) | |||||
symbols = self._get_symbols_for_code(cursor, code_id, tree) | |||||
yield Codelet( | |||||
row["codelet_name"], row["code_code"], None, | |||||
row["code_lang"], authors, url, | |||||
row["codelet_date_created"], row["codelet_date_modified"], | |||||
row["codelet_rank"], symbols, origin) | |||||
def _decompose_url(self, cursor, url): | |||||
"""Break up a URL into an origin (with a URL base) and a suffix.""" | |||||
query = """SELECT origin_id, SUBSTR(?, LENGTH(origin_url_base) + 1) | |||||
FROM origins | |||||
WHERE origin_url_base IS NOT NULL | |||||
AND ? LIKE CONCAT(origin_url_base, "%")""" | |||||
cursor.execute(query, (url, url)) | |||||
result = cursor.fetchone() | |||||
return result if result else (1, url) | |||||
def _insert_symbols(self, cursor, code_id, sym_type, symbols): | |||||
"""Insert a list of symbols of a given type into the database.""" | |||||
query1 = "INSERT INTO symbols VALUES (DEFAULT, ?, ?, ?)" | |||||
query2 = """INSERT INTO symbol_locations VALUES | |||||
(DEFAULT, ?, ?, ?, ?, ?, ?)""" | |||||
build = lambda id, L, typ: [tuple([id, typ] + list(loc)) for loc in L] | |||||
type_id = Symbol.TYPES.index(sym_type) | |||||
for (name, defs, uses) in symbols: | |||||
cursor.execute(query1, (code_id, type_id, name)) | |||||
sym_id = cursor.lastrowid | |||||
params = (build(sym_id, defs, Symbol.DEFINE) + | |||||
build(sym_id, uses, Symbol.USE)) | |||||
cursor.executemany(query2, params) | |||||
def close(self): | |||||
"""Disconnect from the database.""" | |||||
self._conn.close() | |||||
def search(self, tree, page=1): | |||||
""" | |||||
Search the database for a query and return the *n*\ th page of results. | |||||
:param tree: The query to search for. | |||||
:type tree: :py:class:`~.query.tree.Tree` | |||||
:param page: The result page to display. | |||||
:type page: int | |||||
:return: The total number of results, and the *n*\ th page of results. | |||||
:rtype: 2-tuple of (long, list of :py:class:`.Codelet`\ s) | |||||
""" | |||||
query1 = "SELECT 1 FROM cache WHERE cache_id = ?" | |||||
query2 = """SELECT cdata_codelet, cache_count_mnt, cache_count_exp | |||||
FROM cache | |||||
INNER JOIN cache_data ON cache_id = cdata_cache | |||||
WHERE cache_id = ? | |||||
ORDER BY cdata_index ASC""" | |||||
query3 = "INSERT INTO cache VALUES (?, ?, ?, DEFAULT)" | |||||
query4 = "INSERT INTO cache_data VALUES (?, ?, ?)" | |||||
cache_id = mmh3.hash64(str(page) + ":" + tree.serialize())[0] | |||||
with self._conn.cursor() as cursor: | |||||
cursor.execute(query1, (cache_id,)) | |||||
cache_hit = cursor.fetchall() | |||||
if cache_hit: | |||||
cursor.execute(query2, (cache_id,)) | |||||
rows = cursor.fetchall() | |||||
num_results = rows[0][1] * (10 ** rows[0][2]) if rows else 0 | |||||
ids = [row[0] for row in rows] | |||||
else: | |||||
ids, num_results = self._search_with_query(cursor, tree, page) | |||||
num_exp = max(len(str(num_results)) - 3, 0) | |||||
num_results = int(round(num_results, -num_exp)) | |||||
num_mnt = num_results / (10 ** num_exp) | |||||
cursor.execute(query3, (cache_id, num_mnt, num_exp)) | |||||
cdata = [(cache_id, c_id, i) for i, c_id in enumerate(ids)] | |||||
cursor.executemany(query4, cdata) | |||||
codelet_gen = self._get_codelets_from_ids(cursor, ids, tree) | |||||
return (num_results, list(codelet_gen)) | |||||
def insert(self, codelet): | |||||
""" | |||||
Insert a codelet into the database. | |||||
:param codelet: The codelet to insert. | |||||
:type codelet: :py:class:`.Codelet` | |||||
""" | |||||
query1 = """INSERT INTO code VALUES (?, ?, ?) | |||||
ON DUPLICATE KEY UPDATE code_id=code_id""" | |||||
query2 = """INSERT INTO codelets VALUES | |||||
(DEFAULT, ?, ?, ?, ?, ?, ?, ?)""" | |||||
query3 = "INSERT INTO authors VALUES (DEFAULT, ?, ?, ?)" | |||||
hash_key = str(codelet.language) + ":" + codelet.code.encode("utf8") | |||||
code_id = mmh3.hash64(hash_key)[0] | |||||
with self._conn.cursor() as cursor: | |||||
cursor.execute(query1, (code_id, codelet.language, codelet.code)) | |||||
if cursor.rowcount == 1: | |||||
for sym_type, symbols in codelet.symbols.iteritems(): | |||||
self._insert_symbols(cursor, code_id, sym_type, symbols) | |||||
origin, url = self._decompose_url(cursor, codelet.url) | |||||
cursor.execute(query2, (codelet.name, code_id, origin, url, | |||||
codelet.rank, codelet.date_created, | |||||
codelet.date_modified)) | |||||
codelet_id = cursor.lastrowid | |||||
authors = [(codelet_id, a[0], a[1]) for a in codelet.authors] | |||||
cursor.executemany(query3, authors) |
@@ -0,0 +1,147 @@ | |||||
""" | |||||
Contains information about database schema versions, and SQL queries to update | |||||
between them. | |||||
""" | |||||
VERSION = 12 | |||||
MIGRATIONS = [ | |||||
# 1 -> 2 | |||||
[ | |||||
"""ALTER TABLE `codelets` | |||||
DROP FOREIGN KEY `codelets_ibfk_1`""", | |||||
"""ALTER TABLE `code` | |||||
DROP KEY `code_hash`, | |||||
DROP COLUMN `code_hash`, | |||||
MODIFY COLUMN `code_id` BIGINT NOT NULL""", | |||||
"""ALTER TABLE `codelets` | |||||
MODIFY COLUMN `codelet_code_id` BIGINT NOT NULL, | |||||
ADD KEY (`codelet_lang`), | |||||
ADD CONSTRAINT `codelets_ibfk_1` FOREIGN KEY (`codelet_code_id`) | |||||
REFERENCES `code` (`code_id`) | |||||
ON DELETE RESTRICT ON UPDATE CASCADE""", | |||||
"""ALTER TABLE `symbols` | |||||
ADD COLUMN `symbol_end_row` INT UNSIGNED NOT NULL, | |||||
ADD COLUMN `symbol_end_col` INT UNSIGNED NOT NULL""" | |||||
], | |||||
# 2 -> 3 | |||||
[ | |||||
"""ALTER TABLE `symbols` | |||||
DROP FOREIGN KEY `symbols_ibfk_1`, | |||||
CHANGE COLUMN `symbol_codelet` `symbol_code` BIGINT NOT NULL, | |||||
ADD CONSTRAINT `symbols_ibfk_1` FOREIGN KEY (`symbol_code`) | |||||
REFERENCES `code` (`code_id`) | |||||
ON DELETE CASCADE ON UPDATE CASCADE""" | |||||
], | |||||
# 3 -> 4 | |||||
[ | |||||
"""ALTER TABLE `symbols` | |||||
DROP COLUMN `symbol_row`, | |||||
DROP COLUMN `symbol_col`, | |||||
DROP COLUMN `symbol_end_row`, | |||||
DROP COLUMN `symbol_end_col`""", | |||||
"""CREATE TABLE `symbol_locations` ( | |||||
`sloc_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |||||
`sloc_symbol` BIGINT UNSIGNED NOT NULL, | |||||
`sloc_type` TINYINT UNSIGNED NOT NULL, | |||||
`sloc_row` INT UNSIGNED NOT NULL, | |||||
`sloc_col` INT UNSIGNED NOT NULL, | |||||
`sloc_end_row` INT UNSIGNED NOT NULL, | |||||
`sloc_end_col` INT UNSIGNED NOT NULL, | |||||
PRIMARY KEY (`sloc_id`), | |||||
FOREIGN KEY (`sloc_symbol`) | |||||
REFERENCES `symbols` (`symbol_id`) | |||||
ON DELETE CASCADE ON UPDATE CASCADE | |||||
) ENGINE=InnoDB""" | |||||
], | |||||
# 4 -> 5 | |||||
[ | |||||
"""ALTER TABLE `origins` | |||||
MODIFY COLUMN `origin_name` VARCHAR(64) DEFAULT NULL, | |||||
MODIFY COLUMN `origin_url` VARCHAR(512) DEFAULT NULL, | |||||
MODIFY COLUMN `origin_url_base` VARCHAR(512) DEFAULT NULL""" | |||||
], | |||||
# 5 -> 6 | |||||
[ | |||||
"""ALTER TABLE `code` | |||||
ADD COLUMN `code_lang` SMALLINT UNSIGNED DEFAULT NULL | |||||
AFTER `code_id`, | |||||
ADD KEY (`code_lang`)""", | |||||
"""ALTER TABLE `codelets` | |||||
DROP KEY `codelet_lang`, | |||||
DROP COLUMN `codelet_lang`""", | |||||
"""ALTER TABLE `cache_data` | |||||
DROP FOREIGN KEY `cache_data_ibfk_1`""", | |||||
"""ALTER TABLE `cache` | |||||
MODIFY COLUMN `cache_id` BIGINT NOT NULL, | |||||
DROP COLUMN `cache_hash`, | |||||
DROP COLUMN `cache_last_used`, | |||||
MODIFY COLUMN `cache_count_mnt` SMALLINT UNSIGNED NOT NULL""", | |||||
"""ALTER TABLE `cache_data` | |||||
MODIFY COLUMN `cdata_cache` BIGINT NOT NULL, | |||||
ADD PRIMARY KEY (`cdata_cache`, `cdata_codelet`), | |||||
ADD CONSTRAINT `cache_data_ibfk_1` FOREIGN KEY (`cdata_codelet`) | |||||
REFERENCES `codelets` (`codelet_id`) | |||||
ON DELETE CASCADE ON UPDATE CASCADE""", | |||||
"""CREATE EVENT `flush_cache` | |||||
ON SCHEDULE EVERY 1 HOUR | |||||
DO | |||||
DELETE FROM `cache` | |||||
WHERE `cache_created` < DATE_SUB(NOW(), INTERVAL 1 DAY);""" | |||||
], | |||||
# 6 -> 7 | |||||
[ | |||||
"""DELETE FROM `cache`""", | |||||
"""ALTER TABLE `cache_data` | |||||
ADD COLUMN `cdata_index` TINYINT UNSIGNED NOT NULL | |||||
AFTER `cdata_codelet`""" | |||||
], | |||||
# 7 -> 8 | |||||
[ | |||||
"""ALTER TABLE `origins` | |||||
DROP COLUMN `origin_image`""" | |||||
], | |||||
# 8 -> 9 | |||||
[ | |||||
"""DELIMITER // | |||||
CREATE PROCEDURE `empty_database`() | |||||
BEGIN | |||||
DELETE FROM `codelets`; | |||||
DELETE FROM `code`; | |||||
DELETE FROM `cache`; | |||||
ALTER TABLE `codelets` AUTO_INCREMENT = 1; | |||||
ALTER TABLE `authors` AUTO_INCREMENT = 1; | |||||
ALTER TABLE `symbols` AUTO_INCREMENT = 1; | |||||
ALTER TABLE `symbol_locations` AUTO_INCREMENT = 1; | |||||
END// | |||||
DELIMITER ;""" | |||||
], | |||||
# 9 -> 10 | |||||
[ | |||||
"""ALTER TABLE `symbol_locations` | |||||
MODIFY COLUMN `sloc_col` INT UNSIGNED DEFAULT NULL, | |||||
MODIFY COLUMN `sloc_end_row` INT UNSIGNED DEFAULT NULL, | |||||
MODIFY COLUMN `sloc_end_col` INT UNSIGNED DEFAULT NULL""" | |||||
], | |||||
# 10 -> 11 | |||||
[ | |||||
"""ALTER DATABASE `bitshift` | |||||
CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci""" | |||||
], | |||||
# 11 -> 12 | |||||
[ | |||||
"""CREATE TABLE `stopwords` ( | |||||
`value` varchar(18) NOT NULL DEFAULT "" | |||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8""", | |||||
"""INSERT INTO `stopwords` VALUES | |||||
("a"), ("about"), ("an"), ("are"), ("as"), ("at"), ("be"), ("by"), | |||||
("how"), ("i"), ("it"), ("la"), ("of"), ("on"), ("that"), ("the"), | |||||
("to"), ("und"), ("was"), ("what"), ("when"), ("where"), ("who"), | |||||
("will")""" | |||||
] | |||||
] | |||||
if __name__ == "__main__": | |||||
from . import Database | |||||
Database(migrate=True).close() |
@@ -0,0 +1,141 @@ | |||||
-- Schema version 12 | |||||
CREATE DATABASE `bitshift` | |||||
DEFAULT CHARACTER SET utf8mb4 | |||||
COLLATE utf8mb4_unicode_ci; | |||||
USE `bitshift`; | |||||
CREATE TABLE `version` ( | |||||
`version` INT UNSIGNED NOT NULL | |||||
) ENGINE=InnoDB; | |||||
INSERT INTO `version` VALUES (12); | |||||
CREATE TABLE `stopwords` | |||||
LIKE information_schema.innodb_ft_default_stopword | |||||
ENGINE=InnoDB; | |||||
CREATE TABLE `stopwords` ( | |||||
`value` varchar(18) NOT NULL DEFAULT "" | |||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8; | |||||
INSERT INTO `stopwords` VALUES | |||||
("a"), ("about"), ("an"), ("are"), ("as"), ("at"), ("be"), ("by"), ("how"), | |||||
("i"), ("it"), ("la"), ("of"), ("on"), ("that"), ("the"), ("to"), ("und"), | |||||
("was"), ("what"), ("when"), ("where"), ("who"), ("will"); | |||||
CREATE TABLE `origins` ( | |||||
`origin_id` TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, | |||||
`origin_name` VARCHAR(64) DEFAULT NULL, | |||||
`origin_url` VARCHAR(512) DEFAULT NULL, | |||||
`origin_url_base` VARCHAR(512) DEFAULT NULL, | |||||
PRIMARY KEY (`origin_id`) | |||||
) ENGINE=InnoDB; | |||||
INSERT INTO `origins` VALUES (1, NULL, NULL, NULL); | |||||
CREATE TABLE `code` ( | |||||
`code_id` BIGINT NOT NULL, | |||||
`code_lang` SMALLINT UNSIGNED DEFAULT NULL, | |||||
`code_code` MEDIUMTEXT NOT NULL, | |||||
PRIMARY KEY (`code_id`), | |||||
KEY (`code_lang`), | |||||
FULLTEXT KEY (`code_code`) | |||||
) ENGINE=InnoDB; | |||||
CREATE TABLE `codelets` ( | |||||
`codelet_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |||||
`codelet_name` VARCHAR(300) NOT NULL, | |||||
`codelet_code_id` BIGINT NOT NULL, | |||||
`codelet_origin` TINYINT UNSIGNED NOT NULL, | |||||
`codelet_url` VARCHAR(512) NOT NULL, | |||||
`codelet_rank` FLOAT NOT NULL, | |||||
`codelet_date_created` DATETIME DEFAULT NULL, | |||||
`codelet_date_modified` DATETIME DEFAULT NULL, | |||||
PRIMARY KEY (`codelet_id`), | |||||
FULLTEXT KEY (`codelet_name`), | |||||
KEY (`codelet_rank`), | |||||
KEY (`codelet_date_created`), | |||||
KEY (`codelet_date_modified`), | |||||
FOREIGN KEY (`codelet_code_id`) | |||||
REFERENCES `code` (`code_id`) | |||||
ON DELETE RESTRICT ON UPDATE CASCADE, | |||||
FOREIGN KEY (`codelet_origin`) | |||||
REFERENCES `origins` (`origin_id`) | |||||
ON DELETE RESTRICT ON UPDATE CASCADE | |||||
) ENGINE=InnoDB; | |||||
CREATE TABLE `authors` ( | |||||
`author_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |||||
`author_codelet` BIGINT UNSIGNED NOT NULL, | |||||
`author_name` VARCHAR(128) NOT NULL, | |||||
`author_url` VARCHAR(512) DEFAULT NULL, | |||||
PRIMARY KEY (`author_id`), | |||||
FULLTEXT KEY (`author_name`), | |||||
FOREIGN KEY (`author_codelet`) | |||||
REFERENCES `codelets` (`codelet_id`) | |||||
ON DELETE CASCADE ON UPDATE CASCADE | |||||
) ENGINE=InnoDB; | |||||
CREATE TABLE `symbols` ( | |||||
`symbol_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |||||
`symbol_code` BIGINT NOT NULL, | |||||
`symbol_type` TINYINT UNSIGNED NOT NULL, | |||||
`symbol_name` VARCHAR(512) NOT NULL, | |||||
PRIMARY KEY (`symbol_id`), | |||||
KEY (`symbol_type`, `symbol_name`(32)), | |||||
FOREIGN KEY (`symbol_code`) | |||||
REFERENCES `code` (`code_id`) | |||||
ON DELETE CASCADE ON UPDATE CASCADE | |||||
) ENGINE=InnoDB; | |||||
CREATE TABLE `symbol_locations` ( | |||||
`sloc_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | |||||
`sloc_symbol` BIGINT UNSIGNED NOT NULL, | |||||
`sloc_type` TINYINT UNSIGNED NOT NULL, | |||||
`sloc_row` INT UNSIGNED NOT NULL, | |||||
`sloc_col` INT UNSIGNED DEFAULT NULL, | |||||
`sloc_end_row` INT UNSIGNED DEFAULT NULL, | |||||
`sloc_end_col` INT UNSIGNED DEFAULT NULL, | |||||
PRIMARY KEY (`sloc_id`), | |||||
FOREIGN KEY (`sloc_symbol`) | |||||
REFERENCES `symbols` (`symbol_id`) | |||||
ON DELETE CASCADE ON UPDATE CASCADE | |||||
) ENGINE=InnoDB; | |||||
CREATE TABLE `cache` ( | |||||
`cache_id` BIGINT NOT NULL, | |||||
`cache_count_mnt` SMALLINT UNSIGNED NOT NULL, | |||||
`cache_count_exp` TINYINT UNSIGNED NOT NULL, | |||||
`cache_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||||
PRIMARY KEY (`cache_id`) | |||||
) ENGINE=InnoDB; | |||||
CREATE TABLE `cache_data` ( | |||||
`cdata_cache` BIGINT NOT NULL, | |||||
`cdata_codelet` BIGINT UNSIGNED NOT NULL, | |||||
`cdata_index` TINYINT UNSIGNED NOT NULL, | |||||
PRIMARY KEY (`cdata_cache`, `cdata_codelet`), | |||||
FOREIGN KEY (`cdata_cache`) | |||||
REFERENCES `cache` (`cache_id`) | |||||
ON DELETE CASCADE ON UPDATE CASCADE, | |||||
FOREIGN KEY (`cdata_codelet`) | |||||
REFERENCES `codelets` (`codelet_id`) | |||||
ON DELETE CASCADE ON UPDATE CASCADE | |||||
) ENGINE=InnoDB; | |||||
DELIMITER // | |||||
CREATE PROCEDURE `empty_database`() | |||||
BEGIN | |||||
DELETE FROM `codelets`; | |||||
DELETE FROM `code`; | |||||
DELETE FROM `cache`; | |||||
ALTER TABLE `codelets` AUTO_INCREMENT = 1; | |||||
ALTER TABLE `authors` AUTO_INCREMENT = 1; | |||||
ALTER TABLE `symbols` AUTO_INCREMENT = 1; | |||||
ALTER TABLE `symbol_locations` AUTO_INCREMENT = 1; | |||||
END// | |||||
DELIMITER ; | |||||
CREATE EVENT `flush_cache` | |||||
ON SCHEDULE EVERY 1 HOUR | |||||
DO | |||||
DELETE FROM `cache` | |||||
WHERE `cache_created` < DATE_SUB(NOW(), INTERVAL 1 DAY); |
@@ -0,0 +1,22 @@ | |||||
from os import path | |||||
import yaml | |||||
__all__ = ["LANGS", "LANGS_ALL"] | |||||
def _load_langs(): | |||||
filename = path.join(path.dirname(__file__), "languages.yml") | |||||
with open(filename) as fp: | |||||
data = yaml.load(fp)["languages"] | |||||
langs = [(it.keys()[0] if isinstance(it, dict) else it).encode("utf8") | |||||
for it in data] | |||||
all_langs = {} | |||||
for i, lang in enumerate(data): | |||||
if isinstance(lang, dict): | |||||
for val in lang.values()[0]: | |||||
all_langs[val] = i | |||||
else: | |||||
all_langs[lang] = i | |||||
return langs, all_langs | |||||
LANGS, LANGS_ALL = _load_langs() |
@@ -0,0 +1,368 @@ | |||||
# A list of programming languages supported by bitshift: | |||||
languages: | |||||
# With parsers: | |||||
- Python: | |||||
- Python | |||||
- Python 3 | |||||
- Python 3.0 Traceback | |||||
- Python console session | |||||
- Python Traceback | |||||
- NumPy | |||||
- C | |||||
- Java | |||||
- Ruby: | |||||
- Ruby | |||||
- Ruby irb session | |||||
# Without parsers: | |||||
- ABAP | |||||
- APL | |||||
- ActionScript: | |||||
- ActionScript | |||||
- ActionScript 3 | |||||
- ANTLR: | |||||
- ANTLR | |||||
- ANTLR With ActionScript Target | |||||
- ANTLR With CPP Target | |||||
- "ANTLR With C# Target" | |||||
- ANTLR With Java Target | |||||
- ANTLR With ObjectiveC Target | |||||
- ANTLR With Perl Target | |||||
- ANTLR With Python Target | |||||
- ANTLR With Ruby Target | |||||
- Ada | |||||
- Agda: | |||||
- Agda | |||||
- Literate Agda | |||||
- Alloy | |||||
- AmbientTalk | |||||
- ApacheConf | |||||
- AppleScript | |||||
- AspectJ | |||||
- aspx-cs | |||||
- aspx-vb | |||||
- Asymptote | |||||
- autohotkey | |||||
- AutoIt | |||||
- Awk | |||||
- BBCode | |||||
- BUGS | |||||
- Bash: | |||||
- Bash | |||||
- Bash Session | |||||
- Batchfile | |||||
- Befunge | |||||
- BlitzBasic: | |||||
- BlitzBasic | |||||
- BlitzMax | |||||
- Boo | |||||
- Brainfuck | |||||
- Bro | |||||
- "C#" | |||||
- C++ | |||||
- ca65 | |||||
- CBM BASIC V2 | |||||
- Ceylon | |||||
- CFEngine3 | |||||
- cfstatement | |||||
- ChaiScript | |||||
- Chapel | |||||
- Cheetah | |||||
- Cirru | |||||
- Clay | |||||
- Clojure: | |||||
- Clojure | |||||
- ClojureScript | |||||
- CMake | |||||
- COBOL: | |||||
- COBOL | |||||
- COBOLFree | |||||
- CoffeeScript | |||||
- Coldfusion CFC | |||||
- Coldfusion HTML | |||||
- Common Lisp | |||||
- Coq | |||||
- Croc | |||||
- Cryptol: | |||||
- Cryptol | |||||
- Literate Cryptol | |||||
- CSS: | |||||
- CSS | |||||
- CSS+Django/Jinja | |||||
- CSS+Genshi Text | |||||
- CSS+Lasso | |||||
- CSS+Mako | |||||
- CSS+Mako | |||||
- CSS+Myghty | |||||
- CSS+PHP | |||||
- CSS+Ruby | |||||
- CSS+Smarty | |||||
- CUDA | |||||
- Cypher | |||||
- Cython | |||||
- D | |||||
- Darcs Patch | |||||
- Dart | |||||
- Debian Control file | |||||
- Debian Sourcelist | |||||
- Delphi | |||||
- dg | |||||
- Diff | |||||
- Django/Jinja | |||||
- Docker | |||||
- DTD | |||||
- Duel | |||||
- Dylan: | |||||
- Dylan | |||||
- Dylan session | |||||
- DylanLID | |||||
- EBNF | |||||
- eC | |||||
- ECL | |||||
- Eiffel | |||||
- Elixir: | |||||
- Elixir | |||||
- Elixir iex session | |||||
- Embedded Ragel | |||||
- ERB: | |||||
- ERB | |||||
- RHTML | |||||
- Erlang: | |||||
- Erlang | |||||
- Erlang erl session | |||||
- Evoque | |||||
- Factor | |||||
- Fancy | |||||
- Fantom | |||||
- Felix | |||||
- Fortran | |||||
- FoxPro | |||||
- FSharp | |||||
- GAP | |||||
- GAS | |||||
- Genshi | |||||
- Genshi Text | |||||
- Gettext Catalog | |||||
- Gherkin | |||||
- GLSL | |||||
- Gnuplot | |||||
- Go | |||||
- Golo | |||||
- GoodData-CL | |||||
- Gosu | |||||
- Gosu Template | |||||
- Groff | |||||
- Groovy | |||||
- Haml | |||||
- Handlebars | |||||
- Haskell: | |||||
- Haskell | |||||
- Literate Haskell | |||||
- Haxe | |||||
- HTML: | |||||
- HTML | |||||
- HTML+Cheetah | |||||
- HTML+Django/Jinja | |||||
- HTML+Evoque | |||||
- HTML+Genshi | |||||
- HTML+Lasso | |||||
- HTML+Mako | |||||
- HTML+Mako | |||||
- HTML+Myghty | |||||
- HTML+PHP | |||||
- HTML+Smarty | |||||
- HTML+Velocity | |||||
- Hxml | |||||
- Hy | |||||
- Hybris | |||||
- IDL | |||||
- Idris: | |||||
- Idris | |||||
- Literate Idris | |||||
- Igor | |||||
- Inform 6: | |||||
- Inform 6 | |||||
- Inform 6 template | |||||
- Inform 7 | |||||
- INI | |||||
- Io | |||||
- Ioke | |||||
- Jade | |||||
- JAGS | |||||
- Jasmin | |||||
- Java Server Page | |||||
- JavaScript: | |||||
- JavaScript | |||||
- JavaScript+Cheetah | |||||
- JavaScript+Django/Jinja | |||||
- JavaScript+Genshi Text | |||||
- JavaScript+Lasso | |||||
- JavaScript+Mak | |||||
- JavaScript+Mako | |||||
- JavaScript+Myghty | |||||
- JavaScript+PHP | |||||
- JavaScript+Ruby | |||||
- JavaScript+Smarty | |||||
- JSON | |||||
- Julia: | |||||
- Julia | |||||
- Julia console | |||||
- Kal | |||||
- Kconfig | |||||
- Koka | |||||
- Kotlin | |||||
- Lasso | |||||
- Lighttpd configuration file | |||||
- Limbo | |||||
- LiveScript | |||||
- LLVM | |||||
- Logos | |||||
- Logtalk | |||||
- LSL | |||||
- Lua | |||||
- Makefile | |||||
- Makefile | |||||
- Base Makefile | |||||
- Mako | |||||
- MAQL | |||||
- Mask | |||||
- Mason | |||||
- Mathematica | |||||
- Matlab: | |||||
- Matlab | |||||
- Matlab session | |||||
- MiniD | |||||
- Modelica | |||||
- Modula-2 | |||||
- Monkey | |||||
- MOOCode | |||||
- MoonScript | |||||
- MQL | |||||
- Mscgen | |||||
- MuPAD | |||||
- MXML | |||||
- Myghty | |||||
- NASM | |||||
- Nemerle | |||||
- nesC | |||||
- NewLisp | |||||
- Newspeak | |||||
- Nginx configuration file | |||||
- Nimrod | |||||
- Nix | |||||
- NSIS | |||||
- Objective-C | |||||
- Objective-C++ | |||||
- Objective-J | |||||
- OCaml | |||||
- Octave | |||||
- Ooc | |||||
- Opa | |||||
- OpenEdge ABL | |||||
- Pan | |||||
- Pawn | |||||
- Perl: | |||||
- Perl | |||||
- Perl6 | |||||
- PHP | |||||
- Pig | |||||
- Pike | |||||
- PostScript | |||||
- POVRay | |||||
- PowerShell | |||||
- Prolog | |||||
- Properties | |||||
- Protocol Buffer | |||||
- Puppet | |||||
- PyPy Log | |||||
- QBasic | |||||
- QML | |||||
- Racket | |||||
- Ragel: | |||||
- Ragel | |||||
- Ragel in C Host | |||||
- Ragel in CPP Host | |||||
- Ragel in D Host | |||||
- Ragel in Java Host | |||||
- Ragel in Objective C Host | |||||
- Ragel in Ruby Host | |||||
- RConsole | |||||
- Rd | |||||
- REBOL | |||||
- Red | |||||
- Redcode | |||||
- reg | |||||
- reStructuredText | |||||
- Rexx | |||||
- RobotFramework | |||||
- RPMSpec | |||||
- RQL | |||||
- RSL | |||||
- Rust | |||||
- S | |||||
- Sass: | |||||
- Sass | |||||
- SCSS | |||||
- Scala | |||||
- Scalate Server Page | |||||
- Scaml | |||||
- Scheme | |||||
- Scilab | |||||
- Shell Session | |||||
- Slim | |||||
- Smali | |||||
- Smalltalk | |||||
- Smarty | |||||
- Snobol | |||||
- SourcePawn | |||||
- SPARQL | |||||
- SQL: | |||||
- SQL | |||||
- MySQL | |||||
- PL/pgSQL | |||||
- PostgreSQL console (psql) | |||||
- PostgreSQL SQL dialect | |||||
- sqlite3con | |||||
- SquidConf | |||||
- Stan | |||||
- Standard ML | |||||
- SWIG | |||||
- systemverilog | |||||
- Tcl | |||||
- Tcsh | |||||
- Tea | |||||
- TeX | |||||
- Todotxt | |||||
- Treetop | |||||
- TypeScript | |||||
- UrbiScript | |||||
- Vala | |||||
- VB.net | |||||
- VCTreeStatus | |||||
- Velocity | |||||
- verilog | |||||
- VGL | |||||
- vhdl | |||||
- VimL | |||||
- XML: | |||||
- XML | |||||
- XML+Cheetah | |||||
- XML+Django/Jinja | |||||
- XML+Evoque | |||||
- XML+Lasso | |||||
- XML+Mako | |||||
- XML+Mako | |||||
- XML+Myghty | |||||
- XML+PHP | |||||
- XML+Ruby | |||||
- XML+Smarty | |||||
- XML+Velocity | |||||
- XQuery | |||||
- XSLT | |||||
- Xtend | |||||
- YAML: | |||||
- YAML | |||||
- YAML+Jinja | |||||
- Zephir |
@@ -0,0 +1,90 @@ | |||||
import json | |||||
import subprocess | |||||
from os import path | |||||
from pygments import lexers as pgl, util | |||||
from ..languages import LANGS, LANGS_ALL | |||||
from .python import parse_py | |||||
__all__ = ["parse", "UnsupportedFileError", "start_parse_servers"] | |||||
# TODO: Change these | |||||
PARSER_COMMANDS = { | |||||
'Java': ['java', '-cp', | |||||
path.join(path.dirname(__file__), "../../parsers/java/parsing.jar"), | |||||
'com.bitshift.parsing.Parse'], | |||||
'Ruby': ['rake', '-f', | |||||
path.join(path.dirname(__file__), "../../parsers/ruby/Rakefile"), | |||||
'parse'] | |||||
} | |||||
class UnsupportedFileError(Exception): | |||||
pass | |||||
def _lang(codelet): | |||||
""" | |||||
Private function to identify the language of a codelet. | |||||
:param codelet: The codelet object to identified. | |||||
:type code: Codelet | |||||
.. todo:: | |||||
Modify function to incorporate tags from stackoverflow. | |||||
""" | |||||
try: | |||||
if codelet.filename: | |||||
lex = pgl.guess_lexer_for_filename(codelet.filename, codelet.code) | |||||
else: | |||||
lex = pgl.guess_lexer(codelet.code) | |||||
return LANGS_ALL[lex.name] | |||||
except (util.ClassNotFound, KeyError): | |||||
raise UnsupportedFileError(codelet.filename) | |||||
def parse_via_proc(codelet): | |||||
proc = subprocess.Popen(PARSER_COMMANDS[LANGS[codelet.language]], | |||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE) | |||||
data = proc.communicate(codelet.code)[0] | |||||
symbols = json.loads(data) | |||||
return symbols | |||||
PARSERS = { | |||||
"Python": parse_py, | |||||
"Java": parse_via_proc, | |||||
"Ruby": parse_via_proc, | |||||
} | |||||
def parse(codelet): | |||||
""" | |||||
Dispatches the codelet to the correct parser based on its language. | |||||
It is the job of the respective parsers to accumulate data about the | |||||
code and to convert it into a string representing a python dict. | |||||
The codelet is then given dict as its 'symbols' field. | |||||
:param codelet: The codelet object to parsed. | |||||
:type code: Codelet | |||||
""" | |||||
lang = _lang(codelet) | |||||
lang_string = LANGS[lang] | |||||
codelet.language = lang | |||||
def loc_helper(l): | |||||
for i in l: | |||||
if i == -1: | |||||
yield None | |||||
else: | |||||
yield i | |||||
if lang_string in PARSERS: | |||||
symbols = PARSERS[lang_string](codelet) | |||||
symbols = { | |||||
key: [(name, | |||||
[tuple(loc_helper(loc)) for loc in syms[name]["assignments"]], | |||||
[tuple(loc_helper(loc)) for loc in syms[name]["uses"]]) | |||||
for name in syms] | |||||
for key, syms in symbols.iteritems()} | |||||
codelet.symbols = symbols |
@@ -0,0 +1,106 @@ | |||||
from pycparser import c_parser, c_ast | |||||
class _TreeCutter(c_ast.NodeVisitor): | |||||
""" | |||||
Local node visitor for c abstract syntax trees. | |||||
:ivar accum: (dict) Information on variables, functions, and structs | |||||
accumulated from an abstract syntax tree. | |||||
:ivar cache: (dict or None) Information stored about parent nodes. Added | |||||
to accum when node reaches the lowest possible level. | |||||
.. todo:: | |||||
Add visit function for c_ast.ID to record all uses of a variable. | |||||
Use self.cache to store extra information about variables. | |||||
""" | |||||
def __init__(self): | |||||
""" | |||||
Create a _TreeCutter instance. | |||||
""" | |||||
self.accum = {'vars': {}, 'functions': {}, 'structs': {}} | |||||
self.cache = None | |||||
def start_n_end(self, node): | |||||
pass | |||||
def visit_FuncDecl(self, node): | |||||
""" | |||||
Visits FuncDecl nodes in a tree. Adds relevant data about them to accum | |||||
after visiting all of its children as well. | |||||
:param node: The current node. | |||||
:type node: c_ast.FuncDecl | |||||
.. todo:: | |||||
Add other relevant information about functions like parameters and | |||||
return type. | |||||
""" | |||||
self.cache['group'] = 'functions' | |||||
self.cache['meta']['end_ln'] = node.coord.line | |||||
self.cache['meta']['end_col'] = node.coord.column | |||||
self.generic_visit(node) | |||||
def visit_Struct(self, node): | |||||
""" | |||||
Visits Struct nodes in a tree. Adds relevant data about them to accum | |||||
after visiting all of its children as well. | |||||
:param node: The current node. | |||||
:type node: c_ast.Struct | |||||
.. todo:: | |||||
Find other relevant information to add about structs. | |||||
""" | |||||
self.cache['group'] = 'structs' | |||||
self.cache['meta']['end_ln'] = node.coord.line | |||||
self.cache['meta']['end_col'] = node.coord.column | |||||
self.generic_visit(node) | |||||
def visit_Decl(self, node): | |||||
""" | |||||
Visits Decl nodes in a tree. Adds relevant data about them to accum | |||||
after visiting all of its children as well. | |||||
:param node: The current node. | |||||
:type node: c_ast.Decl | |||||
""" | |||||
self.cache = {'group': 'vars', 'meta': {}} | |||||
self.cache['meta']['start_ln'] = node.coord.line | |||||
self.cache['meta']['start_col'] = node.coord.column | |||||
self.cache['meta']['end_ln'] = node.coord.line | |||||
self.cache['meta']['end_col'] = node.coord.column | |||||
self.generic_visit(node) | |||||
self.accum[self.cache['group']][node.name] = self.cache['meta'] | |||||
self.cache = None | |||||
def parse_c(codelet): | |||||
""" | |||||
Adds 'symbols' field to the codelet after parsing the c code. | |||||
:param codelet: The codelet object to parsed. | |||||
:type code: Codelet | |||||
.. todo:: | |||||
Preprocess c code so that no ParseErrors are thrown. | |||||
""" | |||||
tree = c_parser.CParser().parse(codelet.code) | |||||
cutter = _TreeCutter() | |||||
cutter.visit(tree) | |||||
codelet.symbols = cutter.accum |
@@ -0,0 +1,217 @@ | |||||
import ast | |||||
import re | |||||
encoding_re = re.compile(r"^\s*#.*coding[:=]\s*([-\w.]+)", re.UNICODE) | |||||
class _TreeWalker(ast.NodeVisitor): | |||||
""" | |||||
Local node visitor for python abstract syntax trees. | |||||
:ivar symbols: (dict) Information on variables, functions, and classes | |||||
symbolsulated from an abstract syntax tree. | |||||
:ivar cache: (dict or None) Information stored about parent nodes. Added | |||||
to symbols when node reaches the lowest possible level. | |||||
.. todo:: | |||||
Add visit funciton for ast.Name to record all uses of a variable. | |||||
Use self.cache to store extra information about nodes. | |||||
""" | |||||
def __init__(self): | |||||
""" | |||||
Create a _TreeCutter instance. | |||||
""" | |||||
self.symbols = {'vars': {}, 'functions': {}, 'classes': {}} | |||||
self.cache = [] | |||||
def clear_cache(self): | |||||
self.cache = [] | |||||
def block_position(self, node): | |||||
""" | |||||
Helper function to get the start and end lines of an AST node. | |||||
:param node: The node. | |||||
:type node: ast.FunctionDef or ast.ClassDef or ast.Module | |||||
""" | |||||
start_line, start_col = node.lineno, node.col_offset | |||||
temp_node = node | |||||
while 'body' in temp_node.__dict__: | |||||
temp_node = temp_node.body[-1] | |||||
end_line, end_col = temp_node.lineno, temp_node.col_offset | |||||
if start_line == end_line: | |||||
return [start_line, start_col, end_line, -1] | |||||
return [start_line, start_col, end_line, end_col] | |||||
def visit_Assign(self, node): | |||||
""" | |||||
Visits Assign nodes in a tree. Adds relevant data about them to symbols. | |||||
:param node: The current node. | |||||
:type node: ast.Assign | |||||
.. todo:: | |||||
Add value and type metadata to symbols. | |||||
""" | |||||
pos = self.block_position(node) | |||||
for t in node.targets: | |||||
self.visit(t) | |||||
for name in self.cache: | |||||
if not self.symbols['vars'].has_key(name): | |||||
self.symbols['vars'][name] = {'assignments': [], 'uses': []} | |||||
self.symbols['vars'][name]['assignments'].append(pos) | |||||
self.clear_cache() | |||||
self.visit(node.value) | |||||
for name in self.cache: | |||||
if not self.symbols['vars'].has_key(name): | |||||
self.symbols['vars'][name] = {'assignments': [], 'uses': []} | |||||
self.symbols['vars'][name]['uses'].append(pos) | |||||
self.clear_cache() | |||||
def visit_FunctionDef(self, node): | |||||
""" | |||||
Visits FunctionDef nodes in a tree. Adds relevant data about them to symbols. | |||||
:param node: The current node. | |||||
:type node: ast.FunctionDef | |||||
.. todo:: | |||||
Add arguments and decorators metadata to symbols. | |||||
""" | |||||
pos = self.block_position(node) | |||||
if not self.symbols['functions'].has_key(node.name): | |||||
self.symbols['functions'][node.name] = {'assignments': [], 'uses': []} | |||||
self.symbols['functions'][node.name]['assignments'].append(pos) | |||||
self.generic_visit(node) | |||||
def visit_Call(self, node): | |||||
""" | |||||
Visits Function Call nodes in a tree. Adds relevant data about them | |||||
in the functions section for symbols. | |||||
:param node: The current node. | |||||
:type node: ast.Call | |||||
.. todo:: | |||||
Add arguments and decorators metadata to symbols. | |||||
""" | |||||
pos = self.block_position(node) | |||||
self.visit(node.func) | |||||
if not self.cache: | |||||
return | |||||
name = self.cache.pop() | |||||
if not self.symbols['functions'].has_key(name): | |||||
self.symbols['functions'][name] = {'assignments': [], 'uses': []} | |||||
self.symbols['functions'][name]['uses'].append(pos) | |||||
for name in self.cache: | |||||
if not self.symbols['vars'].has_key(name): | |||||
self.symbols['vars'][name] = {'assignments': [], 'uses': []} | |||||
self.symbols['vars'][name]['uses'].append(pos) | |||||
self.clear_cache() | |||||
for a in node.args: | |||||
self.visit(a) | |||||
for name in self.cache: | |||||
if not self.symbols['vars'].has_key(name): | |||||
self.symbols['vars'][name] = {'assignments': [], 'uses': []} | |||||
self.symbols['vars'][name]['uses'].append(pos) | |||||
self.clear_cache() | |||||
def visit_ClassDef(self, node): | |||||
""" | |||||
Visits ClassDef nodes in a tree. Adds relevant data about them to symbols. | |||||
:param node: The current node. | |||||
:type node: ast.ClassDef | |||||
.. todo:: | |||||
Add arguments, inherits, and decorators metadata to symbols. | |||||
""" | |||||
pos = self.block_position(node) | |||||
if node.name not in self.symbols['classes']: | |||||
self.symbols['classes'][node.name] = {'assignments': [], 'uses': []} | |||||
self.symbols['classes'][node.name]['assignments'].append(pos) | |||||
self.generic_visit(node) | |||||
def visit_Name(self, node): | |||||
self.cache.append(node.id) | |||||
def visit_Attribute(self, node): | |||||
self.visit(node.value) | |||||
self.cache.append(node.attr) | |||||
def visit_Import(self, node): | |||||
pos = self.block_position(node) | |||||
# look through aliases | |||||
def parse_py(codelet): | |||||
""" | |||||
Adds 'symbols' field to the codelet after parsing the python code. | |||||
:param codelet: The codelet object to parsed. | |||||
:type code: Codelet | |||||
""" | |||||
def strip_encoding(lines): | |||||
"""Strips the encoding line from a file, which breaks the parser.""" | |||||
it = iter(lines) | |||||
try: | |||||
first = next(it) | |||||
if not encoding_re.match(first): | |||||
yield first | |||||
second = next(it) | |||||
if not encoding_re.match(second): | |||||
yield second | |||||
except StopIteration: | |||||
return | |||||
for line in it: | |||||
yield line | |||||
try: | |||||
tree = ast.parse("\n".join(strip_encoding(codelet.code.splitlines()))) | |||||
except SyntaxError: | |||||
## TODO: add some logging here? | |||||
return {} | |||||
walker = _TreeWalker() | |||||
walker.visit(tree) | |||||
return walker.symbols |
@@ -0,0 +1,320 @@ | |||||
""" | |||||
This subpackage contains code to parse search queries received from the | |||||
frontend into trees that can be used by the database backend. | |||||
""" | |||||
from __future__ import unicode_literals | |||||
from re import IGNORECASE, search | |||||
from sys import maxsize | |||||
from dateutil.parser import parse as parse_date | |||||
from .nodes import (String, Regex, Text, Language, Author, Date, Symbol, | |||||
BinaryOp, UnaryOp) | |||||
from .tree import Tree | |||||
from ..languages import LANGS | |||||
__all__ = ["QueryParseException", "parse_query"] | |||||
class QueryParseException(Exception): | |||||
"""Raised by parse_query() when a query is invalid.""" | |||||
pass | |||||
class _QueryParser(object): | |||||
"""Wrapper class with methods to parse queries. Used as a singleton.""" | |||||
def __init__(self): | |||||
self._prefixes = { | |||||
self._parse_language: ["l", "lang", "language"], | |||||
self._parse_author: ["a", "author"], | |||||
self._parse_modified: ["m", "mod", "modified", "modify"], | |||||
self._parse_created: ["cr", "create", "created"], | |||||
self._parse_symbol: ["s", "sym", "symb", "symbol"], | |||||
self._parse_function: ["f", "fn", "fun", "func", "function", | |||||
"meth", "method"], | |||||
self._parse_class: ["cl", "class", "clss"], | |||||
self._parse_variable: ["v", "var", "variable"], | |||||
self._parse_namespace: ["n", "ns", "namespace", "module"], | |||||
self._parse_interface: ["in", "inter", "interface", "implements"], | |||||
self._parse_import: ["im", "imp", "import", "include", "require", | |||||
"imports", "requires"] | |||||
} | |||||
def _scan_query(self, query, markers): | |||||
"""Scan a query (sub)string for the first occurance of some markers. | |||||
Returns a 2-tuple of (first_marker_found, marker_index). | |||||
""" | |||||
def is_escaped(query, index): | |||||
"""Return whether a query marker is backslash-escaped.""" | |||||
return (index > 0 and query[index - 1] == "\\" and | |||||
(index < 2 or query[index - 2] != "\\")) | |||||
best_marker, best_index = None, maxsize | |||||
for marker in markers: | |||||
index = query.find(marker) | |||||
if is_escaped(query, index): | |||||
_, new_index = self._scan_query(query[index + 1:], marker) | |||||
index += new_index + 1 | |||||
if index >= 0 and index < best_index: | |||||
best_marker, best_index = marker, index | |||||
return best_marker, best_index | |||||
def _split_query(self, query, markers, parens=False): | |||||
"""Split a query string into a nested list of query terms. | |||||
Returns a list of terms and/or nested sublists of terms. Each term and | |||||
sublist is guarenteed to be non-empty. | |||||
""" | |||||
query = query.lstrip() | |||||
if not query: | |||||
return [] | |||||
marker, index = self._scan_query(query, markers) | |||||
if not marker: | |||||
return [query] | |||||
nest = [query[:index]] if index > 0 else [] | |||||
after = query[index + 1:] | |||||
if marker == " ": | |||||
nest += self._split_query(after, markers, parens) | |||||
elif marker in ('"', "'"): | |||||
close_marker, close_index = self._scan_query(after, marker) | |||||
if close_marker: | |||||
if close_index > 0: | |||||
nest.append(after[:close_index]) | |||||
after = after[close_index + 1:] | |||||
nest += self._split_query(after, markers, parens) | |||||
elif after: | |||||
nest.append(after) | |||||
elif marker == "(": | |||||
inner, after = self._split_query(after, markers, True), [] | |||||
if inner and isinstance(inner[-1], tuple): | |||||
after = self._split_query(inner.pop()[0], markers, parens) | |||||
if inner: | |||||
nest.append(inner) | |||||
if after: | |||||
nest += after | |||||
elif marker == ")": | |||||
if parens: | |||||
nest.append((after,)) | |||||
else: | |||||
nest += self._split_query(after, markers) | |||||
return nest | |||||
def _parse_literal(self, literal): | |||||
"""Parse part of a search query into a string or regular expression.""" | |||||
if literal.startswith(("r:", "re:", "regex:", "regexp:")): | |||||
arg = literal.split(":", 1)[1] | |||||
if not arg: | |||||
err = 'Incomplete query term: "%s"' % literal | |||||
raise QueryParseException(err) | |||||
return Regex(arg) | |||||
return String(literal) | |||||
def _parse_language(self, term): | |||||
"""Parse part of a query into a language node and return it.""" | |||||
term = self._parse_literal(term) | |||||
if isinstance(term, Regex): | |||||
langs = [i for i, lang in enumerate(LANGS) | |||||
if search(term.regex, lang, IGNORECASE)] | |||||
if not langs: | |||||
err = 'No languages found for regex: "%s"' % term.regex | |||||
raise QueryParseException(err) | |||||
node = Language(langs.pop()) | |||||
while langs: | |||||
node = BinaryOp(Language(langs.pop()), BinaryOp.OR, node) | |||||
return node | |||||
needle = term.string.lower() | |||||
for i, lang in enumerate(LANGS): | |||||
if lang.lower() == needle: | |||||
return Language(i) | |||||
for i, lang in enumerate(LANGS): | |||||
if lang.lower().startswith(needle): | |||||
return Language(i) | |||||
err = 'No languages found for string: "%s"' % term.string | |||||
raise QueryParseException(err) | |||||
def _parse_author(self, term): | |||||
"""Parse part of a query into an author node and return it.""" | |||||
return Author(self._parse_literal(term)) | |||||
def _parse_date(self, term, type_): | |||||
"""Parse part of a query into a date node and return it.""" | |||||
if ":" not in term: | |||||
err = "A date relationship is required " \ | |||||
'("before:<date>" or "after:<date>"): "%s"' | |||||
raise QueryParseException(err % term) | |||||
relstr, dtstr = term.split(":", 1) | |||||
if relstr.lower() in ("before", "b"): | |||||
relation = Date.BEFORE | |||||
elif relstr.lower() in ("after", "a"): | |||||
relation = Date.AFTER | |||||
else: | |||||
err = 'Bad date relationship (should be "before" or "after"): "%s"' | |||||
raise QueryParseException(err % relstr) | |||||
try: | |||||
dt = parse_date(dtstr) | |||||
except (TypeError, ValueError): | |||||
raise QueryParseException('Bad date/time string: "%s"' % dtstr) | |||||
return Date(type_, relation, dt) | |||||
def _parse_modified(self, term): | |||||
"""Parse part of a query into a date modified node and return it.""" | |||||
return self._parse_date(term, Date.MODIFY) | |||||
def _parse_created(self, term): | |||||
"""Parse part of a query into a date created node and return it.""" | |||||
return self._parse_date(term, Date.CREATE) | |||||
def _parse_symbol(self, term, stype=Symbol.ALL): | |||||
"""Parse part of a query into a symbol node and return it.""" | |||||
defines = ("a:", "assign:", "assignment:", "d:", "def:", "definition:", | |||||
"decl:", "declare:", "declaration:") | |||||
uses = ("u:", "use:", "c:", "call:") | |||||
if term.startswith(defines) or term.startswith(uses): | |||||
context = Symbol.DEFINE if term.startswith(defines) else Symbol.USE | |||||
term_part = term.split(":", 1)[1] | |||||
if not term_part: | |||||
raise QueryParseException('Incomplete query term: "%s"' % term) | |||||
term = term_part | |||||
else: | |||||
context = Symbol.ALL | |||||
literal = self._parse_literal(term) | |||||
if isinstance(literal, String): | |||||
make_symbol = lambda lit: Symbol(context, stype, String(lit)) | |||||
symbols = self._split_query(literal.string, " \"'") | |||||
node = make_symbol(symbols.pop()) | |||||
while symbols: | |||||
node = BinaryOp(make_symbol(symbols.pop()), BinaryOp.OR, node) | |||||
return node | |||||
return Symbol(context, stype, literal) | |||||
def _parse_function(self, term): | |||||
"""Parse part of a query into a function node and return it.""" | |||||
return self._parse_symbol(term, Symbol.FUNCTION) | |||||
def _parse_class(self, term): | |||||
"""Parse part of a query into a class node and return it.""" | |||||
return self._parse_symbol(term, Symbol.CLASS) | |||||
def _parse_variable(self, term): | |||||
"""Parse part of a query into a variable node and return it.""" | |||||
return self._parse_symbol(term, Symbol.VARIABLE) | |||||
def _parse_namespace(self, term): | |||||
"""Parse part of a query into a namespace node and return it.""" | |||||
return self._parse_symbol(term, Symbol.NAMESPACE) | |||||
def _parse_interface(self, term): | |||||
"""Parse part of a query into a interface node and return it.""" | |||||
return self._parse_symbol(term, Symbol.INTERFACE) | |||||
def _parse_import(self, term): | |||||
"""Parse part of a query into a import node and return it.""" | |||||
return self._parse_symbol(term, Symbol.IMPORT) | |||||
def _parse_term(self, term): | |||||
"""Parse a query term into a tree node and return it.""" | |||||
term = term.replace('\\"', '"').replace("\\\\", "\\") | |||||
if ":" in term and not term[0] == ":": | |||||
prefix, arg = term.split(":", 1) | |||||
invert = prefix.lower() == "not" | |||||
if invert: | |||||
prefix, arg = arg.split(":", 1) | |||||
if not arg: | |||||
raise QueryParseException('Incomplete query term: "%s"' % term) | |||||
for meth, prefixes in self._prefixes.iteritems(): | |||||
if prefix.lower() in prefixes: | |||||
if invert: | |||||
return UnaryOp(UnaryOp.NOT, meth(arg)) | |||||
return meth(arg) | |||||
return Text(self._parse_literal(term)) | |||||
def _parse_boolean_operators(self, nest): | |||||
"""Parse boolean operators in a nested query list.""" | |||||
op_lookup = { | |||||
"and": BinaryOp.AND, | |||||
"or": BinaryOp.OR, | |||||
"not": UnaryOp.NOT | |||||
} | |||||
for i, term in enumerate(nest): | |||||
if isinstance(term, list): | |||||
self._parse_boolean_operators(term) | |||||
else: | |||||
nest[i] = op_lookup.get(term.lower(), term) | |||||
def _parse_nest(self, nest): | |||||
"""Recursively parse a nested list of search query terms.""" | |||||
def parse_binary_op(op): | |||||
"""Parse a binary operator in a nested query list.""" | |||||
index = nest.index(op) | |||||
if index == 0 or index == len(nest) - 1: | |||||
err = "Invalid query: '%s' given without argument." | |||||
raise QueryParseException(err % BinaryOp.OPS[op]) | |||||
left = self._parse_nest(nest[:index]) | |||||
right = self._parse_nest(nest[index + 1:]) | |||||
return BinaryOp(left, op, right) | |||||
if not nest: | |||||
err = "Error while parsing query: empty nest detected." | |||||
raise QueryParseException(err) | |||||
elif BinaryOp.OR in nest: | |||||
return parse_binary_op(BinaryOp.OR) | |||||
elif BinaryOp.AND in nest: | |||||
return parse_binary_op(BinaryOp.AND) | |||||
elif UnaryOp.NOT in nest: | |||||
index = nest.index(UnaryOp.NOT) | |||||
if index == len(nest) - 1: | |||||
err = "Invalid query: '%s' given without argument." | |||||
raise QueryParseException(err % UnaryOp.OPS[UnaryOp.NOT]) | |||||
right = UnaryOp(UnaryOp.NOT, self._parse_nest(nest[index + 1:])) | |||||
if index > 0: | |||||
left = self._parse_nest(nest[:index]) | |||||
return BinaryOp(left, BinaryOp.AND, right) | |||||
return right | |||||
elif len(nest) > 1: | |||||
left, right = self._parse_term(nest[0]), self._parse_nest(nest[1:]) | |||||
return BinaryOp(left, BinaryOp.AND, right) | |||||
elif isinstance(nest[0], list): | |||||
return self._parse_nest(nest[0]) | |||||
else: | |||||
return self._parse_term(nest[0]) | |||||
def _balance_tree(self, node): | |||||
"""Auto-balance a tree using a string sorting function.""" | |||||
if isinstance(node, BinaryOp): | |||||
self._balance_tree(node.left) | |||||
self._balance_tree(node.right) | |||||
if node.right.sortkey() < node.left.sortkey(): | |||||
node.left, node.right = node.right, node.left | |||||
elif isinstance(node, UnaryOp): | |||||
self._balance_tree(node.node) | |||||
def parse(self, query): | |||||
""" | |||||
Parse a search query. | |||||
The result is normalized with a sorting function so that | |||||
``"foo OR bar"`` and ``"bar OR foo"`` result in the same tree. This is | |||||
important for caching purposes. | |||||
:param query: The query be converted. | |||||
:type query: str | |||||
:return: A tree storing the data in the query. | |||||
:rtype: :py:class:`~.query.tree.Tree` | |||||
:raises: :py:class:`.QueryParseException` | |||||
""" | |||||
nest = self._split_query(query.rstrip(), " \"'()") | |||||
if not nest: | |||||
raise QueryParseException('Empty query: "%s"' % query) | |||||
self._parse_boolean_operators(nest) | |||||
root = self._parse_nest(nest) | |||||
self._balance_tree(root) | |||||
return Tree(root) | |||||
parse_query = _QueryParser().parse |
@@ -0,0 +1,297 @@ | |||||
from ..languages import LANGS | |||||
__all__ = ["String", "Regex", "Text", "Language", "Author", "Date", "Symbol", | |||||
"BinaryOp", "UnaryOp"] | |||||
class _Node(object): | |||||
"""Represents a single node in a query tree. | |||||
Generally speaking, a node is a constraint applied to the database. Thus, | |||||
a :py:class:`~.Language` node represents a constraint where only codelets | |||||
of a specific language are selected. | |||||
""" | |||||
def _null_regex(self, expr): | |||||
"""Implements a regex search with support for a null expression.""" | |||||
return "IF(ISNULL(%s), 0, %s REGEXP ?)" % (expr, expr) | |||||
def sortkey(self): | |||||
"""Return a string sort key for the node.""" | |||||
return "" | |||||
def parameterize(self, tables): | |||||
"""Parameterize the node. | |||||
Returns a 4-tuple of (conditional string, parameter list, rank list, | |||||
should-we-rank boolean). If the rank list is empty, then it is assumed | |||||
to contain the conditional string. | |||||
""" | |||||
return "", [], [], False | |||||
class _Literal(object): | |||||
"""Represents a literal component of a search query, present at the leaves. | |||||
A literal might be a string or a regular expression. | |||||
""" | |||||
pass | |||||
class String(_Literal): | |||||
"""Represents a string literal.""" | |||||
def __init__(self, string): | |||||
""" | |||||
:type string: unicode | |||||
""" | |||||
self.string = string | |||||
def __repr__(self): | |||||
return "String({0!r})".format(self.string) | |||||
def sortkey(self): | |||||
return self.string | |||||
class Regex(_Literal): | |||||
"""Represents a regular expression literal.""" | |||||
def __init__(self, regex): | |||||
""" | |||||
:type string: unicode | |||||
""" | |||||
self.regex = regex | |||||
def __repr__(self): | |||||
return "Regex({0!r})".format(self.regex) | |||||
def sortkey(self): | |||||
return self.regex | |||||
class Text(_Node): | |||||
"""Represents a text node. | |||||
Searches in codelet names (full-text search), symbols (equality), and | |||||
source code (full-text search). | |||||
""" | |||||
def __init__(self, text): | |||||
""" | |||||
:type text: :py:class:`._Literal` | |||||
""" | |||||
self.text = text | |||||
def __repr__(self): | |||||
return "Text({0})".format(self.text) | |||||
def sortkey(self): | |||||
return self.text.sortkey() | |||||
def parameterize(self, tables): | |||||
tables |= {"code", "symbols"} | |||||
if isinstance(self.text, Regex): | |||||
ranks = ["(codelet_name REGEXP ?)", "(code_code REGEXP ?)", | |||||
self._null_regex("symbol_name")] | |||||
text = self.text.regex | |||||
else: | |||||
ranks = ["(MATCH(codelet_name) AGAINST (? IN BOOLEAN MODE))", | |||||
"(MATCH(code_code) AGAINST (? IN BOOLEAN MODE))", | |||||
"(symbol_name <=> ?)"] | |||||
text = self.text.string | |||||
cond = "(" + " OR ".join(ranks) + ")" | |||||
return cond, [text] * 3, ranks, True | |||||
class Language(_Node): | |||||
"""Represents a language node. | |||||
Searches in the code_lang field. | |||||
""" | |||||
def __init__(self, lang): | |||||
""" | |||||
:type lang: int | |||||
""" | |||||
self.lang = lang | |||||
def __repr__(self): | |||||
return "Language({0})".format(LANGS[self.lang]) | |||||
def sortkey(self): | |||||
return LANGS[self.lang] | |||||
def parameterize(self, tables): | |||||
tables |= {"code"} | |||||
return "(code_lang <=> ?)", [self.lang], [], False | |||||
class Author(_Node): | |||||
"""Represents a author node. | |||||
Searches in the author_name field (full-text search). | |||||
""" | |||||
def __init__(self, name): | |||||
""" | |||||
:type name: :py:class:`_Literal` | |||||
""" | |||||
self.name = name | |||||
def __repr__(self): | |||||
return "Author({0})".format(self.name) | |||||
def sortkey(self): | |||||
return self.name.sortkey() | |||||
def parameterize(self, tables): | |||||
tables |= {"authors"} | |||||
if isinstance(self.name, Regex): | |||||
cond = self._null_regex("author_name") | |||||
return cond, [self.name.regex], [], False | |||||
cond = "(MATCH(author_name) AGAINST (? IN BOOLEAN MODE))" | |||||
return cond, [self.name.string], [], True | |||||
class Date(_Node): | |||||
"""Represents a date node. | |||||
Searches in the codelet_date_created or codelet_date_modified fields. | |||||
""" | |||||
CREATE = 1 | |||||
MODIFY = 2 | |||||
BEFORE = 1 | |||||
AFTER = 2 | |||||
def __init__(self, type_, relation, date): | |||||
""" | |||||
:type type_: int (``CREATE`` or ``MODIFY``) | |||||
:type relation: int (``BEFORE``, ``AFTER``) | |||||
:type date: datetime.datetime | |||||
""" | |||||
self.type = type_ | |||||
self.relation = relation | |||||
self.date = date | |||||
def __repr__(self): | |||||
types = {self.CREATE: "CREATE", self.MODIFY: "MODIFY"} | |||||
relations = {self.BEFORE: "BEFORE", self.AFTER: "AFTER"} | |||||
tm = "Date({0}, {1}, {2})" | |||||
return tm.format(types[self.type], relations[self.relation], self.date) | |||||
def sortkey(self): | |||||
return self.date.strftime("%Y%m%d%H%M%S") | |||||
def parameterize(self, tables): | |||||
column = {self.CREATE: "codelet_date_created", | |||||
self.MODIFY: "codelet_date_modified"}[self.type] | |||||
op = {self.BEFORE: "<=", self.AFTER: ">="}[self.relation] | |||||
cond = "IF(ISNULL(%s), 0, %s %s ?)" % (column, column, op) | |||||
return cond, [self.date], [], False | |||||
class Symbol(_Node): | |||||
"""Represents a symbol node. | |||||
Searches in symbol_type and symbol_name. | |||||
""" | |||||
ALL = -1 | |||||
DEFINE = 0 | |||||
USE = 1 | |||||
FUNCTION = 0 | |||||
CLASS = 1 | |||||
VARIABLE = 2 | |||||
NAMESPACE = 3 | |||||
INTERFACE = 4 | |||||
IMPORT = 5 | |||||
TYPES = ["functions", "classes", "vars", "namespaces", "interfaces", | |||||
"imports"] | |||||
TYPE_REPR = ["FUNCTION", "CLASS", "VARIABLE", "NAMESPACE", "INTERFACE", | |||||
"IMPORT"] | |||||
def __init__(self, context, type_, name): | |||||
""" | |||||
:type context: int (``DEFINE`` or ``USE``) | |||||
:type type_: int (``ALL``, ``FUNCTION``, ``CLASS``, etc.) | |||||
:type name: :py:class:`._Literal` | |||||
""" | |||||
self.context = context | |||||
self.type = type_ | |||||
self.name = name | |||||
def __repr__(self): | |||||
context = ["DEFINE", "USE", "ALL"][self.context] | |||||
type_ = self.TYPE_REPR[self.type] if self.type >= 0 else "ALL" | |||||
return "Symbol({0}, {1}, {2})".format(context, type_, self.name) | |||||
def sortkey(self): | |||||
return self.name.sortkey() | |||||
def parameterize(self, tables): | |||||
tables |= {"code", "symbols"} | |||||
if isinstance(self.name, Regex): | |||||
cond, name = self._null_regex("symbol_name"), self.name.regex | |||||
else: | |||||
cond, name = "symbol_name <=> ?", self.name.string | |||||
if self.type == self.ALL: | |||||
types = ", ".join(str(typ) for typ in xrange(len(self.TYPES))) | |||||
part = " AND IF(ISNULL(symbol_type), 0, symbol_type IN (%s))" | |||||
cond += part % types | |||||
if self.type != self.ALL: | |||||
cond += " AND symbol_type <=> %d" % self.type | |||||
if self.context != self.ALL: | |||||
tables |= {"symbol_locations"} | |||||
cond += " AND sloc_type <=> %d" % self.context | |||||
return "(" + cond + ")", [name], [], False | |||||
class BinaryOp(_Node): | |||||
"""Represents a relationship between two nodes: ``and``, ``or``.""" | |||||
AND = object() | |||||
OR = object() | |||||
OPS = {AND: "AND", OR: "OR"} | |||||
def __init__(self, left, op, right): | |||||
self.left = left | |||||
self.op = op | |||||
self.right = right | |||||
def __repr__(self): | |||||
tmpl = "BinaryOp({0}, {1}, {2})" | |||||
return tmpl.format(self.left, self.OPS[self.op], self.right) | |||||
def sortkey(self): | |||||
return self.left.sortkey() + self.right.sortkey() | |||||
def parameterize(self, tables): | |||||
lcond, largs, lranks, need_lranks = self.left.parameterize(tables) | |||||
rcond, rargs, rranks, need_rranks = self.right.parameterize(tables) | |||||
lranks, rranks = lranks or [lcond], rranks or [rcond] | |||||
op = self.OPS[self.op] | |||||
cond = "(" + lcond + " " + op + " " + rcond + ")" | |||||
need_ranks = need_lranks or need_rranks or self.op == self.OR | |||||
return cond, largs + rargs, lranks + rranks, need_ranks | |||||
class UnaryOp(_Node): | |||||
"""Represents a transformation applied to one node: ``not``.""" | |||||
NOT = object() | |||||
OPS = {NOT: "NOT"} | |||||
def __init__(self, op, node): | |||||
self.op = op | |||||
self.node = node | |||||
def __repr__(self): | |||||
return "UnaryOp({0}, {1})".format(self.OPS[self.op], self.node) | |||||
def sortkey(self): | |||||
return self.node.sortkey() | |||||
def parameterize(self, tables): | |||||
cond, args, ranks, need_ranks = self.node.parameterize(tables) | |||||
new_cond = "(" + self.OPS[self.op] + " " + cond + ")" | |||||
ranks = ranks or [cond] | |||||
return new_cond, args, ranks, need_ranks |
@@ -0,0 +1,84 @@ | |||||
from . import nodes | |||||
__all__ = ["Tree"] | |||||
QUERY_TEMPLATE = """SELECT codelet_id, MAX(codelet_rank%s) AS score | |||||
FROM codelets %s | |||||
WHERE %s | |||||
GROUP BY codelet_id | |||||
ORDER BY score DESC | |||||
LIMIT %d OFFSET %d""".replace("\n", " ") | |||||
class Tree(object): | |||||
"""Represents a query tree.""" | |||||
def __init__(self, root): | |||||
self._root = root | |||||
def __repr__(self): | |||||
return "Tree({0})".format(self._root) | |||||
@property | |||||
def root(self): | |||||
"""The root node of the tree.""" | |||||
return self._root | |||||
def sortkey(self): | |||||
"""Return a string sort key for the query tree.""" | |||||
return self._root.sortkey() | |||||
def serialize(self): | |||||
"""Create a string representation of the query for caching. | |||||
:return: Query string representation. | |||||
:rtype: str | |||||
""" | |||||
return repr(self) | |||||
def walk(self, node_type=None): | |||||
"""Walk through the query tree, returning nodes of a specific type.""" | |||||
pending = [self._root] | |||||
while pending: | |||||
node = pending.pop() | |||||
if not node_type or isinstance(node, node_type): | |||||
yield node | |||||
if isinstance(node, nodes.UnaryOp): | |||||
pending.append(node.node) | |||||
elif isinstance(node, nodes.BinaryOp): | |||||
pending.extend([node.left, node.right]) | |||||
def build_query(self, page=1, page_size=10): | |||||
"""Convert the query tree into a parameterized SQL SELECT statement. | |||||
:param page: The page number to get results for. | |||||
:type page: int | |||||
:param page_size: The number of results per page. | |||||
:type page_size: int | |||||
:return: SQL query data. | |||||
:rtype: 2-tuple of (SQL statement string, query parameter tuple) | |||||
""" | |||||
def get_table_joins(tables): | |||||
joins = [ | |||||
("INNER", "code", "codelet_code_id", "code_id"), | |||||
("LEFT", "authors", "author_codelet", "codelet_id"), | |||||
("LEFT", "symbols", "symbol_code", "code_id"), | |||||
("LEFT", "symbol_locations", "sloc_symbol", "symbol_id") | |||||
] | |||||
tmpl = "%s JOIN %s ON %s = %s" | |||||
for args in joins: | |||||
if args[1] in tables: | |||||
yield tmpl % args | |||||
tables = set() | |||||
cond, arglist, ranks, need_ranks = self._root.parameterize(tables) | |||||
ranks = ranks or [cond] | |||||
if need_ranks: | |||||
score = " + ((%s) / %d)" % (" + ".join(ranks), len(ranks)) | |||||
else: | |||||
score = "" | |||||
joins = " ".join(get_table_joins(tables)) | |||||
offset = (page - 1) * page_size | |||||
query = QUERY_TEMPLATE % (score, joins, cond, page_size, offset) | |||||
return query, tuple(arglist * 2 if need_ranks else arglist) |
@@ -0,0 +1,177 @@ | |||||
# Makefile for Sphinx documentation | |||||
# | |||||
# You can set these variables from the command line. | |||||
SPHINXOPTS = | |||||
SPHINXBUILD = sphinx-build | |||||
PAPER = | |||||
BUILDDIR = build | |||||
# User-friendly check for sphinx-build | |||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) | |||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) | |||||
endif | |||||
# Internal variables. | |||||
PAPEROPT_a4 = -D latex_paper_size=a4 | |||||
PAPEROPT_letter = -D latex_paper_size=letter | |||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source | |||||
# the i18n builder cannot share the environment and doctrees with the others | |||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source | |||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext | |||||
help: | |||||
@echo "Please use \`make <target>' where <target> is one of" | |||||
@echo " html to make standalone HTML files" | |||||
@echo " dirhtml to make HTML files named index.html in directories" | |||||
@echo " singlehtml to make a single large HTML file" | |||||
@echo " pickle to make pickle files" | |||||
@echo " json to make JSON files" | |||||
@echo " htmlhelp to make HTML files and a HTML help project" | |||||
@echo " qthelp to make HTML files and a qthelp project" | |||||
@echo " devhelp to make HTML files and a Devhelp project" | |||||
@echo " epub to make an epub" | |||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" | |||||
@echo " latexpdf to make LaTeX files and run them through pdflatex" | |||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" | |||||
@echo " text to make text files" | |||||
@echo " man to make manual pages" | |||||
@echo " texinfo to make Texinfo files" | |||||
@echo " info to make Texinfo files and run them through makeinfo" | |||||
@echo " gettext to make PO message catalogs" | |||||
@echo " changes to make an overview of all changed/added/deprecated items" | |||||
@echo " xml to make Docutils-native XML files" | |||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes" | |||||
@echo " linkcheck to check all external links for integrity" | |||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)" | |||||
clean: | |||||
rm -rf $(BUILDDIR)/* | |||||
html: | |||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html | |||||
@echo | |||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." | |||||
dirhtml: | |||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml | |||||
@echo | |||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." | |||||
singlehtml: | |||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml | |||||
@echo | |||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." | |||||
pickle: | |||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle | |||||
@echo | |||||
@echo "Build finished; now you can process the pickle files." | |||||
json: | |||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json | |||||
@echo | |||||
@echo "Build finished; now you can process the JSON files." | |||||
htmlhelp: | |||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp | |||||
@echo | |||||
@echo "Build finished; now you can run HTML Help Workshop with the" \ | |||||
".hhp project file in $(BUILDDIR)/htmlhelp." | |||||
qthelp: | |||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp | |||||
@echo | |||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \ | |||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:" | |||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/bitshift.qhcp" | |||||
@echo "To view the help file:" | |||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/bitshift.qhc" | |||||
devhelp: | |||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp | |||||
@echo | |||||
@echo "Build finished." | |||||
@echo "To view the help file:" | |||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/bitshift" | |||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/bitshift" | |||||
@echo "# devhelp" | |||||
epub: | |||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub | |||||
@echo | |||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub." | |||||
latex: | |||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | |||||
@echo | |||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." | |||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \ | |||||
"(use \`make latexpdf' here to do that automatically)." | |||||
latexpdf: | |||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | |||||
@echo "Running LaTeX files through pdflatex..." | |||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf | |||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." | |||||
latexpdfja: | |||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | |||||
@echo "Running LaTeX files through platex and dvipdfmx..." | |||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja | |||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." | |||||
text: | |||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text | |||||
@echo | |||||
@echo "Build finished. The text files are in $(BUILDDIR)/text." | |||||
man: | |||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man | |||||
@echo | |||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man." | |||||
texinfo: | |||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo | |||||
@echo | |||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." | |||||
@echo "Run \`make' in that directory to run these through makeinfo" \ | |||||
"(use \`make info' here to do that automatically)." | |||||
info: | |||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo | |||||
@echo "Running Texinfo files through makeinfo..." | |||||
make -C $(BUILDDIR)/texinfo info | |||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." | |||||
gettext: | |||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale | |||||
@echo | |||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." | |||||
changes: | |||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes | |||||
@echo | |||||
@echo "The overview file is in $(BUILDDIR)/changes." | |||||
linkcheck: | |||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck | |||||
@echo | |||||
@echo "Link check complete; look for any errors in the above output " \ | |||||
"or in $(BUILDDIR)/linkcheck/output.txt." | |||||
doctest: | |||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest | |||||
@echo "Testing of doctests in the sources finished, look at the " \ | |||||
"results in $(BUILDDIR)/doctest/output.txt." | |||||
xml: | |||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml | |||||
@echo | |||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml." | |||||
pseudoxml: | |||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml | |||||
@echo | |||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." |
@@ -0,0 +1,27 @@ | |||||
crawler Package | |||||
=============== | |||||
:mod:`crawler` Package | |||||
---------------------- | |||||
.. automodule:: bitshift.crawler | |||||
:members: | |||||
:undoc-members: | |||||
:show-inheritance: | |||||
:mod:`crawler` Module | |||||
--------------------- | |||||
.. automodule:: bitshift.crawler.crawler | |||||
:members: | |||||
:undoc-members: | |||||
:show-inheritance: | |||||
:mod:`indexer` Module | |||||
--------------------- | |||||
.. automodule:: bitshift.crawler.indexer | |||||
:members: | |||||
:undoc-members: | |||||
:show-inheritance: | |||||
@@ -0,0 +1,19 @@ | |||||
database Package | |||||
================ | |||||
:mod:`database` Package | |||||
----------------------- | |||||
.. automodule:: bitshift.database | |||||
:members: | |||||
:undoc-members: | |||||
:show-inheritance: | |||||
:mod:`migration` Module | |||||
----------------------- | |||||
.. automodule:: bitshift.database.migration | |||||
:members: | |||||
:undoc-members: | |||||
:show-inheritance: | |||||
@@ -0,0 +1,11 @@ | |||||
query Package | |||||
============= | |||||
:mod:`query` Package | |||||
-------------------- | |||||
.. automodule:: bitshift.query | |||||
:members: | |||||
:undoc-members: | |||||
:show-inheritance: | |||||
@@ -0,0 +1,45 @@ | |||||
bitshift Package | |||||
================ | |||||
:mod:`bitshift` Package | |||||
----------------------- | |||||
.. automodule:: bitshift.__init__ | |||||
:members: | |||||
:undoc-members: | |||||
:show-inheritance: | |||||
:mod:`assets` Module | |||||
-------------------- | |||||
.. automodule:: bitshift.assets | |||||
:members: | |||||
:undoc-members: | |||||
:show-inheritance: | |||||
:mod:`codelet` Module | |||||
--------------------- | |||||
.. automodule:: bitshift.codelet | |||||
:members: | |||||
:undoc-members: | |||||
:show-inheritance: | |||||
:mod:`config` Module | |||||
-------------------- | |||||
.. automodule:: bitshift.config | |||||
:members: | |||||
:undoc-members: | |||||
:show-inheritance: | |||||
Subpackages | |||||
----------- | |||||
.. toctree:: | |||||
bitshift.crawler | |||||
bitshift.database | |||||
bitshift.parser | |||||
bitshift.query | |||||
@@ -0,0 +1,7 @@ | |||||
bitshift | |||||
======== | |||||
.. toctree:: | |||||
:maxdepth: 4 | |||||
bitshift |
@@ -0,0 +1,268 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# | |||||
# bitshift documentation build configuration file, created by | |||||
# sphinx-quickstart on Mon Apr 7 21:09:45 2014. | |||||
# | |||||
# This file is execfile()d with the current directory set to its | |||||
# containing dir. | |||||
# | |||||
# Note that not all possible configuration values are present in this | |||||
# autogenerated file. | |||||
# | |||||
# All configuration values have a default; values that are commented out | |||||
# serve to show the default. | |||||
import sys | |||||
import os | |||||
# If extensions (or modules to document with autodoc) are in another directory, | |||||
# add these directories to sys.path here. If the directory is relative to the | |||||
# documentation root, use os.path.abspath to make it absolute, like shown here. | |||||
sys.path.insert(0, os.path.abspath('../..')) | |||||
# -- General configuration ------------------------------------------------ | |||||
# If your documentation needs a minimal Sphinx version, state it here. | |||||
#needs_sphinx = '1.0' | |||||
# Add any Sphinx extension module names here, as strings. They can be | |||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom | |||||
# ones. | |||||
extensions = [ | |||||
'sphinx.ext.autodoc', | |||||
'sphinx.ext.intersphinx', | |||||
'sphinx.ext.coverage', | |||||
'sphinx.ext.mathjax', | |||||
'sphinx.ext.viewcode', | |||||
] | |||||
# Add any paths that contain templates here, relative to this directory. | |||||
templates_path = ['_templates'] | |||||
# The suffix of source filenames. | |||||
source_suffix = '.rst' | |||||
# The encoding of source files. | |||||
#source_encoding = 'utf-8-sig' | |||||
# The master toctree document. | |||||
master_doc = 'index' | |||||
# General information about the project. | |||||
project = u'bitshift' | |||||
copyright = u'2014, Benjamin Attal, Ben Kurtovic, Severyn Kozak' | |||||
# The version info for the project you're documenting, acts as replacement for | |||||
# |version| and |release|, also used in various other places throughout the | |||||
# built documents. | |||||
# | |||||
# The short X.Y version. | |||||
version = '0.1' | |||||
# The full version, including alpha/beta/rc tags. | |||||
release = '0.1.dev' | |||||
# The language for content autogenerated by Sphinx. Refer to documentation | |||||
# for a list of supported languages. | |||||
#language = None | |||||
# There are two options for replacing |today|: either, you set today to some | |||||
# non-false value, then it is used: | |||||
#today = '' | |||||
# Else, today_fmt is used as the format for a strftime call. | |||||
#today_fmt = '%B %d, %Y' | |||||
# List of patterns, relative to source directory, that match files and | |||||
# directories to ignore when looking for source files. | |||||
exclude_patterns = [] | |||||
# The reST default role (used for this markup: `text`) to use for all | |||||
# documents. | |||||
#default_role = None | |||||
# If true, '()' will be appended to :func: etc. cross-reference text. | |||||
#add_function_parentheses = True | |||||
# If true, the current module name will be prepended to all description | |||||
# unit titles (such as .. function::). | |||||
#add_module_names = True | |||||
# If true, sectionauthor and moduleauthor directives will be shown in the | |||||
# output. They are ignored by default. | |||||
#show_authors = False | |||||
# The name of the Pygments (syntax highlighting) style to use. | |||||
pygments_style = 'sphinx' | |||||
# A list of ignored prefixes for module index sorting. | |||||
#modindex_common_prefix = [] | |||||
# If true, keep warnings as "system message" paragraphs in the built documents. | |||||
#keep_warnings = False | |||||
# -- Options for HTML output ---------------------------------------------- | |||||
# The theme to use for HTML and HTML Help pages. See the documentation for | |||||
# a list of builtin themes. | |||||
html_theme = 'nature' | |||||
# Theme options are theme-specific and customize the look and feel of a theme | |||||
# further. For a list of options available for each theme, see the | |||||
# documentation. | |||||
#html_theme_options = {} | |||||
# Add any paths that contain custom themes here, relative to this directory. | |||||
#html_theme_path = [] | |||||
# The name for this set of Sphinx documents. If None, it defaults to | |||||
# "<project> v<release> documentation". | |||||
#html_title = None | |||||
# A shorter title for the navigation bar. Default is the same as html_title. | |||||
#html_short_title = None | |||||
# The name of an image file (relative to this directory) to place at the top | |||||
# of the sidebar. | |||||
#html_logo = None | |||||
# The name of an image file (within the static path) to use as favicon of the | |||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 | |||||
# pixels large. | |||||
#html_favicon = None | |||||
# Add any paths that contain custom static files (such as style sheets) here, | |||||
# relative to this directory. They are copied after the builtin static files, | |||||
# so a file named "default.css" will overwrite the builtin "default.css". | |||||
html_static_path = ['_static'] | |||||
# Add any extra paths that contain custom files (such as robots.txt or | |||||
# .htaccess) here, relative to this directory. These files are copied | |||||
# directly to the root of the documentation. | |||||
#html_extra_path = [] | |||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, | |||||
# using the given strftime format. | |||||
#html_last_updated_fmt = '%b %d, %Y' | |||||
# If true, SmartyPants will be used to convert quotes and dashes to | |||||
# typographically correct entities. | |||||
#html_use_smartypants = True | |||||
# Custom sidebar templates, maps document names to template names. | |||||
#html_sidebars = {} | |||||
# Additional templates that should be rendered to pages, maps page names to | |||||
# template names. | |||||
#html_additional_pages = {} | |||||
# If false, no module index is generated. | |||||
#html_domain_indices = True | |||||
# If false, no index is generated. | |||||
#html_use_index = True | |||||
# If true, the index is split into individual pages for each letter. | |||||
#html_split_index = False | |||||
# If true, links to the reST sources are added to the pages. | |||||
#html_show_sourcelink = True | |||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. | |||||
#html_show_sphinx = True | |||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. | |||||
#html_show_copyright = True | |||||
# If true, an OpenSearch description file will be output, and all pages will | |||||
# contain a <link> tag referring to it. The value of this option must be the | |||||
# base URL from which the finished HTML is served. | |||||
#html_use_opensearch = '' | |||||
# This is the file name suffix for HTML files (e.g. ".xhtml"). | |||||
#html_file_suffix = None | |||||
# Output file base name for HTML help builder. | |||||
htmlhelp_basename = 'bitshiftdoc' | |||||
# -- Options for LaTeX output --------------------------------------------- | |||||
latex_elements = { | |||||
# The paper size ('letterpaper' or 'a4paper'). | |||||
#'papersize': 'letterpaper', | |||||
# The font size ('10pt', '11pt' or '12pt'). | |||||
#'pointsize': '10pt', | |||||
# Additional stuff for the LaTeX preamble. | |||||
#'preamble': '', | |||||
} | |||||
# Grouping the document tree into LaTeX files. List of tuples | |||||
# (source start file, target name, title, | |||||
# author, documentclass [howto, manual, or own class]). | |||||
latex_documents = [ | |||||
('index', 'bitshift.tex', u'bitshift Documentation', | |||||
u'Benjamin Attal, Ben Kurtovic, Severyn Kozak', 'manual'), | |||||
] | |||||
# The name of an image file (relative to this directory) to place at the top of | |||||
# the title page. | |||||
#latex_logo = None | |||||
# For "manual" documents, if this is true, then toplevel headings are parts, | |||||
# not chapters. | |||||
#latex_use_parts = False | |||||
# If true, show page references after internal links. | |||||
#latex_show_pagerefs = False | |||||
# If true, show URL addresses after external links. | |||||
#latex_show_urls = False | |||||
# Documents to append as an appendix to all manuals. | |||||
#latex_appendices = [] | |||||
# If false, no module index is generated. | |||||
#latex_domain_indices = True | |||||
# -- Options for manual page output --------------------------------------- | |||||
# One entry per manual page. List of tuples | |||||
# (source start file, name, description, authors, manual section). | |||||
man_pages = [ | |||||
('index', 'bitshift', u'bitshift Documentation', | |||||
[u'Benjamin Attal, Ben Kurtovic, Severyn Kozak'], 1) | |||||
] | |||||
# If true, show URL addresses after external links. | |||||
#man_show_urls = False | |||||
# -- Options for Texinfo output ------------------------------------------- | |||||
# Grouping the document tree into Texinfo files. List of tuples | |||||
# (source start file, target name, title, author, | |||||
# dir menu entry, description, category) | |||||
texinfo_documents = [ | |||||
('index', 'bitshift', u'bitshift Documentation', | |||||
u'Benjamin Attal, Ben Kurtovic, Severyn Kozak', 'bitshift', 'One line description of project.', | |||||
'Miscellaneous'), | |||||
] | |||||
# Documents to append as an appendix to all manuals. | |||||
#texinfo_appendices = [] | |||||
# If false, no module index is generated. | |||||
#texinfo_domain_indices = True | |||||
# How to display URL addresses: 'footnote', 'no', or 'inline'. | |||||
#texinfo_show_urls = 'footnote' | |||||
# If true, do not generate a @detailmenu in the "Top" node's menu. | |||||
#texinfo_no_detailmenu = False | |||||
# Example configuration for intersphinx: refer to the Python standard library. | |||||
intersphinx_mapping = {'http://docs.python.org/': None} |
@@ -0,0 +1,20 @@ | |||||
bitshift | |||||
======== | |||||
**bitshift** is a semantic search engine for source code. | |||||
Contents: | |||||
.. toctree:: | |||||
:maxdepth: 2 | |||||
API Reference <api/modules> | |||||
Indices and tables | |||||
================== | |||||
* :ref:`genindex` | |||||
* :ref:`modindex` | |||||
* :ref:`search` | |||||
@@ -0,0 +1,9 @@ | |||||
# Configuration file for Gunicorn | |||||
# http://docs.gunicorn.org/en/latest/configure.html | |||||
bind = ["unix:/tmp/gunicorn.sock"] | |||||
workers = 4 | |||||
accesslog = "logs/access.log" | |||||
errorlog = "logs/error.log" | |||||
loglevel = "info" |
@@ -0,0 +1,72 @@ | |||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<groupId>com.bitshift.parsing</groupId> | |||||
<artifactId>parsing</artifactId> | |||||
<packaging>jar</packaging> | |||||
<version>1.0-SNAPSHOT</version> | |||||
<name>parsing</name> | |||||
<url>http://maven.apache.org</url> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>junit</groupId> | |||||
<artifactId>junit</artifactId> | |||||
<version>4.11</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jdt</groupId> | |||||
<artifactId>org.eclipse.jdt.core</artifactId> | |||||
<version>3.7.1</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.google.guava</groupId> | |||||
<artifactId>guava</artifactId> | |||||
<version>17.0</version> | |||||
</dependency> | |||||
</dependencies> | |||||
<build> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.codehaus.mojo</groupId> | |||||
<artifactId>exec-maven-plugin</artifactId> | |||||
<version>1.2.1</version> | |||||
<configuration> | |||||
<mainClass>com.bitshift.parsing.Parse</mainClass> | |||||
<arguments> | |||||
</arguments> | |||||
</configuration> | |||||
</plugin> | |||||
<plugin> | |||||
<artifactId>maven-assembly-plugin</artifactId> | |||||
<version>2.4</version> | |||||
<executions> | |||||
<execution> | |||||
<id>make-assembly</id> | |||||
<phase>package</phase> | |||||
<goals> | |||||
<goal>single</goal> | |||||
</goals> | |||||
<configuration> | |||||
<archive> | |||||
<manifest> | |||||
<addClasspath>true</addClasspath> | |||||
<mainClass>com.bitshift.parsing.Parse</mainClass> | |||||
</manifest> | |||||
</archive> | |||||
<descriptorRefs> | |||||
<descriptorRef>jar-with-dependencies</descriptorRef> | |||||
</descriptorRefs> | |||||
<outputDirectory>${project.basedir}</outputDirectory> | |||||
<finalName>${project.artifactId}</finalName> | |||||
<appendAssemblyId>false</appendAssemblyId> | |||||
</configuration> | |||||
</execution> | |||||
</executions> | |||||
</plugin> | |||||
</plugins> | |||||
</build> | |||||
</project> |
@@ -0,0 +1,35 @@ | |||||
package com.bitshift.parsing; | |||||
import java.io.BufferedReader; | |||||
import java.io.BufferedWriter; | |||||
import java.io.IOException; | |||||
import java.io.InputStreamReader; | |||||
import java.io.OutputStreamWriter; | |||||
import com.bitshift.parsing.parsers.JavaParser; | |||||
public class Parse { | |||||
public static void main(String[] args) { | |||||
try { | |||||
BufferedReader br = new BufferedReader( | |||||
new InputStreamReader(System.in)); | |||||
String str = ""; | |||||
StringBuilder source = new StringBuilder(); | |||||
while ((str = br.readLine()) != null) { | |||||
source.append(str + "\n"); | |||||
} | |||||
String symbols = (new JavaParser(source.toString())).parse(); | |||||
BufferedWriter bw = new BufferedWriter( | |||||
new OutputStreamWriter(System.out)); | |||||
bw.write(symbols); | |||||
bw.flush(); | |||||
} catch (IOException e) { | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,214 @@ | |||||
package com.bitshift.parsing.parsers; | |||||
import java.util.HashMap; | |||||
import java.util.List; | |||||
import java.util.ArrayList; | |||||
import java.util.Map; | |||||
import java.util.Stack; | |||||
import java.util.Arrays; | |||||
import com.google.common.base.Joiner; | |||||
import org.eclipse.jdt.core.JavaCore; | |||||
import org.eclipse.jdt.core.dom.AST; | |||||
import org.eclipse.jdt.core.dom.ASTNode; | |||||
import org.eclipse.jdt.core.dom.ASTParser; | |||||
import org.eclipse.jdt.core.dom.ASTVisitor; | |||||
import org.eclipse.jdt.core.dom.CompilationUnit; | |||||
import org.eclipse.jdt.core.dom.ClassInstanceCreation; | |||||
import org.eclipse.jdt.core.dom.ImportDeclaration; | |||||
import org.eclipse.jdt.core.dom.MethodDeclaration; | |||||
import org.eclipse.jdt.core.dom.MethodInvocation; | |||||
import org.eclipse.jdt.core.dom.Name; | |||||
import org.eclipse.jdt.core.dom.PackageDeclaration; | |||||
import org.eclipse.jdt.core.dom.QualifiedName; | |||||
import org.eclipse.jdt.core.dom.SimpleName; | |||||
import org.eclipse.jdt.core.dom.Statement; | |||||
import org.eclipse.jdt.core.dom.TypeDeclaration; | |||||
import org.eclipse.jdt.core.dom.VariableDeclarationFragment; | |||||
import com.bitshift.parsing.symbols.Symbols; | |||||
import com.bitshift.parsing.symbols.JavaSymbols; | |||||
/*TODO: Work on parsing partial java code.*/ | |||||
public class JavaParser { | |||||
private String source; | |||||
public JavaParser(String source) { | |||||
this.source = source; | |||||
} | |||||
private Symbols genSymbols() { | |||||
ASTParser parser = ASTParser.newParser(AST.JLS3); | |||||
parser.setSource(this.source.toCharArray()); | |||||
Map options = JavaCore.getOptions(); | |||||
parser.setCompilerOptions(options); | |||||
CompilationUnit root = (CompilationUnit) parser.createAST(null); | |||||
NodeVisitor visitor = new NodeVisitor(root); | |||||
root.accept(visitor); | |||||
return visitor.symbols; | |||||
} | |||||
public String parse() { | |||||
JavaSymbols symbols = (JavaSymbols) this.genSymbols(); | |||||
return symbols.toString(); | |||||
} | |||||
class NodeVisitor extends ASTVisitor { | |||||
protected CompilationUnit root; | |||||
protected JavaSymbols symbols; | |||||
private Stack<HashMap<String, Object>> _cache; | |||||
public NodeVisitor(CompilationUnit root) { | |||||
this.root = root; | |||||
this.symbols = new JavaSymbols(); | |||||
this._cache = new Stack<HashMap<String, Object>>(); | |||||
} | |||||
public ArrayList<Integer> blockPosition(ASTNode node) { | |||||
int sl = this.root.getLineNumber(node.getStartPosition()); | |||||
int sc = this.root.getColumnNumber(node.getStartPosition()) + 1; | |||||
int el = this.root.getLineNumber(node.getStartPosition() | |||||
+ node.getLength() - 1); | |||||
int ec = this.root.getColumnNumber(node.getStartPosition() | |||||
+ node.getLength() - 1) + 1; | |||||
return Symbols.createCoord(sl, sc, el, ec); | |||||
} | |||||
public boolean visit(MethodDeclaration node) { | |||||
HashMap<String, Object> data = new HashMap<String, Object>(); | |||||
Name nameObj = node.getName(); | |||||
String name = nameObj.isQualifiedName() ? | |||||
((QualifiedName) nameObj).getFullyQualifiedName() : | |||||
((SimpleName) nameObj).getIdentifier(); | |||||
data.put("coord", this.blockPosition(node)); | |||||
data.put("name", name); | |||||
this._cache.push(data); | |||||
return true; | |||||
} | |||||
public void endVisit(MethodDeclaration node) { | |||||
HashMap<String, Object> data = this._cache.pop(); | |||||
String name = (String)data.remove("name"); | |||||
this.symbols.insertMethodDeclaration("\"" + name + "\"", data); | |||||
} | |||||
public boolean visit(MethodInvocation node) { | |||||
HashMap<String, Object> data = new HashMap<String, Object>(); | |||||
Name nameObj = node.getName(); | |||||
String name = nameObj.isQualifiedName() ? | |||||
((QualifiedName) nameObj).getFullyQualifiedName() : | |||||
((SimpleName) nameObj).getIdentifier(); | |||||
data.put("coord", this.blockPosition(node)); | |||||
data.put("name", name); | |||||
this._cache.push(data); | |||||
return true; | |||||
} | |||||
public void endVisit(MethodInvocation node) { | |||||
HashMap<String, Object> data = this._cache.pop(); | |||||
String name = (String)data.remove("name"); | |||||
this.symbols.insertMethodInvocation("\"" + name + "\"", data); | |||||
} | |||||
public boolean visit(PackageDeclaration node) { | |||||
HashMap<String, Object> data = new HashMap<String, Object>(); | |||||
this._cache.push(data); | |||||
return true; | |||||
} | |||||
public void endVisit(PackageDeclaration node) { | |||||
HashMap<String, Object> data = this._cache.pop(); | |||||
String name = (String)data.remove("name"); | |||||
this.symbols.setPackage(name); | |||||
} | |||||
public boolean visit(TypeDeclaration node) { | |||||
HashMap<String, Object> data = new HashMap<String, Object>(); | |||||
data.put("coord", this.blockPosition(node)); | |||||
this._cache.push(data); | |||||
return true; | |||||
} | |||||
public void endVisit(TypeDeclaration node) { | |||||
HashMap<String, Object> data = this._cache.pop(); | |||||
String name = (String)data.remove("name"); | |||||
if (node.isInterface()) { | |||||
this.symbols.insertInterfaceDeclaration("\"" + name + "\"", data); | |||||
} else { | |||||
this.symbols.insertClassDeclaration("\"" + name + "\"", data); | |||||
} | |||||
} | |||||
public boolean visit(VariableDeclarationFragment node) { | |||||
HashMap<String, Object> data = new HashMap<String, Object>(); | |||||
data.put("coord", this.blockPosition(node)); | |||||
this._cache.push(data); | |||||
return true; | |||||
} | |||||
public void endVisit(VariableDeclarationFragment node) { | |||||
HashMap<String, Object> data = this._cache.pop(); | |||||
String name = (String)data.remove("name"); | |||||
this.symbols.insertVariableDeclaration("\"" + name + "\"", data); | |||||
} | |||||
public boolean visit(QualifiedName node) { | |||||
if (!this._cache.empty()) { | |||||
HashMap<String, Object> data = this._cache.pop(); | |||||
if(!data.containsKey("name")) { | |||||
String name = node.getFullyQualifiedName(); | |||||
data.put("name", name); | |||||
} | |||||
this._cache.push(data); | |||||
} | |||||
return true; | |||||
} | |||||
public boolean visit(SimpleName node) { | |||||
if (!this._cache.empty()) { | |||||
HashMap<String, Object> data = this._cache.pop(); | |||||
if(!data.containsKey("name")) { | |||||
String name = node.getIdentifier(); | |||||
data.put("name", name); | |||||
} | |||||
this._cache.push(data); | |||||
} | |||||
return true; | |||||
} | |||||
public boolean visit(ImportDeclaration node) { | |||||
HashMap<String, Object> data = new HashMap<String, Object>(); | |||||
data.put("coord", this.blockPosition(node)); | |||||
this._cache.push(data); | |||||
return true; | |||||
} | |||||
public void endVisit(ImportDeclaration node) { | |||||
HashMap<String, Object> data = this._cache.pop(); | |||||
String name = (String)data.remove("name"); | |||||
String[] parts = name.split("\\."); | |||||
for(int i = parts.length; i > 1; i--) { | |||||
String pkg = Joiner.on(".").join(Arrays.copyOfRange(parts, 0, i)); | |||||
this.symbols.insertImportStatement("\"" + pkg + "\"", data); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,71 @@ | |||||
package com.bitshift.parsing.parsers; | |||||
import java.util.Formatter; | |||||
import java.io.BufferedReader; | |||||
import java.io.BufferedWriter; | |||||
import java.io.InputStreamReader; | |||||
import java.io.OutputStreamWriter; | |||||
import java.io.IOException; | |||||
import java.nio.ByteBuffer; | |||||
import java.net.Socket; | |||||
import com.bitshift.parsing.symbols.Symbols; | |||||
public abstract class Parser implements Runnable { | |||||
protected Socket clientSocket; | |||||
private String eos; | |||||
public Parser(Socket clientSocket) { | |||||
this.clientSocket = clientSocket; | |||||
} | |||||
protected String readFromClient() { | |||||
String fromClient = ""; | |||||
try { | |||||
BufferedReader clientReader = new BufferedReader( | |||||
new InputStreamReader(this.clientSocket.getInputStream())); | |||||
int bytes = Integer.parseInt(clientReader.readLine()); | |||||
this.eos = clientReader.readLine(); | |||||
StringBuilder builder = new StringBuilder(); | |||||
int i = 0; | |||||
while(i < bytes) { | |||||
char aux = (char)clientReader.read(); | |||||
builder.append(aux); | |||||
i++; | |||||
} | |||||
fromClient = builder.toString(); | |||||
} catch (IOException ex) { | |||||
} | |||||
return fromClient; | |||||
} | |||||
protected void writeToClient(String toClient) { | |||||
try { | |||||
BufferedWriter clientWriter = new BufferedWriter( | |||||
new OutputStreamWriter(this.clientSocket.getOutputStream())); | |||||
clientWriter.write(toClient); | |||||
clientWriter.write(eos); | |||||
clientWriter.flush(); | |||||
this.clientSocket.close(); | |||||
} catch (IOException ex) { | |||||
} | |||||
} | |||||
protected abstract Symbols genSymbols(); | |||||
public abstract void run(); | |||||
} | |||||
@@ -0,0 +1,177 @@ | |||||
package com.bitshift.parsing.symbols; | |||||
import java.util.HashMap; | |||||
import java.util.ArrayList; | |||||
import com.bitshift.parsing.symbols.Symbols; | |||||
/*TODO: Overwrite toString.*/ | |||||
public class JavaSymbols extends Symbols { | |||||
private String _packageName; | |||||
private HashMap<String, HashMap<String, Object>> _classes; | |||||
private HashMap<String, HashMap<String, Object>> _interfaces; | |||||
private HashMap<String, HashMap<String, Object>> _methods; | |||||
private HashMap<String, HashMap<String, Object>> _vars; | |||||
private HashMap<String, HashMap<String, Object>> _imports; | |||||
private final String assignKey = "\"assignments\""; | |||||
private final String useKey = "\"uses\""; | |||||
public JavaSymbols() { | |||||
_packageName = null; | |||||
_classes = new HashMap<String, HashMap<String, Object>>(); | |||||
_interfaces = new HashMap<String, HashMap<String, Object>>(); | |||||
_methods = new HashMap<String, HashMap<String, Object>>(); | |||||
_vars = new HashMap<String, HashMap<String, Object>>(); | |||||
_imports = new HashMap<String, HashMap<String, Object>>(); | |||||
} | |||||
public boolean setPackage(String name) { | |||||
_packageName = name; | |||||
return true; | |||||
} | |||||
public boolean insertClassDeclaration(String name, HashMap<String, Object> data) { | |||||
ArrayList<Object> assignments = new ArrayList<Object>(10); | |||||
ArrayList<Object> uses = new ArrayList<Object>(10); | |||||
HashMap<String, Object> klass = new HashMap<String, Object>(); | |||||
assignments.add(data.get("coord")); | |||||
klass.put(assignKey, assignments); | |||||
klass.put(useKey, uses); | |||||
this._classes.put(name, klass); | |||||
return true; | |||||
} | |||||
public boolean insertInterfaceDeclaration(String name, HashMap<String, Object> data) { | |||||
ArrayList<Object> assignments = new ArrayList<Object>(10); | |||||
ArrayList<Object> uses = new ArrayList<Object>(10); | |||||
HashMap<String, Object> klass = new HashMap<String, Object>(); | |||||
assignments.add(data.get("coord")); | |||||
klass.put(assignKey, assignments); | |||||
klass.put(useKey, uses); | |||||
this._interfaces.put(name, klass); | |||||
return true; | |||||
} | |||||
public boolean insertMethodDeclaration(String name, HashMap<String, Object> data) { | |||||
HashMap<String, Object> method = this._methods.get(name); | |||||
if (method == null) { | |||||
method = new HashMap<String, Object>(); | |||||
ArrayList<Object> assignments = new ArrayList<Object>(10); | |||||
ArrayList<Object> uses = new ArrayList<Object>(10); | |||||
assignments.add(data.get("coord")); | |||||
method.put(assignKey, assignments); | |||||
method.put(useKey, uses); | |||||
} else { | |||||
ArrayList<Object> assignments = (ArrayList<Object>)method.get(assignKey); | |||||
assignments.add(data.get("coord")); | |||||
method.put(assignKey, assignments); | |||||
} | |||||
this._methods.put(name, method); | |||||
return true; | |||||
} | |||||
public boolean insertMethodInvocation(String name, HashMap<String, Object> data) { | |||||
HashMap<String, Object> method = this._methods.get(name); | |||||
if (method == null) { | |||||
method = new HashMap<String, Object>(); | |||||
ArrayList<Object> assignments = new ArrayList<Object>(10); | |||||
ArrayList<Object> uses = new ArrayList<Object>(10); | |||||
uses.add(data.get("coord")); | |||||
method.put(assignKey, assignments); | |||||
method.put(useKey, uses); | |||||
} else { | |||||
ArrayList<Object> uses = (ArrayList<Object>)method.get(useKey); | |||||
uses.add(data.get("coord")); | |||||
method.put(useKey, uses); | |||||
} | |||||
this._methods.put(name, method); | |||||
return true; | |||||
} | |||||
public boolean insertVariableDeclaration(String name, HashMap<String, Object> data) { | |||||
HashMap<String, Object> var = this._vars.get(name); | |||||
if (var == null) { | |||||
var = new HashMap<String, Object>(); | |||||
ArrayList<Object> assignments = new ArrayList<Object>(10); | |||||
ArrayList<Object> uses = new ArrayList<Object>(10); | |||||
assignments.add(data.get("coord")); | |||||
var.put(assignKey, assignments); | |||||
var.put(useKey, uses); | |||||
} else { | |||||
ArrayList<Object> assignments = (ArrayList<Object>)var.get(assignKey); | |||||
assignments.add(data.get("coord")); | |||||
var.put(assignKey, assignments); | |||||
} | |||||
this._vars.put(name, var); | |||||
return true; | |||||
} | |||||
public boolean insertVariableAccess(String name, HashMap<String, Object> data) { | |||||
HashMap<String, Object> var = this._vars.get(name); | |||||
if (var == null) { | |||||
var = new HashMap<String, Object>(); | |||||
ArrayList<Object> assignments = new ArrayList<Object>(10); | |||||
ArrayList<Object> uses = new ArrayList<Object>(10); | |||||
uses.add(data.get("coord")); | |||||
var.put(assignKey, assignments); | |||||
var.put(useKey, uses); | |||||
} else { | |||||
ArrayList<Object> uses = (ArrayList<Object>)var.get(useKey); | |||||
uses.add(data.get("coord")); | |||||
var.put(useKey, uses); | |||||
} | |||||
this._vars.put(name, var); | |||||
return true; | |||||
} | |||||
public boolean insertImportStatement(String name, HashMap<String, Object> data) { | |||||
HashMap<String, Object> lib = this._imports.get(name); | |||||
if (lib == null) { | |||||
lib = new HashMap<String, Object>(); | |||||
ArrayList<Object> assignments = new ArrayList<Object>(10); | |||||
ArrayList<Object> uses = new ArrayList<Object>(10); | |||||
uses.add(data.get("coord")); | |||||
lib.put(assignKey, assignments); | |||||
lib.put(useKey, uses); | |||||
} else { | |||||
ArrayList<Object> uses = (ArrayList<Object>)lib.get(useKey); | |||||
uses.add(data.get("coord")); | |||||
lib.put(useKey, uses); | |||||
} | |||||
this._imports.put(name, lib); | |||||
return true; | |||||
} | |||||
public String toString() { | |||||
StringBuilder builder = new StringBuilder(); | |||||
builder.append("\"classes\":" + this._classes + ","); | |||||
builder.append("\"interfaces\":" + this._interfaces + ","); | |||||
builder.append("\"functions\":" + this._methods + ","); | |||||
builder.append("\"vars\":" + this._vars + ","); | |||||
builder.append("\"imports\":" + this._imports + ","); | |||||
String s = builder.toString().replaceAll("=", ":"); | |||||
s = s.substring(0, s.length() - 1); | |||||
return "{" + s + "}"; | |||||
} | |||||
} | |||||
@@ -0,0 +1,17 @@ | |||||
package com.bitshift.parsing.symbols; | |||||
import java.util.ArrayList; | |||||
public abstract class Symbols { | |||||
public Symbols() { | |||||
} | |||||
public static ArrayList<Integer> createCoord(Integer startLine, Integer startCol, Integer endLine, Integer endCol) { | |||||
ArrayList<Integer> coord = new ArrayList<Integer>(4); | |||||
coord.add(startLine); coord.add(startCol); coord.add(endLine); coord.add(endCol); | |||||
return coord; | |||||
} | |||||
} |
@@ -0,0 +1,4 @@ | |||||
source 'https://rubygems.org' | |||||
gem 'ruby_parser' | |||||
gem 'sexp_processor' |
@@ -0,0 +1,6 @@ | |||||
require 'pp' | |||||
require File.expand_path('../lib/parser.rb', __FILE__) | |||||
task :parse do |t| | |||||
parse | |||||
end |
@@ -0,0 +1,137 @@ | |||||
require 'ripper' | |||||
def parse | |||||
source = STDIN.read | |||||
walker = TreeWalker.new(source) | |||||
walker.parse | |||||
puts walker.to_s | |||||
end | |||||
class TreeWalker < Ripper::SexpBuilder | |||||
attr_accessor :symbols | |||||
def initialize(source) | |||||
ns_hash = Hash.new { | |||||
|hash, key| | |||||
hash[key] = { | |||||
:assignments => [], :uses => [] | |||||
} | |||||
} | |||||
class_hash = ns_hash.clone | |||||
function_hash = ns_hash.clone | |||||
var_hash = ns_hash.clone | |||||
@symbols = { | |||||
:namespaces => ns_hash, | |||||
:classes => class_hash, | |||||
:functions => function_hash, | |||||
:vars => var_hash | |||||
} | |||||
super(source) | |||||
end | |||||
def block_position(node) | |||||
last_node = node[0] | |||||
while last_node.is_a? Array | |||||
sp = last_node | |||||
while not (last_el = last_node[last_node.count - 1]) or | |||||
(last_el.is_a? Array and last_el[last_el.count - 1].nil?) | |||||
last_node = last_node[0..last_node.count - 2] | |||||
end | |||||
last_node = last_el | |||||
end | |||||
last_node = node[0] | |||||
while last_node.is_a? Array | |||||
ep = last_node | |||||
while not (last_el = last_node[last_node.count - 1]) or | |||||
(last_el.is_a? Array and last_el[last_el.count - 1].nil?) | |||||
last_node = last_node[0..last_node.count - 2] | |||||
end | |||||
last_node = last_el | |||||
end | |||||
if sp == ep | |||||
return sp + [sp[0], -1] | |||||
end | |||||
return sp + ep | |||||
end | |||||
def on_module(*node) | |||||
pos = block_position(node) | |||||
name = node[0][1][1] | |||||
symbols[:namespaces][name][:assignments] << pos | |||||
return node | |||||
end | |||||
def on_class(*node) | |||||
pos = block_position(node) | |||||
name = node[0][1][1] | |||||
symbols[:classes][name][:assignments] << pos | |||||
return node | |||||
end | |||||
def on_def(*node) | |||||
pos = block_position(node) | |||||
name = node[0][1] | |||||
symbols[:functions][name][:assignments] << pos | |||||
return node | |||||
end | |||||
def on_call(*node) | |||||
pos = block_position(node) | |||||
name = node[node.count - 1][1] | |||||
symbols[:functions][name][:uses] << pos | |||||
return node | |||||
end | |||||
def on_vcall(*node) | |||||
pos = block_position(node) | |||||
name = node[0][1] | |||||
symbols[:functions][name][:uses] << pos | |||||
return node | |||||
end | |||||
def on_assign(*node) | |||||
pos = block_position(node) | |||||
return node if not node[0][0].is_a? Array | |||||
name = node[0][0][1] | |||||
symbols[:vars][name][:assignments] << pos | |||||
return node | |||||
end | |||||
def on_var_field(*node) | |||||
pos = block_position(node) | |||||
name = node[0][1] | |||||
symbols[:vars][name][:uses] << pos | |||||
return node | |||||
end | |||||
def on_var_ref(*node) | |||||
pos = block_position(node) | |||||
name = node[0][1] | |||||
symbols[:vars][name][:uses] << pos | |||||
return node | |||||
end | |||||
def on_command(*node) | |||||
# catch require statements | |||||
end | |||||
def to_s | |||||
new_symbols = Hash.new {|hash, key| hash[key] = Hash.new} | |||||
symbols.each do |type, sym_list| | |||||
sym_list.each do |name, sym| | |||||
new_symbols[type.to_s][name.to_s] = { | |||||
"assignments" => sym[:assignments], | |||||
"uses" => sym[:uses]} | |||||
end | |||||
end | |||||
str = new_symbols.to_s | |||||
str = str.gsub(/=>/, ":") | |||||
return str | |||||
end | |||||
end |
@@ -0,0 +1,14 @@ | |||||
from setuptools import setup, find_packages | |||||
setup( | |||||
name = "bitshift", | |||||
version = "0.1.dev", | |||||
packages = find_packages(), | |||||
install_requires = [ | |||||
"Flask>=0.10.1", "gunicorn>=18.0", "pygments>=1.6", "requests>=2.2.0", | |||||
"GitPython>=0.3.2.RC1", "beautifulsoup4>=3.2.1", "oursql>=0.9.3.1", | |||||
"mmh3>=2.3", "PyYAML>=3.11", "python-dateutil>=2.2", "cchardet>=0.3.5"], | |||||
author = "Benjamin Attal, Ben Kurtovic, Severyn Kozak", | |||||
license = "MIT", | |||||
url = "https://github.com/earwig/bitshift" | |||||
) |
@@ -0,0 +1,65 @@ | |||||
td.linenos { background: rgba(65,131,196,0.05); padding-right: 10px; border-right: 1px solid #bbb; } | |||||
span.lineno { background: rgba(65,131,196,0.05); padding: 0 5px 0 5px; } | |||||
pre { line-height: 125% } | |||||
.highlighttable { background-color: #fff; padding-left: 10px; width: inherit; height: inherit; } | |||||
.hll { display: block } | |||||
.c { color: #999988; font-style: italic } /* Comment */ | |||||
.err { color: #a61717; background-color: #e3d2d2 } /* Error */ | |||||
.k { color: #000000; font-weight: bold } /* Keyword */ | |||||
.o { color: #000000; font-weight: bold } /* Operator */ | |||||
.cm { color: #999988; font-style: italic } /* Comment.Multiline */ | |||||
.cp { color: #999999; font-weight: bold; font-style: italic } /* Comment.Preproc */ | |||||
.c1 { color: #999988; font-style: italic } /* Comment.Single */ | |||||
.cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ | |||||
.gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ | |||||
.ge { color: #000000; font-style: italic } /* Generic.Emph */ | |||||
.gr { color: #aa0000 } /* Generic.Error */ | |||||
.gh { color: #999999 } /* Generic.Heading */ | |||||
.gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ | |||||
.go { color: #888888 } /* Generic.Output */ | |||||
.gp { color: #555555 } /* Generic.Prompt */ | |||||
.gs { font-weight: bold } /* Generic.Strong */ | |||||
.gu { color: #aaaaaa } /* Generic.Subheading */ | |||||
.gt { color: #aa0000 } /* Generic.Traceback */ | |||||
.kc { color: #000000; font-weight: bold } /* Keyword.Constant */ | |||||
.kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ | |||||
.kn { color: #000000; font-weight: bold } /* Keyword.Namespace */ | |||||
.kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ | |||||
.kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ | |||||
.kt { color: #445588; font-weight: bold } /* Keyword.Type */ | |||||
.m { color: #009999 } /* Literal.Number */ | |||||
.s { color: #d01040 } /* Literal.String */ | |||||
.na { color: #008080 } /* Name.Attribute */ | |||||
.nb { color: #0086B3 } /* Name.Builtin */ | |||||
.nc { color: #445588; font-weight: bold } /* Name.Class */ | |||||
.no { color: #008080 } /* Name.Constant */ | |||||
.nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */ | |||||
.ni { color: #800080 } /* Name.Entity */ | |||||
.ne { color: #990000; font-weight: bold } /* Name.Exception */ | |||||
.nf { color: #990000; font-weight: bold } /* Name.Function */ | |||||
.nl { color: #990000; font-weight: bold } /* Name.Label */ | |||||
.nn { color: #555555 } /* Name.Namespace */ | |||||
.nt { color: #000080 } /* Name.Tag */ | |||||
.nv { color: #008080 } /* Name.Variable */ | |||||
.ow { color: #000000; font-weight: bold } /* Operator.Word */ | |||||
.w { color: #bbbbbb } /* Text.Whitespace */ | |||||
.mf { color: #009999 } /* Literal.Number.Float */ | |||||
.mh { color: #009999 } /* Literal.Number.Hex */ | |||||
.mi { color: #009999 } /* Literal.Number.Integer */ | |||||
.mo { color: #009999 } /* Literal.Number.Oct */ | |||||
.sb { color: #d01040 } /* Literal.String.Backtick */ | |||||
.sc { color: #d01040 } /* Literal.String.Char */ | |||||
.sd { color: #d01040 } /* Literal.String.Doc */ | |||||
.s2 { color: #d01040 } /* Literal.String.Double */ | |||||
.se { color: #d01040 } /* Literal.String.Escape */ | |||||
.sh { color: #d01040 } /* Literal.String.Heredoc */ | |||||
.si { color: #d01040 } /* Literal.String.Interpol */ | |||||
.sx { color: #d01040 } /* Literal.String.Other */ | |||||
.sr { color: #009926 } /* Literal.String.Regex */ | |||||
.s1 { color: #d01040 } /* Literal.String.Single */ | |||||
.ss { color: #990073 } /* Literal.String.Symbol */ | |||||
.bp { color: #999999 } /* Name.Builtin.Pseudo */ | |||||
.vc { color: #008080 } /* Name.Variable.Class */ | |||||
.vg { color: #008080 } /* Name.Variable.Global */ | |||||
.vi { color: #008080 } /* Name.Variable.Instance */ | |||||
.il { color: #009999 } /* Literal.Number.Integer.Long */ |
@@ -0,0 +1,64 @@ | |||||
td.linenos { background-color: #f0f0f0; padding-right: 10px; } | |||||
span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } | |||||
pre { line-height: 125% } | |||||
.highlighttable { background-color: #49483e; width: inherit; height: inherit; } | |||||
.hll { display: block } | |||||
{ background: #272822; color: #f8f8f2 } | |||||
.c { color: #75715e } /* Comment */ | |||||
.err { color: #960050; background-color: #1e0010 } /* Error */ | |||||
.k { color: #66d9ef } /* Keyword */ | |||||
.l { color: #ae81ff } /* Literal */ | |||||
.n { color: #f8f8f2 } /* Name */ | |||||
.o { color: #f92672 } /* Operator */ | |||||
.p { color: #f8f8f2 } /* Punctuation */ | |||||
.cm { color: #75715e } /* Comment.Multiline */ | |||||
.cp { color: #75715e } /* Comment.Preproc */ | |||||
.c1 { color: #75715e } /* Comment.Single */ | |||||
.cs { color: #75715e } /* Comment.Special */ | |||||
.ge { font-style: italic } /* Generic.Emph */ | |||||
.gs { font-weight: bold } /* Generic.Strong */ | |||||
.kc { color: #66d9ef } /* Keyword.Constant */ | |||||
.kd { color: #66d9ef } /* Keyword.Declaration */ | |||||
.kn { color: #f92672 } /* Keyword.Namespace */ | |||||
.kp { color: #66d9ef } /* Keyword.Pseudo */ | |||||
.kr { color: #66d9ef } /* Keyword.Reserved */ | |||||
.kt { color: #66d9ef } /* Keyword.Type */ | |||||
.ld { color: #e6db74 } /* Literal.Date */ | |||||
.m { color: #ae81ff } /* Literal.Number */ | |||||
.s { color: #e6db74 } /* Literal.String */ | |||||
.na { color: #a6e22e } /* Name.Attribute */ | |||||
.nb { color: #f8f8f2 } /* Name.Builtin */ | |||||
.nc { color: #a6e22e } /* Name.Class */ | |||||
.no { color: #66d9ef } /* Name.Constant */ | |||||
.nd { color: #a6e22e } /* Name.Decorator */ | |||||
.ni { color: #f8f8f2 } /* Name.Entity */ | |||||
.ne { color: #a6e22e } /* Name.Exception */ | |||||
.nf { color: #a6e22e } /* Name.Function */ | |||||
.nl { color: #f8f8f2 } /* Name.Label */ | |||||
.nn { color: #f8f8f2 } /* Name.Namespace */ | |||||
.nx { color: #a6e22e } /* Name.Other */ | |||||
.py { color: #f8f8f2 } /* Name.Property */ | |||||
.nt { color: #f92672 } /* Name.Tag */ | |||||
.nv { color: #f8f8f2 } /* Name.Variable */ | |||||
.ow { color: #f92672 } /* Operator.Word */ | |||||
.w { color: #f8f8f2 } /* Text.Whitespace */ | |||||
.mf { color: #ae81ff } /* Literal.Number.Float */ | |||||
.mh { color: #ae81ff } /* Literal.Number.Hex */ | |||||
.mi { color: #ae81ff } /* Literal.Number.Integer */ | |||||
.mo { color: #ae81ff } /* Literal.Number.Oct */ | |||||
.sb { color: #e6db74 } /* Literal.String.Backtick */ | |||||
.sc { color: #e6db74 } /* Literal.String.Char */ | |||||
.sd { color: #e6db74 } /* Literal.String.Doc */ | |||||
.s2 { color: #e6db74 } /* Literal.String.Double */ | |||||
.se { color: #ae81ff } /* Literal.String.Escape */ | |||||
.sh { color: #e6db74 } /* Literal.String.Heredoc */ | |||||
.si { color: #e6db74 } /* Literal.String.Interpol */ | |||||
.sx { color: #e6db74 } /* Literal.String.Other */ | |||||
.sr { color: #e6db74 } /* Literal.String.Regex */ | |||||
.s1 { color: #e6db74 } /* Literal.String.Single */ | |||||
.ss { color: #e6db74 } /* Literal.String.Symbol */ | |||||
.bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ | |||||
.vc { color: #f8f8f2 } /* Name.Variable.Class */ | |||||
.vg { color: #f8f8f2 } /* Name.Variable.Global */ | |||||
.vi { color: #f8f8f2 } /* Name.Variable.Instance */ | |||||
.il { color: #ae81ff } /* Literal.Number.Integer.Long */ |
@@ -1,4 +0,0 @@ | |||||
/* Global project stylesheet. | |||||
*/ | |||||
p { | |||||
font-size: 1.5em; } |
@@ -0,0 +1 @@ | |||||
google-site-verification: google10335120a3066831.html |
@@ -0,0 +1,19 @@ | |||||
/* | |||||
* @file Implements a parallax effect on the about page. | |||||
*/ | |||||
var lastVertPos = $(window).scrollTop(); | |||||
/* | |||||
* Scroll `div#img-[1-4]` at a greater speed than the text, producing a | |||||
* parallax effect. | |||||
*/ | |||||
$(window).scroll(function(e){ | |||||
var currVertPos = $(window).scrollTop(); | |||||
var delta = currVertPos - lastVertPos; | |||||
$(".bg").each(function(){ | |||||
$(this).css("top", parseFloat($(this).css("top")) - | |||||
delta * $(this).attr("speed") + "px"); | |||||
}); | |||||
lastVertPos = currVertPos; | |||||
}); |
@@ -0,0 +1,175 @@ | |||||
/* | |||||
* @file Manages all advanced search form logic. | |||||
*/ | |||||
var searchGroups = $("div#search-groups"); | |||||
/* | |||||
* Load all advanced search form libraries. | |||||
*/ | |||||
function loadInputFieldWidgets(){ | |||||
$(".search-group input#date-last-modified").datepicker(); | |||||
$(".search-group input#date-created").datepicker(); | |||||
$(".search-group input#autocomplete").autocomplete({ | |||||
source: function(request, response){ | |||||
var matcher = new RegExp( | |||||
$.ui.autocomplete.escapeRegex(request.term), "i"); | |||||
response($.grep(AUTOCOMPLETE_LANGUAGES, function(item){ | |||||
return matcher.test(item); | |||||
})); | |||||
} | |||||
}); | |||||
}; | |||||
loadInputFieldWidgets(); | |||||
/* | |||||
* Set all advanced search form button callbacks. | |||||
*/ | |||||
(function setSearchFormCallbacks(){ | |||||
// Create a new search group, and update the `#sidebar` checklist. | |||||
$("button#add-group").click(function(){ | |||||
$("div#sidebar input[type=checkbox]").prop("checked", false); | |||||
searchGroups.children("#selected").removeAttr("id"); | |||||
var searchGroup = $("<div/>", { | |||||
class : "search-group", | |||||
id : "selected" | |||||
}); | |||||
searchGroups.append( | |||||
searchGroup.append(createSearchGroupInput("language", "languages"))); | |||||
loadInputFieldWidgets(); | |||||
$("div#sidebar input[type=checkbox]#language").prop("checked", true); | |||||
searchGroups[0].scrollTop = searchGroups[0].scrollHeight; | |||||
}); | |||||
// Remove the currently selected group if it's not the only one, and mark | |||||
// one of its siblings as selected. | |||||
$("button#remove-group").click(function(){ | |||||
var currentGroup = $("div.search-group#selected"); | |||||
if($("div.search-group").length == 1) | |||||
return; | |||||
else { | |||||
var nextGroup = currentGroup.prev(); | |||||
if(nextGroup.size() == 0) | |||||
nextGroup = currentGroup.next(); | |||||
} | |||||
currentGroup.remove(); | |||||
nextGroup.click(); | |||||
}); | |||||
// Select a search group, and update the `#sidebar` checklist accordingly. | |||||
$(document).on("click", "div.search-group", function(){ | |||||
searchGroups.children("#selected").removeAttr("id"); | |||||
$(this).attr("id", "selected"); | |||||
$("div#sidebar input[type=checkbox]").prop("checked", false); | |||||
$(this).find("input[type=text]").each(function(){ | |||||
var checkBoxSelector = "div#sidebar input[type=checkbox]"; | |||||
$(checkBoxSelector + "#" + $(this).attr("class").split(" ")[0]). | |||||
prop("checked", true); | |||||
}) | |||||
}); | |||||
// Toggle the presence of an input field. | |||||
$("div#sidebar input[type=checkbox]").click(function(){ | |||||
var fieldId = $(this).prop("id"); | |||||
if($(this).is(":checked")){ | |||||
$("div.search-group#selected").append( | |||||
$.parseHTML(createSearchGroupInput( | |||||
fieldId, $(this).next("label").children("div"). | |||||
text()))); | |||||
loadInputFieldWidgets(); | |||||
if(fieldId.slice(0, 4) == "date") | |||||
$(".search-group#selected ." + fieldId).datepicker(); | |||||
} | |||||
else { | |||||
if($(".search-group#selected").children("div").length > 1) | |||||
$(".search-group#selected #" + fieldId).remove() | |||||
else | |||||
$(this).prop("checked", true); | |||||
} | |||||
searchGroups[0].scrollTop = searchGroups[0].scrollHeight; | |||||
}); | |||||
$("div#advanced-search button#submit").click(function(){ | |||||
$("div#advanced-search").hide(); | |||||
advancedSearchButton.removeClass("clicked"); | |||||
assembleQuery(); | |||||
populateResults(); | |||||
}) | |||||
var previousAdvancedQuery = ""; | |||||
var searchBar = $("form#search-bar input[name=query]"); | |||||
window.setInterval(function(){ | |||||
var currentQuery = assembleQuery(); | |||||
if(currentQuery != previousAdvancedQuery){ | |||||
previousAdvancedQuery = currentQuery; | |||||
searchBar.val(assembleQuery()); | |||||
} | |||||
}, 1e3 / 15); | |||||
}()); | |||||
/* | |||||
* Return an HTML string representing a new input field div in a search group. | |||||
* | |||||
* @param fieldId The id of the input field div, and its child elements. | |||||
* @param name The name to display next to the input field. | |||||
*/ | |||||
function createSearchGroupInput(fieldId, name){ | |||||
var fieldHTML = [ | |||||
"<div id='" + fieldId + "'>", | |||||
"<div class='name'>" + name + "</div>", | |||||
"<input class='" + fieldId + "' name='" + fieldId + "'type='text'>", | |||||
"<input type='checkbox' name='regex'>", | |||||
"<span class='regex'>Regex</span>", | |||||
"</div>" | |||||
] | |||||
if(fieldId == "language") | |||||
fieldHTML[2] = [ | |||||
"<input id='autocomplete' class='language'", | |||||
"name='language' type='text'>" | |||||
].join(" "); | |||||
return fieldHTML.join(""); | |||||
} | |||||
/* | |||||
* Create a query from advanced-search groups. | |||||
*/ | |||||
function assembleQuery(){ | |||||
var groups = searchGroups.children(".search-group"); | |||||
var groupQueries = []; | |||||
for(var group = 0; group < groups.length; group++){ | |||||
var inputFields = groups[group].querySelectorAll("input[type=text]"); | |||||
var regexCheckbox = groups[group].querySelectorAll("input[name=regex]"); | |||||
var groupQuery = []; | |||||
for(var field = 0; field < inputFields.length; field++) | |||||
if(inputFields[field].value.length > 0) | |||||
groupQuery.push(genFieldQueryString( | |||||
inputFields[field], regexCheckbox[field].checked)); | |||||
if(groupQuery.length > 0) | |||||
groupQueries.push(groupQuery.join(" AND ")); | |||||
} | |||||
return groupQueries.join(" OR "); | |||||
} | |||||
/* | |||||
* Generate a processed query string for an input field's value. | |||||
* | |||||
* @param field (DOM element) An `input[type=text]` element. | |||||
* @param hasRegex (boolean) Whether or not the field's value has regex. | |||||
* | |||||
* @return The processed query. | |||||
*/ | |||||
function genFieldQueryString(field, hasRegex){ | |||||
var terms = field.value.replace(/\\/g, "\\\\").replace(/\"/g, "\\\""); | |||||
var query = field.getAttribute("name") + ":" + (hasRegex?"re:":"") + terms; | |||||
return '"' + query + '"'; | |||||
} |
@@ -0,0 +1,447 @@ | |||||
/* | |||||
* @file Manages all library initialization, jQuery callbacks, query entry | |||||
* callbacks, server querying, and results diplay for `index.html`. | |||||
*/ | |||||
var advancedSearchDiv = $("div#advanced-search"); | |||||
var advancedSearchButton = $("button#advanced-search"); | |||||
FINISH_TYPING_INTERVAL = 650; | |||||
var searchBar = $("form#search-bar input[type='text']")[0]; | |||||
var resultsDiv = $("div#results")[0]; | |||||
var typingTimer, scrollTimer, lastValue; | |||||
var searchResultsPage = 1; | |||||
/* | |||||
* Set all page callbacks. | |||||
*/ | |||||
(function setHomePageCallbabacks(){ | |||||
var results = $('#results').get(0); | |||||
// Enable infinite scrolling down the results page. | |||||
$(window).scroll(function(){ | |||||
if($(window).scrollTop() + $(window).height() == $(document).height() && | |||||
resultsDiv.querySelectorAll(".result").length > 0) | |||||
loadMoreResults(); | |||||
clearTimeout(scrollTimer); | |||||
if (!results.classList.contains('disable-hover')) | |||||
results.classList.add('disable-hover') | |||||
scrollTimer = setTimeout(function(){ | |||||
if (results.classList.contains('disable-hover')) | |||||
results.classList.remove('disable-hover'); | |||||
}, 200); | |||||
}); | |||||
// Toggle the advanced-search form's visibility. | |||||
advancedSearchButton.click(function(){ | |||||
var searchField = $("div#search-field"); | |||||
if(!advancedSearchDiv.hasClass("visible")){ | |||||
searchField.addClass("partly-visible"); | |||||
advancedSearchDiv.fadeIn(500).addClass("visible"); | |||||
advancedSearchButton.addClass("clicked"); | |||||
} | |||||
else { | |||||
advancedSearchDiv.hide().removeClass("visible"); | |||||
advancedSearchButton.removeClass("clicked"); | |||||
if($("div#results .result").length == 0) | |||||
searchField.removeClass("partly-visible"); | |||||
clearResults(); | |||||
} | |||||
}); | |||||
// Enable capturing the `enter` key. | |||||
$("form#search-bar").submit(function(event){ | |||||
event.preventDefault(); | |||||
return false; | |||||
}); | |||||
searchBar.onkeyup = typingTimer; | |||||
}()); | |||||
/* | |||||
* Set keyboard shortcut mappings. | |||||
*/ | |||||
(function resultsHotkeys(){ | |||||
/* | |||||
* If the currently viewed result is not the first, scroll to the previous | |||||
* result. | |||||
*/ | |||||
var previousResult = function(){ | |||||
var currResult = $(".display-all"); | |||||
if(currResult.length) { | |||||
currResult.removeClass("display-all"); | |||||
currResult = currResult.closest(".result").prev(".result"); | |||||
} else { | |||||
currResult = $(document.querySelectorAll(".result")[0]); | |||||
} | |||||
currResult.addClass("display-all"); | |||||
currResult.each(function(){ | |||||
$('html,body').stop().animate({ | |||||
scrollTop: $(this).offset().top - ( | |||||
$(window).height() - $(this).outerHeight(true)) / 2 | |||||
}, 140); | |||||
}); | |||||
}; | |||||
/* | |||||
* If the currently viewed result is not the last, scroll to the next | |||||
* result. | |||||
*/ | |||||
var nextResult = function(){ | |||||
var currResult = $(".display-all"); | |||||
if(currResult.length) { | |||||
currResult.removeClass("display-all"); | |||||
currResult = currResult.closest(".result").next(".result"); | |||||
} else { | |||||
currResult = $(document.querySelectorAll(".result")[0]); | |||||
} | |||||
currResult.addClass('display-all'); | |||||
currResult.each(function(){ | |||||
$('html,body').stop().animate({ | |||||
scrollTop: $(this).offset().top - ( | |||||
$(window).height() - $(this).outerHeight(true)) / 2 | |||||
}, 140); | |||||
}); | |||||
}; | |||||
var displayHotkeyHelp = function(){ | |||||
var help = $("div#hotkey-help"); | |||||
if(help.hasClass("hidden")) | |||||
help.fadeIn(420); | |||||
else | |||||
help.fadeOut(420); | |||||
$("div#body").toggleClass("faded"); | |||||
help.toggleClass("hidden"); | |||||
} | |||||
var hotkeyActions = { | |||||
"k" : previousResult, | |||||
"j" : nextResult, | |||||
"h" : previousSymbolMatch, | |||||
"l" : nextSymbolMatch, | |||||
"?" : displayHotkeyHelp | |||||
}; | |||||
$(window).keypress(function(key){ | |||||
for(var hotkey in hotkeyActions){ | |||||
var keyChar = String.fromCharCode(key.keyCode); | |||||
if(keyChar == hotkey && | |||||
!($(key.target).is("textarea") || $(key.target).is("input"))) | |||||
hotkeyActions[keyChar](); | |||||
} | |||||
}); | |||||
}()); | |||||
// Enable infinite scrolling down the results page. | |||||
$(window).scroll(function() { | |||||
var searchField = $("div#search-field"); | |||||
if($(window).scrollTop() + $(window).height() == $(document).height() && | |||||
searchField.hasClass('partly-visible')){ | |||||
loadMoreResults(); | |||||
} | |||||
}); | |||||
/* | |||||
* Clear the existing timer and set a new one the the user types text into the | |||||
* search bar. | |||||
*/ | |||||
function typingTimer(event){ | |||||
clearTimeout(typingTimer); | |||||
var enterKeyCode = 13; | |||||
if(event.keyCode != enterKeyCode){ | |||||
if(lastValue != searchBar.value) | |||||
typingTimer = setTimeout(finishedTyping, FINISH_TYPING_INTERVAL); | |||||
} | |||||
else { | |||||
event.preventDefault(); | |||||
finishedTyping(); | |||||
return false; | |||||
} | |||||
}; | |||||
/* | |||||
* Callback which queries the server whenver the user stops typing. | |||||
* | |||||
* Whenever the user doesn't type for a `FINISH_TYPING_INTERVAL` after having | |||||
* entered new text in the search bar, send the current query request to the | |||||
* server. | |||||
*/ | |||||
function finishedTyping(){ | |||||
lastValue = searchBar.value; | |||||
var searchField = $("div#search-field"); | |||||
clearResults(); | |||||
if(searchBar.value){ | |||||
searchField.addClass("partly-visible"); | |||||
populateResults(); | |||||
} | |||||
else { | |||||
searchField.removeClass("partly-visible"); | |||||
$("div#advanced-search").fadeOut(50); | |||||
advancedSearchButton.removeClass("clicked"); | |||||
clearResults(); | |||||
} | |||||
} | |||||
/* | |||||
* Removes any child elements of `div#results`. | |||||
*/ | |||||
function clearResults(){ | |||||
while(resultsDiv.firstChild) | |||||
resultsDiv.removeChild(resultsDiv.firstChild); | |||||
} | |||||
/* | |||||
* Create a result element based upon a codelet instance. | |||||
* | |||||
* @return {Element} The result element. | |||||
*/ | |||||
function createResult(codelet) { | |||||
var maxAttributeLength = 20; | |||||
//Level 1 | |||||
var newDiv = document.createElement("div"), | |||||
table = document.createElement("table"), | |||||
row = document.createElement("tr"); | |||||
//Level 2 | |||||
var displayInfo = document.createElement("div"), | |||||
codeElt = document.createElement("td"), | |||||
hiddenInfoContainer = document.createElement("td"), | |||||
hiddenInfo = document.createElement("div"), | |||||
cycle = document.createElement("div"); | |||||
//Level 3 | |||||
var title = document.createElement("span"), | |||||
site = document.createElement("span"), | |||||
nextMatch = document.createElement("a"), | |||||
prevMatch = document.createElement("a"), | |||||
dateModified = document.createElement("div"), | |||||
language = document.createElement("div"), | |||||
dateCreated = document.createElement("div"), | |||||
authors = document.createElement("div"); | |||||
//Classes and ID's | |||||
newDiv.classList.add('result'); | |||||
displayInfo.id = 'display-info'; | |||||
codeElt.id = 'code'; | |||||
hiddenInfo.id = 'hidden-info'; | |||||
cycle.id = 'cycle-matches' | |||||
title.id = 'title'; | |||||
site.id = 'site'; | |||||
nextMatch.id = 'next-match'; | |||||
nextMatch.href = '#'; | |||||
prevMatch.id = 'prev-match'; | |||||
prevMatch.href = '#'; | |||||
dateModified.id = 'date-modified'; | |||||
language.id = 'language'; | |||||
dateCreated.id = 'date-created'; | |||||
authors.id = 'authors'; | |||||
//Add the bulk of the html | |||||
title.innerHTML = ' » <a href="' + codelet.url + '">' | |||||
+ codelet.name + '</a>'; | |||||
site.innerHTML = '<a href="' + codelet.origin[1] + '">' + | |||||
codelet.origin[0] +'</a>'; | |||||
nextMatch.innerHTML = 'next match'; | |||||
prevMatch.innerHTML = 'prev match'; | |||||
language.innerHTML = 'Language: <span>' + codelet.lang + '</span>'; | |||||
dateModified.innerHTML = 'Last modified: <span>' + codelet.modified + | |||||
'</span>'; | |||||
// Needs to be changed from int to string on the server | |||||
dateCreated.innerHTML = 'Created: <span>' + | |||||
codelet.created.substring(0, maxAttributeLength) + '</span>'; | |||||
var authorsHtml = 'Authors: <span>'; | |||||
var currLength = 0; | |||||
var authorsList = []; | |||||
for(var auth = 0; auth < codelet.authors.length; auth++){ | |||||
currLength += codelet.authors[auth].length; | |||||
if(6 < currLength){ | |||||
authorsList.push("..."); | |||||
break; | |||||
} | |||||
else | |||||
authorsList.push('<a href=#>' + codelet.authors[auth] + '</a>'); | |||||
} | |||||
authors.innerHTML = "Authors: <span>" + authorsList.join(" ") + "</span>"; | |||||
// Needs to be processed on the server | |||||
codeElt.innerHTML = '<div id=tablecontainer>' + codelet.code + '</div>'; | |||||
//Event binding | |||||
$(newDiv).on('mousemove', function(e) { | |||||
var holdCondition = $('.disable-hover'); | |||||
if(holdCondition.length == 0) { | |||||
$(this).siblings().removeClass('display-all'); | |||||
$(this).addClass('display-all'); | |||||
} | |||||
}); | |||||
$(newDiv).on('mouseleave', function(e) { | |||||
var holdCondition = $('.disable-hover'); | |||||
if(holdCondition.length == 0) | |||||
$(this).removeClass('display-all'); | |||||
}); | |||||
$(nextMatch).click(function(e) { | |||||
e.stopPropagation(); | |||||
e.preventDefault(); | |||||
nextSymbolMatch(); | |||||
}); | |||||
$(prevMatch).click(function(e) { | |||||
e.stopPropagation(); | |||||
e.preventDefault(); | |||||
previousSymbolMatch(); | |||||
}); | |||||
//Finish and append elements to parent elements | |||||
hiddenInfo.appendChild(dateCreated); | |||||
hiddenInfo.appendChild(dateModified); | |||||
hiddenInfo.appendChild(language); | |||||
hiddenInfo.appendChild(authors); | |||||
hiddenInfoContainer.appendChild(hiddenInfo); | |||||
row.appendChild(codeElt); | |||||
row.appendChild(hiddenInfoContainer); | |||||
table.appendChild(row); | |||||
displayInfo.appendChild(site); | |||||
displayInfo.appendChild(title); | |||||
cycle.appendChild(prevMatch); | |||||
cycle.appendChild(nextMatch); | |||||
newDiv.appendChild(displayInfo); | |||||
newDiv.appendChild(table); | |||||
return newDiv; | |||||
} | |||||
function previousSymbolMatch() { | |||||
var currResult = $(".display-all"), | |||||
currMatch = currResult.find(".hll.current"), | |||||
matches = currResult.find(".hll"), | |||||
scrollDiv = currResult.find("#tablecontainer"); | |||||
if (currMatch.length == 0) | |||||
currMatch = matches[0]; | |||||
else | |||||
currMatch.removeClass('current'); | |||||
var index = matches.index(currMatch.get(0)) - 1; | |||||
index = index <= 0 ? matches.length - 1 : index; | |||||
var newMatch = $(matches[index]); | |||||
scrollDiv.scrollTop(scrollDiv.scrollTop() | |||||
- scrollDiv.height() / 2 | |||||
+ newMatch.position().top + newMatch.height() / 2); | |||||
newMatch.effect("highlight", {color: '#FFF'}, 750) | |||||
newMatch.addClass('current'); | |||||
}; | |||||
function nextSymbolMatch() { | |||||
var currResult = $(".display-all"), | |||||
currMatch = currResult.find(".hll.current"), | |||||
matches = currResult.find(".hll"), | |||||
scrollDiv = currResult.find("#tablecontainer"); | |||||
if (currMatch.length == 0) | |||||
currMatch = $(matches[0]); | |||||
else | |||||
currMatch.removeClass("current"); | |||||
var index = matches.index(currMatch.get(0)) + 1; | |||||
index = index >= matches.length ? 0 : index; | |||||
var newMatch = $(matches[index]); | |||||
scrollDiv.scrollTop(scrollDiv.scrollTop() | |||||
- scrollDiv.height() / 2 | |||||
+ newMatch.position().top + newMatch.height() / 2); | |||||
newMatch.effect("highlight", {color: "#FFF"}, 750) | |||||
newMatch.addClass("current"); | |||||
}; | |||||
/* | |||||
* AJAX the current query string to the server, and return its response. | |||||
* | |||||
* @return {Array} The server's response in the form of `div.result` DOM | |||||
* elements, to fill `div#results`. | |||||
*/ | |||||
function queryServer(){ | |||||
var queryUrl = document.URL + "search.json?" + $.param({ | |||||
"q" : searchBar.value, | |||||
"p" : searchResultsPage++, | |||||
"hl": 1 | |||||
}); | |||||
var results = $.Deferred(); | |||||
$.getJSON(queryUrl, function(result){ | |||||
var resultDivs = []; | |||||
if("error" in result) | |||||
insertErrorMessage(result["error"]); | |||||
else if(result["results"].length == 0 && searchResultsPage == 2) | |||||
insertErrorMessage("No search results."); | |||||
else | |||||
for(var codelet = 0; codelet < result["results"].length; codelet++) | |||||
resultDivs.push(createResult(result["results"][codelet])); | |||||
results.resolve(resultDivs); | |||||
}); | |||||
return results; | |||||
} | |||||
/* | |||||
* Query the server with the current search string, and populate `div#results` | |||||
* with its response. | |||||
*/ | |||||
function populateResults(){ | |||||
searchResultsPage = 1; | |||||
loadMoreResults(); | |||||
} | |||||
/* | |||||
* Query the server for the next results page, and add its codelets to | |||||
* `div#results`. | |||||
*/ | |||||
function loadMoreResults(){ | |||||
queryServer().done(function(results){ | |||||
for(var result = 0; result < results.length; result++){ | |||||
var newDiv = results[result]; | |||||
resultsDiv.appendChild(newDiv); | |||||
setTimeout( | |||||
(function(divReference){ | |||||
return function(){ | |||||
divReference.classList.add("cascade"); | |||||
}; | |||||
}(newDiv)), | |||||
result * 20); | |||||
} | |||||
}); | |||||
} | |||||
/* | |||||
* Displays a warning message in the UI. | |||||
* | |||||
* @param msg (str) The message string. | |||||
*/ | |||||
function insertErrorMessage(msg){ | |||||
var error = $( | |||||
[ | |||||
"<div id='error'><span id='s1'>Error</span> ", | |||||
"<span id='s2'>»</span> </div>" | |||||
].join("")); | |||||
error.append(msg); | |||||
resultsDiv.appendChild(error[0]); | |||||
} |
@@ -0,0 +1,6 @@ | |||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | |||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | |||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | |||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga'); | |||||
ga('create', 'UA-51910807-1', 'bitshift.it'); | |||||
ga('send', 'pageview'); |
@@ -0,0 +1,3 @@ | |||||
User-agent: * | |||||
Disallow: /search.json | |||||
Sitemap: http://www.bitshift.it/sitemap.xml |
@@ -0,0 +1,18 @@ | |||||
a#logo | |||||
letter-spacing: 0.3em | |||||
text-decoration: none | |||||
div#logo | |||||
font-size: 400% | |||||
padding-bottom: 0.2em | |||||
text-align: center | |||||
#logo-bit | |||||
color: $baseColor1 | |||||
#logo-angle | |||||
color: $baseColor3 | |||||
#logo-shift | |||||
color: $baseColor2 | |||||
font-style: italic |
@@ -1,11 +1,29 @@ | |||||
/* | /* | ||||
Partial to contain all globally-applicable mixins | |||||
Partial to contain all globally-applicable mixins. | |||||
*/ | */ | ||||
// add vendor prefixes for the property $property with value $value | |||||
// Add vendor prefixes for the property $property with value $value. | |||||
@mixin vendor($property, $value) | @mixin vendor($property, $value) | ||||
-webkit-#{$property}: $value | -webkit-#{$property}: $value | ||||
-moz-#{$property}: $value | -moz-#{$property}: $value | ||||
-ms-#{$property}: $value | -ms-#{$property}: $value | ||||
-o-#{$property}: $value | -o-#{$property}: $value | ||||
#{$property}: $value | #{$property}: $value | ||||
// Add portable opacity style. | |||||
@mixin opaque($opacity) | |||||
@include vendor(opacity, $opacity) | |||||
filter: alpha(opacity=$opacity) | |||||
@mixin delay($time) | |||||
transition-delay: $time | |||||
-webkit-transition-delay: $time | |||||
.t1 | |||||
@include vendor(transition, all 0.1s ease-out) | |||||
.t2 | |||||
@include vendor(transition, all 0.2s ease-out) | |||||
.t3 | |||||
@include vendor(transition, all 0.3s ease-out) |
@@ -0,0 +1,12 @@ | |||||
/* | |||||
Partial to contain all globally-applicable variables. | |||||
*/ | |||||
$baseColor1: #A31F34 | |||||
$baseColor2: #8A8B8C | |||||
$baseColor3: #C2C0BF | |||||
$lightGray: #F1F1F1 | |||||
$lightBlue: #67A0FD | |||||
$blue: #3177EB |
@@ -0,0 +1,139 @@ | |||||
/* | |||||
Stylesheet for `templates/about.html` | |||||
*/ | |||||
@import mixins | |||||
@import variables | |||||
$centered-section-min-width: 500px | |||||
div.bg | |||||
$img-height: 650px | |||||
position: fixed | |||||
width: 100% | |||||
left: 0 | |||||
z-index: -1 | |||||
&#img-1 | |||||
background: url(../img/about/bg1.png) no-repeat | |||||
background-size: cover | |||||
height: 600px | |||||
top: -300px | |||||
&#img-2 | |||||
background: url(../img/about/bg2.png) no-repeat | |||||
background-size: cover | |||||
height: $img-height + 300 | |||||
top: 1150px | |||||
&#img-3 | |||||
background: url(../img/about/bg3.png) no-repeat | |||||
background-size: cover | |||||
height: $img-height + 300 | |||||
top: 2050px | |||||
&#img-4 | |||||
background: url(../img/about/bg4.png) no-repeat | |||||
background-size: cover | |||||
height: $img-height + 400 | |||||
top: 3200px | |||||
div.section | |||||
background-color: white | |||||
border: 1px solid $baseColor2 | |||||
margin-bottom: 200px | |||||
margin-top: 300px | |||||
padding-bottom: 80px | |||||
padding-top: 20px | |||||
overflow: hidden | |||||
&#top | |||||
margin-top: 0px | |||||
div#wrap | |||||
width: 100% | |||||
position: relative | |||||
padding-top: 56.782% // aspect ration, 9 / 16 | |||||
iframe#vimeo | |||||
border: 1px solid $baseColor3 | |||||
margin-top: 40px | |||||
position: absolute | |||||
top: 0 | |||||
left: 0 | |||||
height: none | |||||
width: none | |||||
div.centered | |||||
font-size: 110% | |||||
line-height: 150% | |||||
margin-left: auto | |||||
margin-right: auto | |||||
min-width: 500px | |||||
width: 65% | |||||
&#how | |||||
b | |||||
font-family: monospace | |||||
font-size: 110% | |||||
ul | |||||
list-style: none | |||||
span | |||||
color: $baseColor1 | |||||
font-weight: bold | |||||
h1 | |||||
color: $baseColor1 | |||||
span | |||||
color: $baseColor2 | |||||
a | |||||
color: #727070 | |||||
font-style: italic | |||||
text-decoration: none | |||||
&:hover | |||||
@extend .t3 | |||||
color: #575757 | |||||
span#title | |||||
color: $baseColor1 | |||||
font-weight: bold | |||||
div.person | |||||
font-size: 80% | |||||
overflow: hidden | |||||
&#top | |||||
margin-top: 40px | |||||
>div | |||||
$image-min-width: 100px | |||||
display: inline-block | |||||
height: 100% | |||||
margin-bottom: 40px | |||||
vertical-align: top | |||||
&.photo | |||||
margin-right: 40px | |||||
width: $image-min-width | |||||
img | |||||
display: block | |||||
height: $image-min-width | |||||
width: $image-min-width | |||||
&.bio | |||||
min-width: $centered-section-min-width - $image-min-width - 50px | |||||
width: 70% | |||||
h1 | |||||
font-size: 130% | |||||
margin: 0 |
@@ -0,0 +1,63 @@ | |||||
@import mixins | |||||
@import variables | |||||
@import logo | |||||
h1 | |||||
color: $baseColor1 | |||||
span | |||||
color: $baseColor2 | |||||
h2, h3 | |||||
color: $baseColor2 * 0.8 | |||||
p | |||||
line-height: 1.8em | |||||
ul | |||||
list-style: none | |||||
margin-bottom: 2% | |||||
li | |||||
margin-bottom: 2% | |||||
a | |||||
color: #727070 | |||||
font-style: italic | |||||
text-decoration: none | |||||
&:hover | |||||
@extend .t3 | |||||
color: #575757 | |||||
span | |||||
&.code | |||||
background-color: $baseColor3 * 1.2 | |||||
font-family: monospace | |||||
padding: 5px | |||||
&.string | |||||
color: $baseColor1 | |||||
font-family: monospace | |||||
font-size: 1.1em | |||||
&.title | |||||
color: $baseColor1 | |||||
font-weight: bold | |||||
table.code-example | |||||
border-collapse: collapse | |||||
width: 100% !important | |||||
td.linenos | |||||
border: 1px solid $baseColor2 | |||||
padding-left: 10px | |||||
width: 20px | |||||
td.code | |||||
padding-left: 10px | |||||
li#sec3 span#gasp | |||||
color: $baseColor2 * 0.8 | |||||
font-style: italic |
@@ -0,0 +1,19 @@ | |||||
@import variables | |||||
div#message | |||||
color: $baseColor1 | |||||
font-family: monospace | |||||
font-size: 700% | |||||
font-weight: normal | |||||
margin-top: 8% | |||||
text-align: center | |||||
span | |||||
&.light | |||||
color: $baseColor3 | |||||
&.dark | |||||
color: $baseColor2 | |||||
&.red | |||||
color: $baseColor1 |
@@ -0,0 +1,443 @@ | |||||
/* | |||||
Stylesheet for `templates/index.html`. | |||||
*/ | |||||
@import mixins | |||||
@import variables | |||||
@import logo | |||||
$minSearchFieldsWidth: 490px | |||||
$codeWidth: 700px | |||||
$hiddenInfoWidth: 300px | |||||
.ui-datepicker | |||||
font-size: 70% | |||||
.ui-autocomplete | |||||
max-height: 30% | |||||
overflow-x: hidden | |||||
overflow-y: scroll | |||||
padding: 0px | |||||
>li.ui-menu-item a.ui-state-focus | |||||
@include vendor(transition, background-color 0.3s ease-out) | |||||
div#body | |||||
@extend .t3 | |||||
&.faded | |||||
@include opaque(0.8) | |||||
div#hotkey-help | |||||
$width: 40% | |||||
background-color: white | |||||
border: 1px solid $baseColor3 | |||||
left: 50% - $width / 2 | |||||
min-width: 400px | |||||
padding: 35px | |||||
position: fixed | |||||
top: 30% | |||||
width: $width | |||||
z-index: 200 | |||||
&.hidden | |||||
display: none | |||||
div | |||||
border-bottom: 1px solid $baseColor2 | |||||
color: $baseColor1 | |||||
font-size: 130% | |||||
padding-bottom: 8px | |||||
text-align: center | |||||
ul | |||||
list-style: none | |||||
margin-left: auto | |||||
margin-right: auto | |||||
position: relative | |||||
width: 300px | |||||
li | |||||
margin-bottom: 4px | |||||
span.hotkey | |||||
color: $baseColor1 | |||||
font-family: monospace | |||||
font-size: 130% | |||||
font-weight: bold | |||||
span.seperator | |||||
color: $baseColor2 | |||||
div#search-field | |||||
@extend .t2 | |||||
bottom: 0 | |||||
height: 50% | |||||
left: 0 | |||||
margin: auto | |||||
margin-top: 15% | |||||
max-height: 100px | |||||
right: 0 | |||||
position: absolute | |||||
z-index: 2 | |||||
top: 0 | |||||
width: 40% | |||||
form#search-bar | |||||
min-width: $minSearchFieldsWidth | |||||
input[type="text"], button | |||||
@extend .t3 | |||||
@include vendor(box-sizing, border-box) | |||||
border: 1px solid $baseColor2 | |||||
font-size: 110% | |||||
margin-bottom: 0px | |||||
padding: 6px | |||||
input[type="text"]#query | |||||
width: 100% | |||||
&:hover | |||||
border-color: $baseColor1 | |||||
button#advanced-search | |||||
background-color: white | |||||
border: none | |||||
color: $baseColor2 | |||||
font-size: 1.1em | |||||
font-style: italic | |||||
&:hover | |||||
color: $baseColor1 | |||||
cursor: pointer | |||||
&.clicked | |||||
color: $baseColor1 | |||||
&:focus | |||||
outline: 0 | |||||
&.partly-visible | |||||
margin-top: 0% | |||||
position: absolute | |||||
width: 100% | |||||
#logo | |||||
position: absolute | |||||
top: -1% | |||||
left: 1% | |||||
span | |||||
font-size: 50% | |||||
form#search-bar | |||||
padding-top: 3% | |||||
margin-left: auto | |||||
margin-right: auto | |||||
min-width: 800px | |||||
width: 60% | |||||
input | |||||
@extend .t3 | |||||
&#query | |||||
width: 80% | |||||
&:hover | |||||
border: 1px solid $baseColor1 | |||||
button#advanced-search | |||||
margin-left: 30px | |||||
div#advanced-search | |||||
background-color: white | |||||
border: 1px solid $baseColor3 | |||||
display: none | |||||
font-size: 96% | |||||
height: 400px | |||||
min-width: $minSearchFieldsWidth | |||||
padding-top: 0px | |||||
overflow-x: auto | |||||
overflow-y: hidden | |||||
#heading | |||||
color: $baseColor2 | |||||
display: block | |||||
font-size: 120% | |||||
padding-left: 1% | |||||
padding-top: 1% | |||||
width: 100% | |||||
div | |||||
display: inline-block | |||||
font-size: 110% | |||||
&#col1 | |||||
width: 25% | |||||
&#col2 | |||||
width: 75% | |||||
button | |||||
border: none | |||||
color: white | |||||
float: right | |||||
font-size: 80% | |||||
font-weight: bold | |||||
margin-right: 1% | |||||
padding-left: 4% | |||||
padding-right: 4% | |||||
&:hover | |||||
cursor: pointer | |||||
&#add-group | |||||
background-color: #7FAFFC | |||||
&:hover | |||||
background-color: #609AF8 | |||||
&#remove-group | |||||
background-color: #E74C4C | |||||
&:hover | |||||
background-color: #D63636 | |||||
&#submit | |||||
background-color: #4ee76c | |||||
&:hover | |||||
background-color: #0FDD38 | |||||
>div | |||||
@include vendor(box-sizing, border-box) | |||||
display: inline-block | |||||
float: left | |||||
#sidebar | |||||
padding-left: 1% | |||||
width: 25% | |||||
>ul | |||||
list-style: none | |||||
padding-left: 0 | |||||
margin-bottom: 8% | |||||
margin-top: 2% | |||||
li | |||||
margin-bottom: 2% | |||||
label | |||||
user-select: none | |||||
div | |||||
@extend .t3 | |||||
background-color: $lightGray | |||||
border: none | |||||
padding: 3% | |||||
width: 85% | |||||
&:hover, &.selectedInputField | |||||
@extend .t3 | |||||
background-color: $baseColor2 | |||||
color: white | |||||
cursor: pointer | |||||
width: 90% | |||||
input[type="checkbox"] | |||||
display: none | |||||
&:checked + label > div | |||||
@extend .selectedInputField | |||||
background-color: $baseColor1 | |||||
color: white | |||||
width: 90% | |||||
#search-groups | |||||
margin-top: 1% | |||||
max-height: 87% | |||||
overflow-y: auto | |||||
width: 75% | |||||
.search-group | |||||
@include vendor(transition, all 0.6s ease-out) | |||||
background-color: $lightGray | |||||
padding: 1% | |||||
margin-bottom: 2% | |||||
width: 97% | |||||
>div | |||||
margin-bottom: 0.7% | |||||
>div.name | |||||
display: inline-block | |||||
font-size: 90% | |||||
width: 20% | |||||
>input[type=text] | |||||
display: inline-block | |||||
padding: 2px | |||||
width: 60% | |||||
>input[type=checkbox] | |||||
margin-left: 2% | |||||
&:checked + span | |||||
@extend .t2 | |||||
color: green | |||||
font-weight: bold | |||||
&:hover | |||||
cursor: checkbox | |||||
span.regex | |||||
font-size: 80% | |||||
&:hover | |||||
cursor: pointer | |||||
background-color: #d6d6d6 | |||||
&#selected | |||||
background-color: #CACACA | |||||
div#results | |||||
margin: 3% auto 0 auto | |||||
margin-left: auto | |||||
margin-right: auto | |||||
width: 80% | |||||
a | |||||
@extend .t3 | |||||
text-decoration: none | |||||
&:hover | |||||
color: $baseColor1 | |||||
div#error | |||||
font-size: 170% | |||||
margin-top: 22% | |||||
text-align: center | |||||
span | |||||
margin-right: 10px | |||||
font-size: 150% | |||||
&#s1 | |||||
color: $baseColor1 | |||||
&#s2 | |||||
color: $baseColor2 | |||||
&.disable-hover | |||||
pointer-events: none | |||||
div.result | |||||
@extend .t3 | |||||
height: 200px | |||||
margin-bottom: 100% | |||||
pointer-events: auto | |||||
table | |||||
border-collapse: collapse | |||||
height: inherit | |||||
tr | |||||
@extend .t3 | |||||
@include opaque(0.8) | |||||
height: inherit | |||||
&.cascade | |||||
@extend .t1 | |||||
margin-bottom: 15% | |||||
&.display-all | |||||
table tr | |||||
@include opaque(1.0) | |||||
#tablecontainer | |||||
max-width: 70% | |||||
overflow: auto !important | |||||
div#display-info | |||||
font-size: 1.3em | |||||
padding: 5px 0px 5px 5px | |||||
width: 100% | |||||
#title | |||||
margin-right: 10px | |||||
#site | |||||
text-transform: capitalize | |||||
td#code | |||||
@include vendor(transition, width 0.2s ease-in-out) | |||||
width: $codeWidth | |||||
max-width: $codeWidth | |||||
height: inherit | |||||
padding: 0px | |||||
#tablecontainer | |||||
width: 100% | |||||
height: inherit | |||||
overflow: hidden | |||||
background-color: #49483e | |||||
position: relative | |||||
z-index: 1 | |||||
table | |||||
border-collapse: collapse | |||||
font-family: monospace | |||||
.linenos | |||||
padding-left: 8px | |||||
pre | |||||
margin-top: 5px | |||||
.code pre | |||||
margin-top: 5px | |||||
.hll | |||||
background: #5B5A51 | |||||
div#hidden-info | |||||
width: $hiddenInfoWidth | |||||
margin-left: -$hiddenInfoWidth | |||||
height: 100% | |||||
padding-top: 40px | |||||
font-size: 1.2em | |||||
line-height: 1.5em | |||||
position: relative | |||||
z-index: 0 | |||||
@include vendor(transition, margin-left 0.2s ease-in-out) | |||||
.display-all & | |||||
margin-left: -$hiddenInfoWidth / 1.5 | |||||
padding-left: 20px | |||||
span | |||||
color: $baseColor1 | |||||
font-family: monospace | |||||
font-size: 1.1em | |||||
// float: right | |||||
div | |||||
display: block | |||||
#authors | |||||
a | |||||
font-family: monospace |
@@ -2,6 +2,53 @@ | |||||
Global project stylesheet. | Global project stylesheet. | ||||
*/ | */ | ||||
// placeholder | |||||
p | |||||
font-size: 1.5em | |||||
@import mixins | |||||
@import variables | |||||
html, body | |||||
height: 100% | |||||
margin: 0 | |||||
padding: 0 | |||||
font-family: sans-serif | |||||
div#container | |||||
min-height: 100% | |||||
position: relative | |||||
div#header | |||||
padding: 10px | |||||
div#body | |||||
height: 100% | |||||
padding-bottom: 110px | |||||
padding-top: 4% | |||||
div#center | |||||
margin-left: auto | |||||
margin-right: auto | |||||
width: 75% | |||||
div#footer | |||||
background-color: $baseColor1 | |||||
bottom: 0 | |||||
height: 30px | |||||
padding-bottom: 5px | |||||
padding-top: 15px | |||||
position: fixed | |||||
text-align: center | |||||
width: 100% | |||||
z-index: 100 | |||||
* | |||||
color: white | |||||
a | |||||
@extend .t3 | |||||
font-size: 1.2em | |||||
margin-left: 5% | |||||
margin-right: 5% | |||||
text-decoration: none | |||||
&:hover | |||||
text-decoration: underline |
@@ -0,0 +1,16 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> | |||||
<url> | |||||
<loc>http://bitshift.it/</loc> | |||||
<changefreq>monthly</changefreq> | |||||
</url> | |||||
<url> | |||||
<loc>http://bitshift.it/about</loc> | |||||
<changefreq>monthly</changefreq> | |||||
</url> | |||||
<url> | |||||
<loc>http://bitshift.it/docs</loc> | |||||
<changefreq>monthly</changefreq> | |||||
</url> | |||||
</urlset> |
@@ -0,0 +1,92 @@ | |||||
= extends "layout.html" | |||||
= block title | |||||
about | |||||
= endblock | |||||
= block head | |||||
{{ assets.tag("lib/jquery.min.js") }} | |||||
{{ assets.tag("main.css") }} | |||||
{{ assets.tag("about.css") }} | |||||
= endblock | |||||
= block after_body | |||||
<div id="img-1" class="bg" speed="-1.25"></div> | |||||
<div id="img-2" class="bg" speed="1.4"></div> | |||||
<div id="img-3" class="bg" speed="1.4"></div> | |||||
<div id="img-4" class="bg" speed="1.4"></div> | |||||
<div id="top" class="section"> | |||||
<div class="centered"> | |||||
<h1><span>»</span> What</h1> | |||||
<span id="title">bitshift</span> is an <a href="https://github.com/earwig/bitshift">open-source</a> | |||||
online source-code search engine, developed by programmers, for programmers. The engine currently aggregates | |||||
publicly-available code from two online frameworks – <a href="https://github.com/">GitHub</a> and <a | |||||
href="https://bitbucket.org/">Bitbucket</a> – but has the necessary infrastructure to quickly incorporate | |||||
others, like <a href="http://stackoverflow.com/">StackOverflow</a> and | |||||
<a href="https://gitorious.org/">Gitorious</a>. <span id="title">bitshift</span> supports a robust query | |||||
language, which allows users to search for specific languages, files, dates of creation and last modifcation, | |||||
and symbols (function, class, and variable names), amongst other attributes. | |||||
Watch our introductory video: | |||||
<div id="wrap"> | |||||
<iframe id="vimeo" src="//player.vimeo.com/video/98697078" width="100%" height="100%" frameborder="0" | |||||
webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="section"> | |||||
<div id="how" class="centered"> | |||||
<h1><span>»</span> How</h1> | |||||
<span id="title">bitshift</span> has an extensive back-end, roughly divided into three sections: | |||||
<ul> | |||||
<li><span>indexer</span> : finds and downloads code from online frameworks</li> | |||||
<li><span>parser</span> : parses newly crawled code, identifying its symbols</li> | |||||
<li><span>database</span> : interprets and compiles user searches into database queries</li> | |||||
</ul> | |||||
The engine was developed over the span of four months, and is primarily implemented in <b>Python</b>, but has | |||||
parsers in <b>Ruby</b>, <b>Java</b>, and a number of other languages. | |||||
</div> | |||||
</div> | |||||
<div class="section"> | |||||
<div class="centered"> | |||||
<h1><span>»</span> Who</h1> | |||||
<span id="title">bitshift</span> was developed by three seniors from New York City's Stuyvesant High School. | |||||
<div id="top" class="person"> | |||||
<div class="photo"> | |||||
<a href="https://github.com/breuckelen"><img src="img/about/bio1.jpg" alt="Benjamin Attal's photo."></a> | |||||
</div> | |||||
<div class="bio"> | |||||
<h1><a href="https://github.com/breuckelen">Benjamin Attal</a></h1> | |||||
Benjamin Attal hacked together <span id="title">bitshift</span>'s parsers and is working on | |||||
data-visualization for bitshift's statistics page. He is a software developer and entrepreneur who enjoys | |||||
listening to and playing country music, as well as working with smart people. | |||||
</div> | |||||
</div> | |||||
<div class="person"> | |||||
<div class="photo"> | |||||
<a href="https://github.com/earwig"><img src="img/about/bio2.jpg" alt="Ben Kurtovic's photo."></a> | |||||
</div> | |||||
<div class="bio"> | |||||
<h1><a href="https://github.com/earwig">Ben Kurtovic</a></h1> Ben Kurtovic designed <span | |||||
id="title">bitshift</span>’s database and acts as its server admin. In his free time, he edits Wikipedia | |||||
and invents new ways of confusing the hell out of people through source code obfuscation. | |||||
</div> | |||||
</div> | |||||
<div class="person"> | |||||
<div class="photo"> | |||||
<a href="https://github.com/sevko"><img src="img/about/bio3.jpg" alt="Severyn Kozak's photo."></a> | |||||
</div> | |||||
<div class="bio"> | |||||
<h1><a href="https://github.com/sevko">Severyn Kozak</a></h1> | |||||
Severyn developed <span id="title">bitshift</span>'s crawlers and its front-end. He loves skiing, mathematics | |||||
that he doesn't understand, and the art of good software development. | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
{{ assets.tag("about.js") }} | |||||
= endblock |
@@ -0,0 +1,282 @@ | |||||
= extends "layout.html" | |||||
= block title | |||||
docs | |||||
= endblock | |||||
= block head | |||||
{{ assets.tag("lib/highlight.css") }} | |||||
{{ assets.tag("docs.css") }} | |||||
= endblock | |||||
= block body | |||||
<a id="logo" href="/"> | |||||
<div id="logo"> | |||||
<span id="logo-bit">bit</span | |||||
><span id="logo-angle">«</span | |||||
><span id="logo-shift">shift</span> | |||||
</div> | |||||
</a> | |||||
<ul> | |||||
<li> | |||||
<h1><span>»</span> Usage</h1> | |||||
<p> | |||||
<span class="title">bitshift</span> is a search-engine optimized for | |||||
source code: beyond supporting searches with the full range of ASCII | |||||
symbols, the engine <em>understands</em> code, allowing users to query | |||||
for metadata, like time of creation/last modification, programming | |||||
language, and even symbols like function names and variables. Basic use | |||||
boils down to general and advanced searches. | |||||
<ul> | |||||
<li> | |||||
<h2>general search</h2> | |||||
<p> | |||||
To perform a "general search," simply place your cursor in the | |||||
search bar on our home page and begin entering text; when you | |||||
stop typing for a short period of time, we'll automatically | |||||
execute the query for you. As you scroll down the page, new | |||||
codelets, or results, will be seamlessly downloaded from our | |||||
server and appended to the end. | |||||
</p> | |||||
</li> | |||||
<li> | |||||
<h2>advanced search</h2> | |||||
<p> | |||||
General searches, though, are limited. To allow users to make the | |||||
best of our engine, we created an advanced search form that | |||||
allows the creation of complex queries with the following | |||||
specifiers: | |||||
<ul> | |||||
<li> | |||||
<h3>search fields</h3> | |||||
<ul id="search-fields"> | |||||
<li> | |||||
<span class="code">languages</span> : The programming | |||||
languages to search for. | |||||
</li> | |||||
<li> | |||||
<span class="code">authors</span> : Search for code | |||||
written/modified by a specific person. | |||||
</li> | |||||
<li> | |||||
<span class="code">date last modified</span> : Search for | |||||
code last modified on a specific date | |||||
(<span class="code">mm/dd/yy</span> format). | |||||
</li> | |||||
<li> | |||||
<span class="code">date created</span> : Search for code | |||||
created on a specific date | |||||
(<span class="code">mm/dd/yy</span> format). | |||||
</li> | |||||
<li> | |||||
<span class="code">symbols</span> : Search for specific | |||||
symbols. | |||||
</li> | |||||
<li> | |||||
<span class="code">functions</span> : Search for | |||||
functions with specific names. | |||||
</li> | |||||
<li> | |||||
<span class="code">classes</span> : Search for classes | |||||
with specific names. | |||||
</li> | |||||
<li> | |||||
<span class="code">variables</span> : Search for | |||||
variables with specific names. | |||||
</li> | |||||
</ul> | |||||
<p> | |||||
Each of the search fields allows for numerous values; just | |||||
separate them with spaces. If you'd like to search for a | |||||
multi-word, space-delimited string, on the other hand, | |||||
enclose it in double quotes. | |||||
A query for <span class="code">foo bar</span> will search | |||||
for occurrences of both <span id="string">"foo"</span> and | |||||
<span class="string">"bar"</span>, while | |||||
<span class="code">"foo bar"</span> will search for | |||||
occurrences of <span class="string">"foo bar"</span>. | |||||
</p> | |||||
</li> | |||||
<li> | |||||
<h3>search groups</h3> | |||||
<p> | |||||
Search groups facilitate even more robust queries: they're | |||||
like a bunch of individual searches grouped into one. A | |||||
user searching for occurrenes of symbol | |||||
<span class="string">"curses"</span> in the language | |||||
<span class="string">"Python"</span>, and | |||||
<span class="string">"ncurses"</span> in | |||||
<span id="string">"C"</span>, won't get away with: | |||||
<span class="code">"symbols:curses ncurses"</span> and | |||||
<span class="code">"languages:Python C"</span>. The engine | |||||
might return results <span id="string">"curses"</span> in | |||||
<span class="string">"C"</span> and | |||||
<span class="string">"ncurses"</span> in | |||||
<span class="string">"Python"</span>! | |||||
To work around that, you can use two search groups: one for | |||||
<span class="string">"curses"</span> in | |||||
<span class="string">"Python"</span>, and another for | |||||
<span class="string">"curses"</span> in | |||||
<span id="string">"C"</span>. | |||||
<span class="title">bitshift</span> will return the union | |||||
of both sets of search results. | |||||
</p> | |||||
</li> | |||||
</ul> | |||||
</p> | |||||
</li> | |||||
</ul> | |||||
</p> | |||||
</li> | |||||
<li> | |||||
<h1><span>»</span> API</h1> | |||||
<p> | |||||
<span class="title">bitshift</span> provides an API through GET | |||||
requests to | |||||
<a href="http://bitshift.it/search.json"><span class="code">/search.json</span></a>. | |||||
</p> | |||||
<h2>parameters</h2> | |||||
<ul> | |||||
<li> | |||||
<span class="code">q</span> : The search query, as entered into the | |||||
search bar. | |||||
</li> | |||||
<li> | |||||
<span class="code">p</span> : The result page to return. Defaults to | |||||
<span class="code">1</span>. Each page contains ten results, so this | |||||
effectively offsets the search by | |||||
<span class="code">10 * (p - 1)</span> codelets. | |||||
</li> | |||||
<li> | |||||
<span class="code">hl</span> : Whether to return code as | |||||
<a href="http://pygments.org/">pygments</a>-highlighted HTML or as | |||||
plain source. Defaults to <span class="code">false</span>. | |||||
</li> | |||||
</ul> | |||||
<h2>output</h2> | |||||
<p> | |||||
<span class="code">/search.json</span> returns a JSON-encoded | |||||
dictionary. If there was an error, it will contain a single key, | |||||
<span class="code">"error"</span>, whose value will contain a | |||||
human-readable description of the error. Otherwise, there will be two | |||||
keys: <span class="code">"count"</span>, storing the number of results, | |||||
and <span class="code">"results"</span>, storing a list of codelets. | |||||
Each codelet is a dictionary with the following key–value pairs: | |||||
</p> | |||||
<ul> | |||||
<li> | |||||
<span class="code">name</span> : The name of the codelet. | |||||
</li> | |||||
<li> | |||||
<span class="code">code</span> : The actual source code if | |||||
<span class="code">hl</span> was not given or was | |||||
<span class="code">false</span>; HTML code otherwise. | |||||
</li> | |||||
<li> | |||||
<span class="code">lang</span> : The language of the code. | |||||
</li> | |||||
<li> | |||||
<span class="code">authors</span> : A list of authors. Each author is | |||||
a list of two items: their name, and URL (or | |||||
<span class="code">null</span> if none is known). | |||||
</li> | |||||
<li> | |||||
<span class="code">url</span> : The URL of the page where the code | |||||
was crawled from. | |||||
</li> | |||||
<li> | |||||
<span class="code">created</span> : The date the code was created, as | |||||
a | |||||
<a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a>-formatted | |||||
string (e.g. <span class="code">"2014-06-01T12:41:28"</span>). | |||||
</li> | |||||
<li> | |||||
<span class="code">modified</span> : The date the code was last | |||||
modified, as a | |||||
<a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a>-formatted | |||||
string (e.g. <span class="code">"2014-06-01T12:41:28"</span>). | |||||
</li> | |||||
<li> | |||||
<span class="code">origin</span> : A list of two items: the | |||||
originating site's name (e.g. <span class="code">"GitHub"</span>) and | |||||
URL (e.g. <span class="code">"https://github.com"</span>). | |||||
</li> | |||||
</ul> | |||||
<h2>example</h2> | |||||
<p> | |||||
The following example Python 2 code searches for a given Python | |||||
function definition and prints the URL of the first result: | |||||
</p> | |||||
<table class="highlighttable code-example"> | |||||
<tr> | |||||
<td class="linenos"> | |||||
<div class="linenodiv"> | |||||
<pre> 1 | |||||
2 | |||||
3 | |||||
4 | |||||
5 | |||||
6 | |||||
7 | |||||
8 | |||||
9 | |||||
10 | |||||
11 | |||||
12 | |||||
13 | |||||
14 | |||||
15 | |||||
16 | |||||
17 | |||||
18 | |||||
19</pre> | |||||
</div> | |||||
</td> | |||||
<td class="code"> | |||||
<div class="highlight"> | |||||
<pre><span class="c">#!/usr/bin/env python</span> | |||||
<span class="kn">from</span> <span class="nn">json</span> <span class="kn">import</span> <span class="n">loads</span> | |||||
<span class="kn">from</span> <span class="nn">sys</span> <span class="kn">import</span> <span class="n">argv</span> | |||||
<span class="kn">from</span> <span class="nn">urllib</span> <span class="kn">import</span> <span class="n">urlencode</span> | |||||
<span class="kn">from</span> <span class="nn">urllib2</span> <span class="kn">import</span> <span class="n">urlopen</span> | |||||
<span class="k">def</span> <span class="nf">get_function</span><span class="p">(</span><span class="n">name</span><span class="p">):</span> | |||||
<span class="n">params</span> <span class="o">=</span> <span class="p">{</span><span class="s">"q"</span><span class="p">:</span> <span class="s">"lang:python and func:def:</span><span class="si">%s</span><span class="s">"</span> <span class="o">%</span> <span class="n">name</span><span class="p">}</span> | |||||
<span class="n">request</span> <span class="o">=</span> <span class="n">urlopen</span><span class="p">(</span><span class="s">"http://bitshift.it/search.json?"</span> <span class="o">+</span> <span class="n">urlencode</span><span class="p">(</span><span class="n">params</span><span class="p">))</span> | |||||
<span class="n">res</span> <span class="o">=</span> <span class="n">loads</span><span class="p">(</span><span class="n">request</span><span class="o">.</span><span class="n">read</span><span class="p">())[</span><span class="s">"results"</span><span class="p">]</span> | |||||
<span class="k">if</span> <span class="n">res</span><span class="p">:</span> | |||||
<span class="k">print</span> <span class="s">"</span><span class="si">%s</span><span class="s">: </span><span class="si">%s</span><span class="s">"</span> <span class="o">%</span> <span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">res</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="s">"url"</span><span class="p">])</span> | |||||
<span class="k">else</span><span class="p">:</span> | |||||
<span class="k">print</span> <span class="s">"</span><span class="si">%s</span><span class="s"> not found."</span> <span class="o">%</span> <span class="n">name</span> | |||||
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span> | |||||
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">argv</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span> | |||||
<span class="n">get_function</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span></pre> | |||||
</div> | |||||
</td> | |||||
</tr> | |||||
</table> | |||||
</li> | |||||
<li id="sec3"> | |||||
<h1><span>»</span> Get Involved</h1> | |||||
<p> | |||||
<span class="title">bitshift</span> is <span id="gasp">(gasp)</span> | |||||
open-source! The project is hosted on | |||||
<a href="https://github.com/earwig/bitshift">GitHub</a>; feel free to | |||||
file an issue or submit a pull request. | |||||
</p> | |||||
</li> | |||||
</ul> | |||||
= endblock |
@@ -0,0 +1,26 @@ | |||||
= extends "layout.html" | |||||
= block title | |||||
404 | |||||
= endblock | |||||
= block head | |||||
{{ assets.tag("error404.css") }} | |||||
= endblock | |||||
= block body | |||||
<div id="message"> | |||||
{{ assets.syntax_highlight([ | |||||
'puts("404");', | |||||
'printf("%d\n", 404);', | |||||
'puts 404', | |||||
'System.out.println("404")', | |||||
'print 404', | |||||
'console.log("404")', | |||||
'echo 404', | |||||
'std::cout << "404\\n"', | |||||
'(println "404")', | |||||
'say "404!";' | |||||
] | random) | safe }} | |||||
</div> | |||||
= endblock |
@@ -1,9 +1,121 @@ | |||||
= extends "layout.html" | = extends "layout.html" | ||||
= block title | = block title | ||||
Home | |||||
home | |||||
= endblock | |||||
= block head | |||||
{{ assets.tag("lib/jqueryui.custom.min.css") }} | |||||
{{ assets.tag("lib/jquery.min.js") }} | |||||
{{ assets.tag("lib/jquery-ui.min.js") }} | |||||
{{ assets.tag("lib/highlight.css") }} | |||||
{{ assets.tag("index.css") }} | |||||
<script> | |||||
AUTOCOMPLETE_LANGUAGES = {{ autocomplete_languages | safe }}; | |||||
</script> | |||||
= endblock | = endblock | ||||
= block body | = block body | ||||
<p>Hello, world.</p> | |||||
<div id="search-field"> | |||||
<a id="logo" href="/"> | |||||
<div id="logo"> | |||||
<span id="logo-bit">bit</span | |||||
><span id="logo-angle">«</span | |||||
><span id="logo-shift">shift</span> | |||||
</div> | |||||
</a> | |||||
<form id="search-bar"> | |||||
<input id="query" type="text" name="query" | |||||
><button id="advanced-search" title="advanced search" type="button"> | |||||
Adv. Search | |||||
</button> | |||||
<div id="advanced-search"> | |||||
<div id="heading"> | |||||
<div id="col1">Fields</div | |||||
><div id="col2"> | |||||
Search groups | |||||
<button id="submit"> | |||||
<div>Search</div> | |||||
</button> | |||||
<button id="add-group"> | |||||
<div><span>+</span> Add</div> | |||||
</button> | |||||
<button id="remove-group"> | |||||
<div><span>-</span> Remove</div> | |||||
</button> | |||||
</div> | |||||
</div> | |||||
<div id="sidebar"> | |||||
<ul> | |||||
<li> | |||||
<input type="checkbox" id="language" checked="true"> | |||||
<label for="language"><div>languages</div></label> | |||||
</li> | |||||
<li> | |||||
<input type="checkbox" id="author"> | |||||
<label for="author"><div>authors</div></label> | |||||
</li> | |||||
<li> | |||||
<input type="checkbox" id="date-last-modified"> | |||||
<label for="date-last-modified"><div>date last modified</div></label> | |||||
</li> | |||||
<li> | |||||
<input type="checkbox" id="date-created"> | |||||
<label for="date-created"><div>date created</div></label> | |||||
</li> | |||||
<li> | |||||
<input type="checkbox" id="symbol"> | |||||
<label for="symbol"><div>symbols</div></label> | |||||
</li> | |||||
<li> | |||||
<input type="checkbox" id="function"> | |||||
<label for="function"><div>functions</div></label> | |||||
</li> | |||||
<li> | |||||
<input type="checkbox" id="class"> | |||||
<label for="class"><div>classes</div></label> | |||||
</li> | |||||
<li> | |||||
<input type="checkbox" id="variable"> | |||||
<label for="variable"><div>variables</div></label> | |||||
</li> | |||||
</ul> | |||||
</div> | |||||
<div id="search-groups"> | |||||
<div class="search-group" id="selected"> | |||||
<div id="language"> | |||||
<div class="name">languages</div | |||||
><input id="autocomplete" class="language" name="language" type="text" | |||||
><input type="checkbox" name="regex" | |||||
><span class="regex">Regex</span> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</form> | |||||
</div> | |||||
<div id="results"></div> | |||||
{{ assets.tag("index.js") }} | |||||
{{ assets.tag("index.advanced-search-form.js") }} | |||||
= endblock | |||||
= block after_body | |||||
<div id="hotkey-help" class="hidden"> | |||||
<div>Hotkeys</div> | |||||
<ul> | |||||
<li><span class="hotkey">k</span> <span class="seperator">:</span> move window up to the previous result</li> | |||||
<li><span class="hotkey">j</span> <span class="seperator">:</span> move window down to the next result</li> | |||||
<li><span class="hotkey">h</span> <span class="seperator">:</span> move to the previous symbol match</li> | |||||
<li><span class="hotkey">l</span> <span class="seperator">:</span> move to the next symbol match</li> | |||||
<li><span class="hotkey">?</span> <span class="seperator">:</span> toggle help</li> | |||||
</ul> | |||||
</div> | |||||
= endblock | = endblock |
@@ -4,24 +4,44 @@ | |||||
<html> | <html> | ||||
<head> | <head> | ||||
<title> | <title> | ||||
= block title | |||||
= endblock | |||||
bitshift « | |||||
= filter lower | |||||
= block title | |||||
= endblock | |||||
= endfilter | |||||
</title> | </title> | ||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/> | <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/> | ||||
<meta name="description" content="bitshift is an online code snippet | |||||
exchange."/> | |||||
<meta name="keywords" content="code snippet exchange golf programming | |||||
software community"/> | |||||
<meta name="description" content="bitshift is a source code search engine."/> | |||||
<meta name="keywords" content="source code language search engine"/> | |||||
<meta name="author" content="Benjamin Attal Ben Kurtovic Severyn Kozak"/> | <meta name="author" content="Benjamin Attal Ben Kurtovic Severyn Kozak"/> | ||||
{{ assets.tag("main.css") }} | {{ assets.tag("main.css") }} | ||||
{{ assets.tag("main.js") }} | |||||
= block head | = block head | ||||
= endblock | = endblock | ||||
</head> | </head> | ||||
<body> | <body> | ||||
= block body | |||||
= endblock | |||||
<div id="container"> | |||||
<div id="header"> | |||||
</div> | |||||
<div id="body"> | |||||
<div id="center"> | |||||
= block body | |||||
= endblock | |||||
</div> | |||||
</div> | |||||
= block after_body | |||||
= endblock | |||||
<div id="footer"> | |||||
<a href="/">home</a> | |||||
<a href="/about">about</a> | |||||
<a href="/docs">docs</a> | |||||
</div> | |||||
</div> | |||||
</body> | </body> | ||||
</html> | </html> |
@@ -0,0 +1,19 @@ | |||||
#!/usr/bin/env python | |||||
from json import loads | |||||
from sys import argv | |||||
from urllib import urlencode | |||||
from urllib2 import urlopen | |||||
def get_function(name): | |||||
params = {"q": "lang:python and func:def:%s" % name} | |||||
request = urlopen("http://bitshift.it/search.json?" + urlencode(params)) | |||||
res = loads(request.read())["results"] | |||||
if res: | |||||
print "%s: %s" % (name, res[0]["url"]) | |||||
else: | |||||
print "%s not found." % name | |||||
if __name__ == "__main__": | |||||
if len(argv) == 2: | |||||
get_function(argv[1]) |
@@ -0,0 +1,56 @@ | |||||
import socket, sys, struct | |||||
file_name = 'resources/<name>.c' | |||||
server_socket_number = 5001 | |||||
recv_size = 8192 | |||||
if __name__ == '__main__': | |||||
if len(sys.argv) == 1: | |||||
print "Please input a parser to test." | |||||
elif len(sys.argv) > 2: | |||||
print "Too many arguments." | |||||
else: | |||||
if sys.argv[1] == 'c': | |||||
pass | |||||
elif sys.argv[1] == 'java': | |||||
file_name = "resources/Matrix.java" | |||||
server_socket_number = 5002 | |||||
elif sys.argv[1] == 'ruby': | |||||
file_name = "resources/parser.rb" | |||||
server_socket_number = 5065 | |||||
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |||||
server_socket.connect(("localhost", server_socket_number)) | |||||
with open(file_name, "r") as source_file: | |||||
source = source_file.read() | |||||
server_socket.send("%d\n%s" % (len(source), source)); | |||||
total_data = []; size_data = cur_data = '' | |||||
total_size = 0; size = sys.maxint | |||||
while total_size < size: | |||||
cur_data = server_socket.recv(recv_size) | |||||
if not total_data: | |||||
if len(size_data) > 4: | |||||
size_data += cur_data | |||||
size = struct.unpack('>i', size_data[:4])[0] | |||||
recv_size = size | |||||
if recv_size > sys.maxint: recv_size = sys.maxint | |||||
total_data.append(size_data[4:]) | |||||
else: | |||||
size_data += cur_data | |||||
else: | |||||
total_data.append(cur_data) | |||||
total_size = sum([len(s) for s in total_data]) | |||||
server_socket.close() | |||||
print ''.join(total_data); |
@@ -0,0 +1,218 @@ | |||||
package battlechap; | |||||
import java.io.PrintStream; | |||||
public class Matrix { | |||||
private Object[][] _datmatrix; | |||||
public Matrix(int paramInt){ | |||||
this._datmatrix = new Object[paramInt][paramInt]; | |||||
} | |||||
public int size() { | |||||
return this._datmatrix.length; | |||||
} | |||||
public Object get(int paramInt1, int paramInt2) { | |||||
return this._datmatrix[paramInt1][paramInt2]; | |||||
} | |||||
public boolean isEmpty(int paramInt1, int paramInt2) { | |||||
return this._datmatrix[paramInt1][paramInt2] == null; | |||||
} | |||||
public boolean equals(Object paramObject) { | |||||
boolean bool = true; | |||||
if ((paramObject instanceof Matrix)) { | |||||
Matrix localMatrix = (Matrix)paramObject; | |||||
if (localMatrix.size() == size()) { | |||||
for (int i = 0; i < size(); i++) { | |||||
for (int j = 0; j < size(); j++) { | |||||
if (!localMatrix.get(i, j).equals(get(i, j))) { | |||||
bool = false; | |||||
break; | |||||
} | |||||
} | |||||
if (!bool) | |||||
break; | |||||
} | |||||
} | |||||
else | |||||
bool = false; | |||||
} | |||||
else | |||||
{ | |||||
bool = false; | |||||
} | |||||
return bool; | |||||
} | |||||
public Object set(int paramInt1, int paramInt2, Object paramObject) { | |||||
Object localObject = this._datmatrix[paramInt1][paramInt2]; | |||||
this._datmatrix[paramInt1][paramInt2] = paramObject; | |||||
return localObject; | |||||
} | |||||
public void transpose() { | |||||
int i = 0; | |||||
for (int j = 0; j < size(); j++) { | |||||
for (int k = i; k < size(); k++) { | |||||
set(j, k, set(k, j, get(j, k))); | |||||
} | |||||
i++; | |||||
} | |||||
} | |||||
public static void swapRows(int paramInt1, int paramInt2, Object[][] paramArrayOfObject) { | |||||
for (int i = 0; i < paramArrayOfObject[paramInt1].length; i++) { | |||||
Object localObject = paramArrayOfObject[paramInt1][i]; | |||||
paramArrayOfObject[paramInt1][i] = paramArrayOfObject[paramInt2][i]; | |||||
paramArrayOfObject[paramInt2][i] = localObject; | |||||
} | |||||
} | |||||
public static void swapCols(int paramInt1, int paramInt2, Object[][] paramArrayOfObject) { | |||||
for (int i = 0; i < paramArrayOfObject.length; i++) { | |||||
Object localObject = paramArrayOfObject[i][paramInt1]; | |||||
paramArrayOfObject[i][paramInt1] = paramArrayOfObject[i][paramInt2]; | |||||
paramArrayOfObject[i][paramInt2] = localObject; | |||||
} | |||||
} | |||||
public Object[] getRow(int paramInt) { | |||||
Object[] arrayOfObject = new Object[this._datmatrix[paramInt].length]; | |||||
for (int i = 0; i < arrayOfObject.length; i++) { | |||||
arrayOfObject[i] = this._datmatrix[paramInt][i]; | |||||
} | |||||
return arrayOfObject; | |||||
} | |||||
public Object[] getCol(int paramInt) { | |||||
Object[] arrayOfObject = new Object[this._datmatrix[paramInt].length]; | |||||
for (int i = 0; i < arrayOfObject.length; i++) { | |||||
arrayOfObject[i] = this._datmatrix[i][paramInt]; | |||||
} | |||||
return arrayOfObject; | |||||
} | |||||
public Object[] setRow(int paramInt, Object[] paramArrayOfObject) { | |||||
Object[] arrayOfObject = getRow(paramInt); | |||||
for (int i = 0; i < size(); i++) { | |||||
set(paramInt, i, paramArrayOfObject[i]); | |||||
} | |||||
return arrayOfObject; | |||||
} | |||||
public Object[] setCol(int paramInt, Object[] paramArrayOfObject) { | |||||
Object[] arrayOfObject = getCol(paramInt); | |||||
for (int i = 0; i < size(); i++) { | |||||
set(i, paramInt, paramArrayOfObject[i]); | |||||
} | |||||
return arrayOfObject; | |||||
} | |||||
public String toString() | |||||
{ | |||||
String str1 = ""; | |||||
for (int i = 0; i < this._datmatrix.length; i++) { | |||||
if (i < 9) | |||||
str1 = str1 + (i + 1) + ": "; | |||||
else | |||||
str1 = str1 + (i + 1) + ":"; | |||||
for (int j = 0; j < this._datmatrix[i].length; j++) { | |||||
int k = (this._datmatrix[i][j] + "").length(); | |||||
String str2 = " ".substring(k); | |||||
str1 = str1 + this._datmatrix[i][j] + str2; | |||||
} | |||||
str1 = str1 + "\n"; | |||||
} | |||||
return str1; | |||||
} | |||||
public static void print(Object[][] paramArrayOfObject) { | |||||
for (int i = 0; i < paramArrayOfObject.length; i++) { | |||||
for (int j = 0; j < paramArrayOfObject[i].length; j++) { | |||||
int k = (paramArrayOfObject[i][j] + "").length(); | |||||
String str = " ".substring(k); | |||||
System.out.print(paramArrayOfObject[i][j] + str); | |||||
} | |||||
System.out.print("\n"); | |||||
} | |||||
} | |||||
public static void printArray(Object[] paramArrayOfObject) { | |||||
for (int i = 0; i < paramArrayOfObject.length; i++) { | |||||
int j = (paramArrayOfObject[i] + "").length(); | |||||
String str = " ".substring(j); | |||||
System.out.print(paramArrayOfObject[i] + str); | |||||
} | |||||
System.out.print("\n"); | |||||
} | |||||
public static void main(String[] paramArrayOfString) { | |||||
Matrix localMatrix1 = new Matrix(5); | |||||
Matrix localMatrix2 = new Matrix(5); | |||||
for (int i = 0; i < localMatrix1.size(); i++) { | |||||
for (int j = 0; j < localMatrix1.size(); j++) { | |||||
Integer localInteger1 = new Integer((int)(Math.random() * 20.0D)); | |||||
localMatrix1.set(i, j, localInteger1); | |||||
localMatrix2.set(i, j, localInteger1); | |||||
} | |||||
} | |||||
System.out.println("\nDemonstrating equals method (should be true)\t" + localMatrix2.equals(localMatrix1) + "\n"); | |||||
System.out.println("Demonstrating get method\n" + localMatrix1.get(0, 0) + "\n"); | |||||
System.out.println("Demonstrating is empty method\n" + localMatrix1.isEmpty(1, 0) + "\n"); | |||||
System.out.println("Demonstrating size method \n" + localMatrix1.size() + "\n"); | |||||
System.out.println("Demonstrating toString method\n" + localMatrix1 + "\n"); | |||||
localMatrix1.transpose(); | |||||
System.out.println("Blop has been transposed\n" + localMatrix1 + "\n"); | |||||
Object[][] arrayOfObject = new Object[4][4]; | |||||
for (int j = 0; j < arrayOfObject.length; j++) { | |||||
for (int k = 0; k < arrayOfObject[j].length; k++) { | |||||
Integer localInteger2 = new Integer((int)(Math.random() * 20.0D)); | |||||
arrayOfObject[j][k] = localInteger2; | |||||
} | |||||
} | |||||
System.out.println("\n\n**Swapping Rows Demo**"); | |||||
print(arrayOfObject); | |||||
System.out.println("\nRows 1 and 2 have been Swapped \n"); | |||||
swapRows(1, 2, arrayOfObject); | |||||
print(arrayOfObject); | |||||
System.out.println("\n**Swapping Columns Demo**"); | |||||
print(arrayOfObject); | |||||
System.out.println("\n\nColumns 1 and 2 have been Swapped \n"); | |||||
swapCols(1, 2, arrayOfObject); | |||||
print(arrayOfObject); | |||||
System.out.println("\n**Getting rows demo (from blop)**"); | |||||
System.out.println(localMatrix1); | |||||
System.out.println("\nGetting row 1\n"); | |||||
printArray(localMatrix1.getRow(1)); | |||||
System.out.println("\n**Getting cols demo (from blop)**"); | |||||
System.out.println(localMatrix1); | |||||
System.out.println("\nGetting col 1\n"); | |||||
printArray(localMatrix1.getCol(1)); | |||||
System.out.println("\n**Demonstrating set row method**"); | |||||
System.out.println(localMatrix1); | |||||
System.out.println("\nSwitching row 1 of blop to 1st column of blop\n"); | |||||
localMatrix1.setRow(1, localMatrix1.getCol(1)); | |||||
System.out.println(localMatrix1 + "\n"); | |||||
System.out.println("\n**Demonstrating set col method**"); | |||||
System.out.println(localMatrix1); | |||||
System.out.println("\nSwitching col 1 of blop to 2nd row of blop\n"); | |||||
localMatrix1.setCol(1, localMatrix1.getRow(2)); | |||||
System.out.println(localMatrix1 + "\n"); | |||||
} | |||||
} | |||||
@@ -0,0 +1,40 @@ | |||||
""" | |||||
Module to contain all the project's Flask server plumbing. | |||||
""" | |||||
from flask import Flask | |||||
from flask import render_template, session | |||||
from bitshift import assets | |||||
# from bitshift.database import Database | |||||
# from bitshift.query import parse_query | |||||
app = Flask(__name__) | |||||
app.config.from_object("bitshift.config") | |||||
app_env = app.jinja_env | |||||
app_env.line_statement_prefix = "=" | |||||
app_env.globals.update(assets=assets) | |||||
# database = Database() | |||||
@app.route("/") | |||||
def index(): | |||||
return render_template("index.html") | |||||
@app.route("/search/<query>") | |||||
def search(query): | |||||
# tree = parse_query(query) | |||||
# database.search(tree) | |||||
pass | |||||
@app.route("/about") | |||||
def about(): | |||||
return render_template("about.html") | |||||
@app.route("/developers") | |||||
def developers(): | |||||
return render_template("developers.html") | |||||
if __name__ == "__main__": | |||||
app.run(debug=True) |
@@ -0,0 +1,126 @@ | |||||
require 'socket' | |||||
require 'ruby_parser' | |||||
require 'sexp_processor' | |||||
module Bitshift | |||||
class Parser | |||||
def initialize(source) | |||||
@source = source | |||||
end | |||||
def parse | |||||
parser = RubyParser.new | |||||
tree = parser.parse(@source) | |||||
puts tree.inspect | |||||
offset = tree.line - 1 | |||||
processor = NodeVisitor.new offset | |||||
processor.process tree | |||||
return processor.symbols | |||||
end | |||||
end | |||||
class NodeVisitor < SexpProcessor | |||||
attr_accessor :symbols | |||||
attr_accessor :offset | |||||
def initialize(offset) | |||||
super() | |||||
@require_empty = false | |||||
@offset = offset | |||||
module_hash = Hash.new {|hash, key| hash[key] = Hash.new} | |||||
class_hash = module_hash.clone | |||||
function_hash = Hash.new {|hash, key| hash[key] = { calls: [] } } | |||||
var_hash = Hash.new {|hash, key| hash[key] = [] } | |||||
@symbols = { | |||||
modules: module_hash, | |||||
classes: class_hash, | |||||
functions: function_hash, | |||||
vars: var_hash | |||||
} | |||||
end | |||||
def block_position(exp) | |||||
pos = Hash.new | |||||
end_ln = (start_ln = exp.line - offset) | |||||
cur_exp = exp | |||||
while cur_exp.is_a? Sexp | |||||
end_ln = cur_exp.line - offset | |||||
cur_exp = cur_exp.last | |||||
break if cur_exp == nil | |||||
end | |||||
pos[:coord] = { | |||||
start_ln: start_ln, | |||||
end_ln: end_ln } | |||||
return pos | |||||
end | |||||
def statement_position(exp) | |||||
pos = Hash.new | |||||
end_ln = start_ln = exp.line - offset | |||||
pos[:coord] = { | |||||
start_ln: start_ln, | |||||
end_ln: end_ln } | |||||
return pos | |||||
end | |||||
def process_module(exp) | |||||
pos = block_position exp | |||||
exp.shift | |||||
name = exp.shift | |||||
symbols[:modules][name] = pos | |||||
exp.each_sexp {|s| process(s)} | |||||
return exp.clear | |||||
end | |||||
def process_class(exp) | |||||
pos = block_position exp | |||||
exp.shift | |||||
name = exp.shift | |||||
symbols[:classes][name] = pos | |||||
exp.each_sexp {|s| process(s)} | |||||
return exp.clear | |||||
end | |||||
def process_defn(exp) | |||||
pos = block_position exp | |||||
exp.shift | |||||
name = exp.shift | |||||
symbols[:functions][name][:declaration] = pos | |||||
exp.each_sexp {|s| process(s)} | |||||
return exp.clear | |||||
end | |||||
def process_call(exp) | |||||
pos = statement_position exp | |||||
exp.shift | |||||
exp.shift | |||||
name = exp.shift | |||||
symbols[:functions][name][:calls] << pos | |||||
exp.each_sexp {|s| process(s)} | |||||
return exp.clear | |||||
end | |||||
def process_iasgn(exp) | |||||
pos = statement_position exp | |||||
exp.shift | |||||
name = exp.shift | |||||
symbols[:vars][name] << pos | |||||
exp.each_sexp {|s| process(s)} | |||||
return exp.clear | |||||
end | |||||
def process_lasgn(exp) | |||||
pos = statement_position exp | |||||
exp.shift | |||||
name = exp.shift | |||||
symbols[:vars][name] << pos | |||||
exp.each_sexp {|s| process(s)} | |||||
return exp.clear | |||||
end | |||||
end | |||||
end |
@@ -0,0 +1,76 @@ | |||||
# -*- coding: utf-8 -*- | |||||
from __future__ import unicode_literals | |||||
import unittest | |||||
from bitshift.query import parse_query | |||||
TESTS = [ | |||||
# Text | |||||
("test", "Tree(Text(String(u'test')))"), | |||||
("re:test", "Tree(Text(Regex(u'test')))"), | |||||
# Language | |||||
("language:python", "Tree(Language(Python))"), | |||||
("language:py", "Tree(Language(Python))"), | |||||
("l:r:r..y", "Tree(Language(Ruby))"), | |||||
(r'"lang:re:python|^c$"', | |||||
"Tree(BinaryOp(Language(C), OR, Language(Python)))"), | |||||
# Author | |||||
('"author:Ben Kurtovic"', "Tree(Author(String(u'Ben Kurtovic')))"), | |||||
(r"'a:re:b.*?\sk.*?'", r"Tree(Author(Regex(u'b.*?\\sk.*?')))"), | |||||
# Date | |||||
("'create:before:Jan 1, 2014'", | |||||
"Tree(Date(CREATE, BEFORE, 2014-01-01 00:00:00))"), | |||||
("'modify:after:2010-05-09 10:11:12'", | |||||
"Tree(Date(MODIFY, AFTER, 2010-05-09 10:11:12))"), | |||||
# Symbol | |||||
("sym:foobar", "Tree(Symbol(ALL, ALL, String(u'foobar')))"), | |||||
("func:foo_bar", "Tree(Symbol(ALL, FUNCTION, String(u'foo_bar')))"), | |||||
("func:foo_bar()", "Tree(Symbol(ALL, FUNCTION, String(u'foo_bar')))"), | |||||
("class:FooBar", "Tree(Symbol(ALL, CLASS, String(u'FooBar')))"), | |||||
("var:foobar", "Tree(Symbol(ALL, VARIABLE, String(u'foobar')))"), | |||||
("var:r:foobar", "Tree(Symbol(ALL, VARIABLE, Regex(u'foobar')))"), | |||||
# Composition | |||||
("(a and b) or (c and d)", ", ".join([ | |||||
"Tree(BinaryOp(BinaryOp(Text(String(u'a'))", "AND", | |||||
"Text(String(u'b')))", "OR", "BinaryOp(Text(String(u'c'))", "AND", | |||||
"Text(String(u'd')))))"])), | |||||
("a and b or c and d", ", ".join([ | |||||
"Tree(BinaryOp(BinaryOp(Text(String(u'a'))", "AND", | |||||
"Text(String(u'b')))", "OR", "BinaryOp(Text(String(u'c'))", "AND", | |||||
"Text(String(u'd')))))"])), | |||||
("a and b or c or d", ", ".join([ | |||||
"Tree(BinaryOp(BinaryOp(Text(String(u'a'))", "AND", | |||||
"Text(String(u'b')))", "OR", "BinaryOp(Text(String(u'c'))", "OR", | |||||
"Text(String(u'd')))))"])), | |||||
("a and (b or c or d)", ", ".join([ | |||||
"Tree(BinaryOp(Text(String(u'a'))", "AND", | |||||
"BinaryOp(Text(String(u'b'))", "OR", "BinaryOp(Text(String(u'c'))", "OR", | |||||
"Text(String(u'd'))))))"])), | |||||
("a not b", ", ".join([ | |||||
"Tree(BinaryOp(Text(String(u'a'))", "AND", "UnaryOp(NOT", | |||||
"Text(String(u'b')))))"])), | |||||
# Unicode, Escaping | |||||
(r'lang:py "author:fo\\o \"bar\" baz\\"', ", ".join([ | |||||
"Tree(BinaryOp(Language(Python)", "AND", | |||||
"Author(String(u'fo\\\\o \"bar\" baz\\\\'))))"])), | |||||
('"author:Ben Kurtović"', "Tree(Author(String(u'Ben Kurtovi\\u0107')))") | |||||
] | |||||
class TestQueryParser(unittest.TestCase): | |||||
"""Unit tests for the query parser in :py:mod:`bitshift.query`.""" | |||||
def test_parse(self): | |||||
"""test full query parsing""" | |||||
for test, expected in TESTS: | |||||
self.assertEqual(expected, parse_query(test).serialize()) | |||||
if __name__ == "__main__": | |||||
unittest.main(verbosity=2) |