sourcedr: Remove incomplete sourcedr
This commit removes the incomplete source dependency review tool. Test: n/a Change-Id: I5f3b98aace3198262f35b45db552d64828533707
This commit is contained in:
@@ -1,64 +1,8 @@
|
||||
# Source Deps Reviewer
|
||||
# Source Dr.
|
||||
|
||||
## Synopsis
|
||||
This is a collection of source tree analysis tools.
|
||||
|
||||
This is a tool for labeling dependencies with a web interface.
|
||||
|
||||
Basically, it greps the specified directory for the given pattern,
|
||||
and let human reviewers label their dependencies, even code dependencies,
|
||||
which are code segments that are highly related to the specific pattern.
|
||||
|
||||
## Installation and Dependencies
|
||||
|
||||
This tool depends on [codesearch](https://github.com/google/codesearch)
|
||||
to generate regular expression index, please install them with:
|
||||
|
||||
```
|
||||
$ go get github.com/google/codesearch/cmd/cindex
|
||||
$ go get github.com/google/codesearch/cmd/csearch
|
||||
```
|
||||
|
||||
This tool depends on several Python packages,
|
||||
|
||||
```
|
||||
$ pip install -e .
|
||||
```
|
||||
|
||||
To run functional test, please do
|
||||
|
||||
```
|
||||
$ pip install -e .[dev]
|
||||
```
|
||||
|
||||
Prism, a code syntax highlighter is used.
|
||||
It can be found at https://github.com/PrismJS/prism
|
||||
|
||||
## Usage
|
||||
|
||||
Initialize a project:
|
||||
|
||||
```
|
||||
sourcedr init --source-dir [android-src] [project-dir]
|
||||
```
|
||||
|
||||
Scan the codebase:
|
||||
|
||||
```
|
||||
sourcedr scan
|
||||
```
|
||||
|
||||
If there are occurrences that are not reviewed, then review the occurrences
|
||||
with:
|
||||
|
||||
```
|
||||
sourcedr review
|
||||
```
|
||||
|
||||
Open browser and visit [http://localhost:5000](http://localhost:5000).
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
```
|
||||
$ python3 sourcedr/functional_tests.py
|
||||
```
|
||||
- [blueprint](blueprint) analyzes Android.bp and the dependencies between the
|
||||
modules.
|
||||
- [ninja](ninja) analyzes `$(OUT)/combined-${target}.ninja`, which contains all
|
||||
file dependencies.
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
flask
|
||||
Flask-testing
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Unit tests and functional tests runner."""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import unittest
|
||||
|
||||
|
||||
TESTS_DIR = os.path.join(os.path.dirname(__file__), 'sourcedr', 'tests')
|
||||
|
||||
|
||||
def main():
|
||||
""" Find and run unit tests and functional tests."""
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--verbose', '-v', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
verbosity = 2 if args.verbose else 1
|
||||
|
||||
loader = unittest.TestLoader()
|
||||
tests = loader.discover(TESTS_DIR, 'test_*.py')
|
||||
runner = unittest.runner.TextTestRunner(verbosity=verbosity)
|
||||
runner.run(tests)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,37 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='sourcedr',
|
||||
version='1.0',
|
||||
description='Shared Libs Deps Review Tool',
|
||||
url='https://android.googlesource.com/platform/development/+'
|
||||
'/master/vndk/tools/source-deps-reviewer/',
|
||||
packages=[
|
||||
'blueprint'
|
||||
'ninja',
|
||||
'sourcedr',
|
||||
],
|
||||
package_data={
|
||||
'sourcedr': [
|
||||
'defaults/pattern_db.csv',
|
||||
'defaults/sourcedr.json',
|
||||
'static/css/main.css',
|
||||
'static/js/main.js',
|
||||
'static/prism/css/prism.css',
|
||||
'static/prism/js/prism.js',
|
||||
],
|
||||
},
|
||||
install_requires=['flask'],
|
||||
extras_require={
|
||||
'dev': [
|
||||
'flask_testing'
|
||||
],
|
||||
},
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'sourcedr = sourcedr.commands:main',
|
||||
],
|
||||
}
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
@@ -1,324 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Code indexing and searching utilities.
|
||||
|
||||
This module will build n-gram file index with codesearch_ and use the index as
|
||||
a bloom filter to find the regular expression pattern.
|
||||
|
||||
In addition, language-specific filters are provided to ignore matchings in
|
||||
string literals or comments in the source code.
|
||||
|
||||
.. _codesearch: https://github.com/google/codesearch
|
||||
"""
|
||||
|
||||
import collections
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
|
||||
class ClikeFilter(object):
|
||||
def __init__(self, skip_literals=True, skip_comments=True):
|
||||
self.file_exts = (b'.c', b'.cpp', b'.cc', b'.cxx', b'.h', b'.hpp',
|
||||
b'.hxx', b'.java')
|
||||
self.skip_literals = skip_literals
|
||||
self.skip_comments = skip_comments
|
||||
|
||||
def process(self, code):
|
||||
if self.skip_comments:
|
||||
# Remove // comments.
|
||||
code = re.sub(b'//[^\\r\\n]*[\\r\\n]', b'', code)
|
||||
# Remove matched /* */ comments.
|
||||
code = re.sub(b'/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/', b'', code)
|
||||
if self.skip_literals:
|
||||
# Remove matching quotes.
|
||||
code = re.sub(b'"(?:\\\\?.)*?"', b'', code)
|
||||
code = re.sub(b'\'(?:\\\\?.)*?\'', b'', code)
|
||||
return code
|
||||
|
||||
def get_span(self, code):
|
||||
span = []
|
||||
if self.skip_comments:
|
||||
# Remove // comments.
|
||||
p = re.compile(b'//[^\\r\\n]*[\\r\\n]')
|
||||
for m in p.finditer(code):
|
||||
span.append(m.span())
|
||||
# Remove matched /* */ comments.
|
||||
p = re.compile(b'/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/')
|
||||
for m in p.finditer(code):
|
||||
span.append(m.span())
|
||||
if self.skip_literals:
|
||||
# Remove matching quotes.
|
||||
p = re.compile(b'"(?:\\\\?.)*?"')
|
||||
for m in p.finditer(code):
|
||||
span.append(m.span())
|
||||
p = re.compile(b'\'(?:\\\\?.)*?\'')
|
||||
for m in p.finditer(code):
|
||||
span.append(m.span())
|
||||
return span
|
||||
|
||||
class PyFilter(object):
|
||||
def __init__(self, skip_literals=True, skip_comments=True):
|
||||
self.file_exts = (b'.py',)
|
||||
self.skip_literals = skip_literals
|
||||
self.skip_comments = skip_comments
|
||||
|
||||
def process(self, code):
|
||||
if self.skip_comments:
|
||||
# Remove # comments
|
||||
code = re.sub(b'#[^\\r\\n]*[\\r\\n]', b'', code)
|
||||
if self.skip_literals:
|
||||
# Remove matching quotes.
|
||||
code = re.sub(b'"(?:\\\\?.)*?"', b'', code)
|
||||
code = re.sub(b'\'(?:\\\\?.)*?\'', b'', code)
|
||||
return code
|
||||
|
||||
def get_span(self, code):
|
||||
span = []
|
||||
if self.skip_comments:
|
||||
# Remove # comments.
|
||||
p = re.compile(b'#[^\\r\\n]*[\\r\\n]')
|
||||
for m in p.finditer(code):
|
||||
span.append(m.span())
|
||||
if self.skip_literals:
|
||||
# Remove matching quotes.
|
||||
p = re.compile(b'"(?:\\\\?.)*?"')
|
||||
for m in p.finditer(code):
|
||||
span.append(m.span())
|
||||
p = re.compile(b'\'(?:\\\\?.)*?\'')
|
||||
for m in p.finditer(code):
|
||||
span.append(m.span())
|
||||
return span
|
||||
|
||||
class AssemblyFilter(object):
|
||||
def __init__(self, skip_literals=True, skip_comments=True):
|
||||
self.file_exts = (b'.s', b'.S')
|
||||
self.skip_literals = skip_literals
|
||||
self.skip_comments = skip_comments
|
||||
|
||||
def process(self, code):
|
||||
if self.skip_comments:
|
||||
# Remove @ comments
|
||||
code = re.sub(b'@[^\\r\\n]*[\\r\\n]', b'', code)
|
||||
# Remove // comments.
|
||||
code = re.sub(b'//[^\\r\\n]*[\\r\\n]', b'', code)
|
||||
# Remove matched /* */ comments.
|
||||
code = re.sub(b'/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/', b'', code)
|
||||
return code
|
||||
|
||||
def get_span(self, code):
|
||||
span = []
|
||||
if self.skip_comments:
|
||||
# Remove # comments.
|
||||
p = re.compile(b'@[^\\r\\n]*[\\r\\n]')
|
||||
for m in p.finditer(code):
|
||||
span.append(m.span())
|
||||
# Remove // comments
|
||||
p = re.compile(b'//[^\\r\\n]*[\\r\\n]')
|
||||
for m in p.finditer(code):
|
||||
span.append(m.span())
|
||||
# Remove matched /* */ comments
|
||||
p = re.compile(b'/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/')
|
||||
for m in p.finditer(code):
|
||||
span.append(m.span())
|
||||
return span
|
||||
|
||||
class MkFilter(object):
|
||||
def __init__(self, skip_literals=True, skip_comments=True):
|
||||
self.file_exts = (b'.mk',)
|
||||
self.skip_literals = skip_literals
|
||||
self.skip_comments = skip_comments
|
||||
|
||||
def process(self, code):
|
||||
if self.skip_comments:
|
||||
# Remove # comments
|
||||
code = re.sub(b'#[^\\r\\n]*[\\r\\n]', b'', code)
|
||||
return code
|
||||
|
||||
def get_span(self, code):
|
||||
span = []
|
||||
if self.skip_comments:
|
||||
# Remove # comments.
|
||||
p = re.compile(b'#[^\\r\\n]*[\\r\\n]')
|
||||
for m in p.finditer(code):
|
||||
span.append(m.span())
|
||||
return span
|
||||
|
||||
class BpFilter(object):
|
||||
def __init__(self, skip_literals=True, skip_comments=True):
|
||||
self.file_exts = (b'.bp',)
|
||||
self.skip_literals = skip_literals
|
||||
self.skip_comments = skip_comments
|
||||
|
||||
def process(self, code):
|
||||
if self.skip_comments:
|
||||
# Remove // comments
|
||||
code = re.sub(b'//[^\\r\\n]*[\\r\\n]', b'', code)
|
||||
return code
|
||||
|
||||
def get_span(self, code):
|
||||
span = []
|
||||
if self.skip_comments:
|
||||
# Remove // comments.
|
||||
p = re.compile(b'//[^\\r\\n]*[\\r\\n]')
|
||||
for m in p.finditer(code):
|
||||
span.append(m.span())
|
||||
return span
|
||||
|
||||
class PathFilter(object):
|
||||
def __init__(self, file_ext_black_list=tuple(),
|
||||
file_name_black_list=tuple(),
|
||||
path_component_black_list=tuple()):
|
||||
self.file_ext_black_list = set(
|
||||
x.encode('utf-8') for x in file_ext_black_list)
|
||||
self.file_name_black_list = set(
|
||||
x.encode('utf-8') for x in file_name_black_list)
|
||||
self.path_component_black_list = set(
|
||||
x.encode('utf-8') for x in path_component_black_list)
|
||||
|
||||
def should_skip(self, path):
|
||||
file_name = os.path.basename(path)
|
||||
file_ext = os.path.splitext(file_name)[1]
|
||||
|
||||
if file_ext.lower() in self.file_ext_black_list:
|
||||
return True
|
||||
if file_name in self.file_name_black_list:
|
||||
return True
|
||||
return any(patt in path for patt in self.path_component_black_list)
|
||||
|
||||
class CodeSearch(object):
|
||||
DEFAULT_NAME = 'csearchindex'
|
||||
|
||||
@classmethod
|
||||
def get_default_path(cls, project_dir):
|
||||
return os.path.join(project_dir, 'tmp', cls.DEFAULT_NAME)
|
||||
|
||||
def __init__(self, root_dir, index_file_path, path_filter=None):
|
||||
self.path = os.path.abspath(index_file_path)
|
||||
self._root_dir = os.path.abspath(root_dir)
|
||||
self._env = dict(os.environ)
|
||||
self._env['CSEARCHINDEX'] = self.path
|
||||
self._filters = {}
|
||||
self._path_filter = PathFilter() if path_filter is None else path_filter
|
||||
|
||||
def _run_cindex(self, options):
|
||||
subprocess.check_call(['cindex'] + options, env=self._env,
|
||||
cwd=self._root_dir, stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL)
|
||||
|
||||
def _run_csearch(self, options):
|
||||
if not os.path.exists(self.path):
|
||||
raise ValueError('Failed to find ' + self.path)
|
||||
return subprocess.check_output(['csearch'] + options, env=self._env,
|
||||
cwd=self._root_dir,
|
||||
stderr=subprocess.DEVNULL)
|
||||
|
||||
def add_filter(self, lang_filter):
|
||||
for ext in lang_filter.file_exts:
|
||||
self._filters[ext] = lang_filter
|
||||
|
||||
def add_default_filters(self, skip_literals=True, skip_comments=True):
|
||||
self.add_filter(ClikeFilter(skip_literals, skip_comments))
|
||||
self.add_filter(AssemblyFilter(skip_literals, skip_comments))
|
||||
self.add_filter(PyFilter(skip_literals, skip_comments))
|
||||
self.add_filter(MkFilter(skip_literals, skip_comments))
|
||||
self.add_filter(BpFilter(skip_literals, skip_comments))
|
||||
|
||||
def build_index(self, remove_existing_index=True):
|
||||
if remove_existing_index and os.path.exists(self.path):
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
os.remove(self.path)
|
||||
os.makedirs(os.path.dirname(self.path), exist_ok=True)
|
||||
self._run_cindex([self._root_dir])
|
||||
|
||||
def _sanitize_code(self, file_path):
|
||||
with open(file_path, 'rb') as f:
|
||||
code = f.read()
|
||||
file_name = os.path.basename(file_path)
|
||||
f, ext = os.path.splitext(file_name)
|
||||
try:
|
||||
code = self._filters[ext].process(code)
|
||||
except KeyError:
|
||||
pass
|
||||
return code
|
||||
|
||||
def _remove_prefix(self, raw_grep):
|
||||
ret = b''
|
||||
patt = re.compile(b'([^:]+):(\\d+):(.*)$')
|
||||
for line in raw_grep.split(b'\n'):
|
||||
match = patt.match(line)
|
||||
if not match:
|
||||
continue
|
||||
file_path = os.path.relpath(match.group(1),
|
||||
self._root_dir.encode('utf-8'))
|
||||
line_no = match.group(2)
|
||||
code = match.group(3)
|
||||
ret += file_path + b':' + line_no + b':' + code + b'\n'
|
||||
return ret
|
||||
|
||||
def process_grep(self, raw_grep, pattern, is_regex):
|
||||
pattern = pattern.encode('utf-8')
|
||||
if not is_regex:
|
||||
pattern = re.escape(pattern)
|
||||
# Limit pattern not to match exceed a line
|
||||
# Since grep may get multiple patterns in a single entry
|
||||
pattern = re.compile(pattern + b'[^\\n\\r]*(?:\\n|\\r|$)')
|
||||
|
||||
patt = re.compile(b'([^:]+):(\\d+):(.*)$')
|
||||
suspect = collections.defaultdict(list)
|
||||
for line in raw_grep.split(b'\n'):
|
||||
match = patt.match(line)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
file_path = match.group(1)
|
||||
line_no = match.group(2)
|
||||
code = match.group(3)
|
||||
|
||||
if self._path_filter.should_skip(file_path):
|
||||
continue
|
||||
|
||||
abs_file_path = os.path.join(self._root_dir.encode('utf-8'),
|
||||
file_path)
|
||||
# Check if any pattern can be found after sanitize_code
|
||||
if not pattern.search(self._sanitize_code(abs_file_path)):
|
||||
continue
|
||||
suspect[abs_file_path].append((file_path, line_no, code))
|
||||
|
||||
suspect = sorted(suspect.items())
|
||||
|
||||
processed = b''
|
||||
for file_path, entries in suspect:
|
||||
with open(file_path, 'rb') as f:
|
||||
code = f.read()
|
||||
# deep filter
|
||||
file_name = os.path.basename(file_path)
|
||||
f, ext = os.path.splitext(file_name)
|
||||
try:
|
||||
span = self._filters[ext].get_span(code)
|
||||
except KeyError:
|
||||
span = []
|
||||
|
||||
matchers = [m for m in pattern.finditer(code)]
|
||||
for i, matcher in enumerate(matchers):
|
||||
if not span or all(span_ent[0] > matcher.start() or
|
||||
span_ent[1] <= matcher.start()
|
||||
for span_ent in span):
|
||||
processed += (entries[i][0] + b':' +
|
||||
entries[i][1] + b':' +
|
||||
entries[i][2] + b'\n')
|
||||
|
||||
return processed
|
||||
|
||||
def raw_grep(self, pattern):
|
||||
try:
|
||||
return self._remove_prefix(self._run_csearch(['-n', pattern]))
|
||||
except subprocess.CalledProcessError as e:
|
||||
if e.output == b'':
|
||||
return b''
|
||||
raise
|
||||
|
||||
def raw_search(self, pattern, is_regex):
|
||||
if not is_regex:
|
||||
pattern = re.escape(pattern)
|
||||
return self.raw_grep(pattern)
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Parser for command line options."""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
from sourcedr.commands import collect, init, scan, review
|
||||
|
||||
|
||||
def main():
|
||||
"""Register sub-commands, parse command line options, and delegate."""
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest='subcmd')
|
||||
commands = {}
|
||||
|
||||
def _register_subcmd(name, init_argparse):
|
||||
commands[name] = init_argparse(subparsers)
|
||||
|
||||
_register_subcmd('init', init.init_argparse)
|
||||
_register_subcmd('scan', scan.init_argparse)
|
||||
_register_subcmd('review', review.init_argparse)
|
||||
_register_subcmd('collect', collect.init_argparse)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
func = commands[args.subcmd]
|
||||
except KeyError:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(func(args))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""`sourcedr collect` command."""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from sourcedr.project import Project
|
||||
from sourcedr.map import (
|
||||
link_build_dep_and_review_data, load_build_dep_ninja, load_review_data)
|
||||
|
||||
|
||||
def init_argparse(parsers):
|
||||
"""Initialize argument parser for `sourcedr collect`."""
|
||||
parser = parsers.add_parser('collect', help='Open web-based review UI')
|
||||
parser.add_argument('input', help='Ninja file')
|
||||
parser.add_argument('--ninja-deps')
|
||||
parser.add_argument('--project-dir', default='.')
|
||||
parser.add_argument('-o', '--output', required=True)
|
||||
return run
|
||||
|
||||
|
||||
def run(args):
|
||||
project_dir = os.path.expanduser(args.project_dir)
|
||||
project = Project(project_dir)
|
||||
|
||||
# Load build dependency file
|
||||
try:
|
||||
dep = load_build_dep_ninja(args.input, project.source_dir,
|
||||
args.ninja_deps)
|
||||
except IOError:
|
||||
print('error: Failed to open build dependency file:', args.input,
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Load review data
|
||||
table = load_review_data(project.review_db.path)
|
||||
|
||||
# Link build dependency file and review data
|
||||
res = link_build_dep_and_review_data(dep, table)
|
||||
|
||||
# Write the output file
|
||||
with open(args.output, 'w') as f:
|
||||
json.dump(res, f, sort_keys=True, indent=4)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""`sourcedr init` command."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from sourcedr.project import Project
|
||||
|
||||
|
||||
def _is_dir_empty(path):
|
||||
"""Determine whether the given path is an empty directory."""
|
||||
return len(os.listdir(path)) == 0
|
||||
|
||||
|
||||
def init_argparse(parsers):
|
||||
"""Initialize argument parser for `sourcedr init`."""
|
||||
parser = parsers.add_parser('init', help='Start a new review project')
|
||||
parser.add_argument('--project-dir', default='.')
|
||||
parser.add_argument('--android-root', required=True,
|
||||
help='Android source tree root directory')
|
||||
return run
|
||||
|
||||
|
||||
def run(args):
|
||||
"""Main function for `sourcedr init`."""
|
||||
|
||||
if args.project_dir == '.' and not _is_dir_empty(args.project_dir):
|
||||
print('error: Current working directory is not an empty directory.',
|
||||
file=sys.stderr)
|
||||
|
||||
project_dir = os.path.expanduser(args.project_dir)
|
||||
source_dir = os.path.expanduser(args.android_root)
|
||||
|
||||
Project.get_or_create_project_dir(project_dir, source_dir)
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""`sourcedr review` command."""
|
||||
|
||||
import os
|
||||
|
||||
from sourcedr.project import Project
|
||||
from sourcedr.server import create_app
|
||||
|
||||
|
||||
def init_argparse(parsers):
|
||||
"""Initialize argument parser for `sourcedr init`."""
|
||||
parser = parsers.add_parser('review', help='Open web-based review UI')
|
||||
parser.add_argument('--project-dir', default='.')
|
||||
parser.add_argument('--rebuild-csearch-index', action='store_true',
|
||||
help='Re-build the existing csearch index file')
|
||||
return run
|
||||
|
||||
|
||||
def run(args):
|
||||
"""Main function for `sourcedr init`."""
|
||||
project_dir = os.path.expanduser(args.project_dir)
|
||||
|
||||
project = Project(project_dir)
|
||||
project.update_csearch_index(args.rebuild_csearch_index)
|
||||
project.update_review_db()
|
||||
|
||||
app = create_app(project)
|
||||
app.run()
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""`sourcedr scan` command."""
|
||||
|
||||
|
||||
def init_argparse(parsers):
|
||||
"""Initialize argument parser for `sourcedr scan`."""
|
||||
parsers.add_parser('scan', help='Scan all pattern occurrences')
|
||||
return run
|
||||
|
||||
|
||||
def run(_):
|
||||
"""Main function for `sourcedr scan`."""
|
||||
print('error: Need human review. Run: `sourcedr review`')
|
||||
@@ -1 +0,0 @@
|
||||
1,\bdlopen\b
|
||||
|
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"source_dir": "",
|
||||
"file_ext_blacklist": [
|
||||
".1",
|
||||
".ac",
|
||||
".cmake",
|
||||
".html",
|
||||
".info",
|
||||
".la",
|
||||
".m4",
|
||||
".map",
|
||||
".md",
|
||||
".py",
|
||||
".rst",
|
||||
".sh",
|
||||
".sym",
|
||||
".txt",
|
||||
".xml"
|
||||
],
|
||||
"file_name_blacklist": [
|
||||
"CHANGES.0",
|
||||
"ChangeLog",
|
||||
"config.h.in",
|
||||
"configure",
|
||||
"configure.in",
|
||||
"configure.linaro",
|
||||
"libtool"
|
||||
],
|
||||
"path_component_blacklist": [
|
||||
".git",
|
||||
".repo",
|
||||
"autom4te.cache",
|
||||
"binutils",
|
||||
"dejagnu",
|
||||
"llvm/Config/Config"
|
||||
]
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""This command maps source file review results to compiled binaries.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ninja import ninja
|
||||
from sourcedr.review_db import ReviewDB
|
||||
|
||||
|
||||
def load_build_dep_graph(graph):
|
||||
# Collect all shared libraries
|
||||
shared_libs = set()
|
||||
for key, value in graph.items():
|
||||
if key.split('.')[-1] == 'so':
|
||||
shared_libs.add(key)
|
||||
for v in value:
|
||||
if v.split('.')[-1] == 'so':
|
||||
shared_libs.add(v)
|
||||
|
||||
# Collect transitive closures
|
||||
dep = {}
|
||||
for s in shared_libs:
|
||||
visited = set()
|
||||
stack = [s]
|
||||
while stack:
|
||||
v = stack.pop()
|
||||
if v not in visited:
|
||||
visited.add(v)
|
||||
try:
|
||||
stack.extend(x for x in graph[v]
|
||||
if x not in visited and not x.endswith('.so')
|
||||
and not x.endswith('.toc'))
|
||||
except KeyError:
|
||||
pass
|
||||
visited.remove(s)
|
||||
dep[s] = visited
|
||||
|
||||
return dep
|
||||
|
||||
|
||||
def load_build_dep_ninja(ninja_path, work_dir, ninja_deps=None):
|
||||
manifest = ninja.Parser().parse(ninja_path, 'utf-8', ninja_deps)
|
||||
graph = collections.defaultdict(set)
|
||||
for build in manifest.builds:
|
||||
for path in itertools.chain(build.explicit_outs, build.implicit_outs):
|
||||
ins = graph[path]
|
||||
ins.update(build.explicit_ins)
|
||||
ins.update(build.implicit_ins)
|
||||
ins.update(build.depfile_implicit_ins)
|
||||
return load_build_dep_graph(graph)
|
||||
|
||||
|
||||
def load_build_dep_file(fp):
|
||||
return load_build_dep_graph(json.load(fp))
|
||||
|
||||
|
||||
def load_build_dep_file_from_path(path):
|
||||
with open(path, 'r') as fp:
|
||||
return load_build_dep_file(fp)
|
||||
|
||||
|
||||
def load_review_data(path):
|
||||
table = collections.defaultdict(list)
|
||||
review_db = ReviewDB(path, None)
|
||||
for key, item in review_db.data.items():
|
||||
table[key.split(':')[0]] += item[0]
|
||||
return table
|
||||
|
||||
|
||||
def link_build_dep_and_review_data(dep, table):
|
||||
res = collections.defaultdict(list)
|
||||
for out, ins in dep.items():
|
||||
try:
|
||||
res[out] += table[out]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
for in_file in ins:
|
||||
try:
|
||||
res[out] += table[in_file]
|
||||
except KeyError:
|
||||
pass
|
||||
return res
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
import os
|
||||
|
||||
|
||||
class PatternDB(object):
|
||||
"""Pattern database for patterns to be searched in the source tree.
|
||||
"""
|
||||
|
||||
DEFAULT_NAME = 'pattern_db.csv'
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_default_path(cls, project_dir):
|
||||
return os.path.join(project_dir, cls.DEFAULT_NAME)
|
||||
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
self.data = self._load()
|
||||
|
||||
|
||||
def _load(self):
|
||||
with open(self.path, 'r') as f:
|
||||
patterns = []
|
||||
is_regexs = []
|
||||
for line in f:
|
||||
line = line.rstrip('\n')
|
||||
sp = line.split(',')
|
||||
is_regexs.append(sp[0])
|
||||
patterns.append(','.join(sp[1:]))
|
||||
return (patterns, is_regexs)
|
||||
|
||||
|
||||
def load(self):
|
||||
self.data = self._load()
|
||||
return self.data
|
||||
|
||||
|
||||
def save_new_pattern(self, patt, is_regex):
|
||||
"""Add a pattern to the database."""
|
||||
with open(self.path, 'a') as f:
|
||||
f.write(str(int(is_regex)) + ',' + patt + '\n')
|
||||
@@ -1,173 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""SourceDR project configurations and databases.
|
||||
|
||||
`Project` class holds configuration files, review databases, pattern databases,
|
||||
and `codesearch` index files.
|
||||
"""
|
||||
|
||||
import collections
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from sourcedr.codesearch import CodeSearch, PathFilter
|
||||
from sourcedr.pattern_db import PatternDB
|
||||
from sourcedr.review_db import ReviewDB
|
||||
from sourcedr.utils import LockedFile
|
||||
|
||||
|
||||
class Config(object):
|
||||
"""SourceDR project configuration file."""
|
||||
|
||||
DEFAULT_NAME = 'sourcedr.json'
|
||||
|
||||
_PATH_TRAVERSAL_ATTRS = (
|
||||
'file_ext_blacklist', 'file_name_blacklist',
|
||||
'path_component_blacklist')
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_default_path(cls, project_dir):
|
||||
"""Get the default path of the configuration file under a project
|
||||
directory."""
|
||||
return os.path.join(project_dir, cls.DEFAULT_NAME)
|
||||
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
self.source_dir = None
|
||||
self.file_ext_blacklist = set()
|
||||
self.file_name_blacklist = set()
|
||||
self.path_component_blacklist = set()
|
||||
|
||||
|
||||
def load(self):
|
||||
"""Load the project configuration from the JSON file."""
|
||||
with open(self.path, 'r') as config_fp:
|
||||
config_json = json.load(config_fp)
|
||||
for key, value in config_json.items():
|
||||
if key == 'source_dir':
|
||||
self.source_dir = value
|
||||
elif key in self._PATH_TRAVERSAL_ATTRS:
|
||||
setattr(self, key, set(value))
|
||||
else:
|
||||
raise ValueError('unknown config name: ' + key)
|
||||
|
||||
|
||||
def save(self):
|
||||
"""Save the project configuration to the JSON file."""
|
||||
with LockedFile(self.path, 'x') as config_fp:
|
||||
config = collections.OrderedDict()
|
||||
config['source_dir'] = self.source_dir
|
||||
for key in self._PATH_TRAVERSAL_ATTRS:
|
||||
config[key] = sorted(getattr(self, key))
|
||||
json.dump(config, config_fp, indent=2)
|
||||
|
||||
|
||||
class Project(object):
|
||||
"""SourceDR project configuration files and databases."""
|
||||
|
||||
def __init__(self, project_dir):
|
||||
"""Load a project from a given project directory."""
|
||||
|
||||
project_dir = os.path.abspath(project_dir)
|
||||
self.project_dir = project_dir
|
||||
|
||||
if not os.path.isdir(project_dir):
|
||||
raise ValueError('project directory not found: ' + project_dir)
|
||||
|
||||
# Load configuration files
|
||||
config_path = Config.get_default_path(project_dir)
|
||||
self.config = Config(config_path)
|
||||
self.config.load()
|
||||
|
||||
# Recalculate source directory
|
||||
self.source_dir = os.path.abspath(
|
||||
os.path.join(project_dir, self.config.source_dir))
|
||||
|
||||
# csearchindex file
|
||||
path_filter = PathFilter(self.config.file_ext_blacklist,
|
||||
self.config.file_name_blacklist,
|
||||
self.config.path_component_blacklist)
|
||||
csearch_index_path = CodeSearch.get_default_path(project_dir)
|
||||
self.codesearch = CodeSearch(self.source_dir, csearch_index_path,
|
||||
path_filter)
|
||||
self.codesearch.add_default_filters()
|
||||
|
||||
# Review database file
|
||||
review_db_path = ReviewDB.get_default_path(project_dir)
|
||||
self.review_db = ReviewDB(review_db_path, self.codesearch)
|
||||
|
||||
# Pattern database file
|
||||
pattern_db_path = PatternDB.get_default_path(project_dir)
|
||||
self.pattern_db = PatternDB(pattern_db_path)
|
||||
|
||||
# Sanity checks
|
||||
self._check_source_dir()
|
||||
self._check_lock_files()
|
||||
|
||||
|
||||
def update_csearch_index(self, remove_existing_index):
|
||||
"""Create or update codesearch index."""
|
||||
self.codesearch.build_index(remove_existing_index)
|
||||
|
||||
|
||||
def update_review_db(self):
|
||||
"""Update the entries in the review database."""
|
||||
patterns, is_regexs = self.pattern_db.load()
|
||||
self.review_db.find(patterns, is_regexs)
|
||||
|
||||
|
||||
def _check_source_dir(self):
|
||||
"""Check the availability of the source directory."""
|
||||
if not os.path.isdir(self.source_dir):
|
||||
raise ValueError('source directory not found: ' + self.source_dir)
|
||||
|
||||
|
||||
def _check_lock_files(self):
|
||||
"""Check whether there are some lock files."""
|
||||
for path in (self.config.path, self.review_db.path,
|
||||
self.pattern_db.path):
|
||||
if LockedFile.is_locked(path):
|
||||
raise ValueError('file locked: ' + path)
|
||||
|
||||
|
||||
@classmethod
|
||||
def create_project_dir(cls, project_dir, source_dir):
|
||||
"""Create a directory for a new project and setup default
|
||||
configurations."""
|
||||
|
||||
if not os.path.isdir(source_dir):
|
||||
raise ValueError('source directory not found: ' + source_dir)
|
||||
|
||||
os.makedirs(project_dir, exist_ok=True)
|
||||
|
||||
# Compute the relative path between project_dir and source_dir
|
||||
project_dir = os.path.abspath(project_dir)
|
||||
source_dir = os.path.relpath(os.path.abspath(source_dir), project_dir)
|
||||
|
||||
# Copy default files
|
||||
defaults_dir = os.path.join(os.path.dirname(__file__), 'defaults')
|
||||
for name in (Config.DEFAULT_NAME, PatternDB.DEFAULT_NAME):
|
||||
shutil.copyfile(os.path.join(defaults_dir, name),
|
||||
os.path.join(project_dir, name))
|
||||
|
||||
# Update the source directory in the configuration file
|
||||
config_path = Config.get_default_path(project_dir)
|
||||
config = Config(config_path)
|
||||
config.load()
|
||||
config.source_dir = source_dir
|
||||
config.save()
|
||||
|
||||
return Project(project_dir)
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_or_create_project_dir(cls, project_dir, source_dir):
|
||||
config_file_path = Config.get_default_path(project_dir)
|
||||
if os.path.exists(config_file_path):
|
||||
return Project(project_dir)
|
||||
else:
|
||||
return cls.create_project_dir(project_dir, source_dir)
|
||||
@@ -1,94 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
class ReviewDB(object):
|
||||
DEFAULT_NAME = 'review_db.json'
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_default_path(cls, project_dir):
|
||||
return os.path.join(project_dir, cls.DEFAULT_NAME)
|
||||
|
||||
|
||||
def __init__(self, path, codesearch):
|
||||
self.path = path
|
||||
self._cs = codesearch
|
||||
try:
|
||||
self.data = self._load_data()
|
||||
except FileNotFoundError:
|
||||
self.data = {}
|
||||
|
||||
|
||||
# patterns and is_regexs are lists
|
||||
def find(self, patterns, is_regexs):
|
||||
# they shouldn't be empty
|
||||
assert patterns and is_regexs
|
||||
processed = b''
|
||||
for pattern, is_regex in zip(patterns, is_regexs):
|
||||
if not is_regex:
|
||||
pattern = re.escape(pattern)
|
||||
raw_grep = self._cs.raw_grep(pattern)
|
||||
if raw_grep == b'':
|
||||
continue
|
||||
processed += self._cs.process_grep(raw_grep, pattern, is_regex)
|
||||
self.to_json(processed)
|
||||
|
||||
|
||||
def add_pattern(self, pattern, is_regex):
|
||||
if not is_regex:
|
||||
pattern = re.escape(pattern)
|
||||
raw_grep = self._cs.raw_grep(pattern)
|
||||
if raw_grep == b'':
|
||||
return
|
||||
processed = self._cs.process_grep(raw_grep, pattern, is_regex)
|
||||
self.add_to_json(processed)
|
||||
|
||||
|
||||
def to_json(self, processed):
|
||||
data = {}
|
||||
patt = re.compile('([^:]+):(\\d+):(.*)$')
|
||||
for line in processed.decode('utf-8').split('\n'):
|
||||
match = patt.match(line)
|
||||
if not match:
|
||||
continue
|
||||
data[line] = ([], [])
|
||||
|
||||
# if old data exists, perform merge
|
||||
if os.path.exists(self.path):
|
||||
data.update(self._load_data())
|
||||
|
||||
self._save_data(data)
|
||||
self.data = self._load_data()
|
||||
|
||||
|
||||
def add_to_json(self, processed):
|
||||
# Load all matched grep.
|
||||
data = self._load_data()
|
||||
patt = re.compile('([^:]+):(\\d+):(.*)$')
|
||||
for line in processed.decode('utf-8').split('\n'):
|
||||
match = patt.match(line)
|
||||
if not match:
|
||||
continue
|
||||
data[line] = ([], [])
|
||||
|
||||
self._save_data(data)
|
||||
self.data = self._load_data()
|
||||
|
||||
|
||||
def add_label(self, label, deps, codes):
|
||||
self.data[label] = (deps, codes)
|
||||
self._save_data(self.data)
|
||||
|
||||
|
||||
def _save_data(self, data):
|
||||
with open(self.path, 'w') as data_fp:
|
||||
json.dump(data, data_fp, sort_keys=True, indent=4)
|
||||
|
||||
|
||||
def _load_data(self):
|
||||
with open(self.path, 'r') as data_fp:
|
||||
return json.load(data_fp)
|
||||
@@ -1,165 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import collections
|
||||
import functools
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
from flask import (
|
||||
Blueprint, Flask, current_app, jsonify, render_template, request)
|
||||
|
||||
|
||||
codereview = Blueprint('codereview', '__name__', 'templates')
|
||||
|
||||
|
||||
# whether the code segment is exactly in file
|
||||
def same(fl, code, source_dir):
|
||||
fl = os.path.join(source_dir, fl)
|
||||
with open(fl, 'r') as f:
|
||||
fc = f.read()
|
||||
return code in fc
|
||||
|
||||
|
||||
# check if the file needes to be reiewed again
|
||||
def check(codes, source_dir):
|
||||
ret = []
|
||||
for item in codes:
|
||||
fl = item.split(':')[0]
|
||||
code = item[len(fl) + 1:]
|
||||
ret.append(same(fl, code, source_dir))
|
||||
return ret
|
||||
|
||||
|
||||
@codereview.route('/get_started')
|
||||
def _get_started():
|
||||
project = current_app.config.project
|
||||
source_dir = project.source_dir
|
||||
review_db = project.review_db
|
||||
|
||||
lst, done= [], []
|
||||
for key, item in sorted(review_db.data.items()):
|
||||
lst.append(key)
|
||||
if item[0]:
|
||||
done.append(all(check(item[1], source_dir)))
|
||||
else:
|
||||
done.append(False)
|
||||
|
||||
pattern_lst = project.pattern_db.load()[0]
|
||||
abs_path = os.path.abspath(source_dir)
|
||||
|
||||
return jsonify(lst=json.dumps(lst),
|
||||
done=json.dumps(done),
|
||||
pattern_lst=json.dumps(pattern_lst),
|
||||
path_prefix=os.path.join(abs_path, ''))
|
||||
|
||||
|
||||
@codereview.route('/load_file')
|
||||
def _load_file():
|
||||
project = current_app.config.project
|
||||
source_dir = project.source_dir
|
||||
review_db = project.review_db
|
||||
|
||||
path = request.args.get('path')
|
||||
|
||||
if path not in review_db.data.keys():
|
||||
print('No such entry', path)
|
||||
return jsonify(result='')
|
||||
deps, codes = review_db.data[path]
|
||||
|
||||
return jsonify(deps=json.dumps(deps), codes=json.dumps(codes),
|
||||
okays=json.dumps(check(codes, source_dir)))
|
||||
|
||||
|
||||
@codereview.route('/get_file')
|
||||
def _get_file():
|
||||
path = request.args.get('path')
|
||||
path = os.path.join(current_app.config.project.source_dir, path)
|
||||
|
||||
if not os.path.exists(path):
|
||||
return jsonify(result='No such file')
|
||||
with open(path, 'r') as f:
|
||||
code = f.read()
|
||||
|
||||
return jsonify(result=code)
|
||||
|
||||
|
||||
@codereview.route('/save_all')
|
||||
def _save_all():
|
||||
label = request.args.get('label')
|
||||
deps = json.loads(request.args.get('deps'))
|
||||
codes = json.loads(request.args.get('codes'))
|
||||
|
||||
project = current_app.config.project
|
||||
review_db = project.review_db
|
||||
review_db.add_label(label, deps, codes)
|
||||
|
||||
return jsonify(result='done')
|
||||
|
||||
|
||||
# This function add pattern to grep
|
||||
@codereview.route('/add_pattern')
|
||||
def _add_pattern():
|
||||
patt = request.args.get('pattern')
|
||||
is_regex = request.args.get('is_regex')
|
||||
engine = current_app.config.project.review_db
|
||||
engine.add_pattern(patt, is_regex)
|
||||
|
||||
project = current_app.config.project
|
||||
project.pattern_db.save_new_pattern(patt, is_regex)
|
||||
return jsonify(result='done')
|
||||
|
||||
|
||||
# This function does a temporary grep to the directory
|
||||
# Not adding the result to database
|
||||
@codereview.route('/temporary_search')
|
||||
def _temporary_search():
|
||||
path = request.args.get('path')
|
||||
patt = request.args.get('pattern')
|
||||
is_regex = request.args.get('is_regex')
|
||||
codesearch = current_app.config.project.codesearch
|
||||
result = codesearch.raw_search(patt, is_regex).decode('utf-8')
|
||||
dic = collections.defaultdict(list)
|
||||
patt = re.compile('([^:]+):(\\d+):(.*)$')
|
||||
for line in result.split('\n'):
|
||||
match = patt.match(line)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
file_path = match.group(1)
|
||||
line_no = match.group(2)
|
||||
code = match.group(3)
|
||||
dic[file_path].append((line_no, code))
|
||||
|
||||
def compare(item1, item2):
|
||||
key1, value1 = item1
|
||||
key2, value2 = item2
|
||||
cnt1 = os.path.commonprefix([path, key1]).count('/')
|
||||
cnt2 = os.path.commonprefix([path, key2]).count('/')
|
||||
e1 = os.path.relpath(key1, path).count('/')
|
||||
e2 = os.path.relpath(key2, path).count('/')
|
||||
# prefer smaller edit distance
|
||||
if e1 < e2: return -1
|
||||
if e2 < e1: return 1
|
||||
# prefer deeper common ancestor
|
||||
if cnt1 > cnt2: return -1
|
||||
if cnt2 > cnt1: return 1
|
||||
# lexicographical order
|
||||
if key1 < key2: return -1
|
||||
if key2 < key1: return 1
|
||||
return 0
|
||||
|
||||
result = sorted(dic.items(), key=functools.cmp_to_key(compare))
|
||||
return jsonify(result=json.dumps(result))
|
||||
|
||||
|
||||
@codereview.route('/')
|
||||
def render():
|
||||
return render_template('index.html')
|
||||
|
||||
|
||||
def create_app(project):
|
||||
app = Flask(__name__)
|
||||
app.register_blueprint(codereview)
|
||||
app.config.project = project
|
||||
return app
|
||||
@@ -1,26 +0,0 @@
|
||||
h3, h4 {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap; /* Since CSS 2.1 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
background-color: #ffffff;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.modal-xl {
|
||||
width: 90%;
|
||||
max-width:1200px;
|
||||
}
|
||||
}
|
||||
|
||||
.affix {
|
||||
top:50px;
|
||||
right:0;
|
||||
position:fixed;
|
||||
}
|
||||
@@ -1,333 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var ccounter = 0;
|
||||
var counter = 0;
|
||||
var current_item = null;
|
||||
|
||||
// make item list sortable
|
||||
$( function() {
|
||||
$("#item_list").sortable();
|
||||
$("#item_list").disableSelection();
|
||||
});
|
||||
|
||||
function moveToTop(index) {
|
||||
if (index == 0) {
|
||||
return;
|
||||
}
|
||||
let target = $('#item_list').children().eq(index);
|
||||
let tp = $('#item_list').children().eq(0);
|
||||
let old_offset = target.position();
|
||||
tp.before(target);
|
||||
let new_offset = target.position();
|
||||
let tmp = target.clone().appendTo('#item_list')
|
||||
.css('position', 'absolute')
|
||||
.css('left', old_offset.left)
|
||||
.css('top', old_offset.top);
|
||||
target.hide();
|
||||
let new_pos = {'top': new_offset.top, 'left': new_offset.left};
|
||||
tmp.animate(new_pos, 'slow', function() {
|
||||
target.show();
|
||||
tmp.remove();
|
||||
});
|
||||
}
|
||||
|
||||
function getSelText() {
|
||||
let txt = window.getSelection();
|
||||
$('#selected_text').val(txt);
|
||||
$('#code_file_path').val($('#browsing_file_path').text());
|
||||
return txt;
|
||||
}
|
||||
|
||||
function taskHtml(text, cnt) {
|
||||
return '<li><span class="display" id="dep' + cnt + '">' + text +
|
||||
'</span>' + '<input type="text" class="edit" style="display:none"/>' +
|
||||
'<input type="submit" class="delete" value="X">' +'</li>';
|
||||
}
|
||||
|
||||
function codeHtml(text, cnt, okay) {
|
||||
return (okay? '<li>' : '<li style="color:red;">') +
|
||||
'<span id="code' + cnt + '">' + text +
|
||||
'</span><input type="submit" class="delete" value="X">' + '</li>';
|
||||
}
|
||||
|
||||
function itemHtml(done, text) {
|
||||
let atag = document.createElement('a');
|
||||
atag.innerText = text;
|
||||
if (done) {
|
||||
atag.className = 'list-group-item list-group-item-success';
|
||||
} else {
|
||||
atag.className = 'list-group-item list-group-item-danger';
|
||||
}
|
||||
let pretag = document.createElement('pre');
|
||||
pretag.appendChild(atag);
|
||||
pretag.onclick = setItem;
|
||||
return pretag;
|
||||
}
|
||||
|
||||
function grepResultHtml(items) {
|
||||
let ret = document.createElement('p');
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let path = document.createElement('span');
|
||||
path.style.color = 'purple';
|
||||
path.style.fontSize = '20px';
|
||||
path.innerHTML = items[i][0];
|
||||
ret.appendChild(path);
|
||||
ret.appendChild(document.createElement('br'));
|
||||
for (let j = 0; j < items[0][1].length; j++) {
|
||||
let line_no = items[i][1][j][0];
|
||||
let content = items[i][1][j][1];
|
||||
let line_html = document.createElement('font');
|
||||
line_html.style.color = 'green';
|
||||
line_html.style.fontSize = '18px';
|
||||
line_html.innerHTML = line_no + ':';
|
||||
ret.appendChild(line_html);
|
||||
let content_html = document.createElement('span');
|
||||
content_html.style.fontSize = '18px';
|
||||
content_html.appendChild(document.createTextNode(content));
|
||||
ret.appendChild(content_html);
|
||||
ret.appendChild(document.createElement('br'));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function enterTask() {
|
||||
let text = $('#enter_deps').val();
|
||||
$('#deps_list').append(taskHtml(text, counter));
|
||||
$('.delete').click(function () {
|
||||
$(this).parent().remove();
|
||||
});
|
||||
counter++;
|
||||
return false;
|
||||
}
|
||||
|
||||
function setTask(deps) {
|
||||
$('#deps_list').empty();
|
||||
counter = 0;
|
||||
let len = deps.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
let text = deps[i];
|
||||
$('#deps_list').append(taskHtml(text, counter));
|
||||
$('.delete').click(function () {
|
||||
$(this).parent().remove();
|
||||
});
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
function enterCode() {
|
||||
let text = $('#code_file_path').val() + ':' + $('#selected_text').val();
|
||||
$('#code_list').append(codeHtml(text, ccounter, true));
|
||||
$('.delete').click(function () {
|
||||
$(this).parent().remove();
|
||||
});
|
||||
ccounter++;
|
||||
return false;
|
||||
}
|
||||
|
||||
function setCode(codes, okays) {
|
||||
$('#code_list').empty();
|
||||
ccounter = 0;
|
||||
let len = codes.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
let text = codes[i];
|
||||
$('#code_list').append(codeHtml(text, ccounter, okays[i]));
|
||||
$('.delete').click(function () {
|
||||
$(this).parent().remove();
|
||||
});
|
||||
ccounter++;
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(onLoad);
|
||||
|
||||
function onLoad() {
|
||||
$.getJSON('/get_started', {
|
||||
}, function (data) {
|
||||
$('#item_list').empty();
|
||||
$('#pattern_list').empty();
|
||||
|
||||
const lst = JSON.parse(data.lst);
|
||||
const done = JSON.parse(data.done);
|
||||
const pattern_lst = JSON.parse(data.pattern_lst);
|
||||
let len = done.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
$('#item_list').append(itemHtml(done[i], lst[i]));
|
||||
}
|
||||
len = pattern_lst.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
$('#pattern_list').append('<li>' + pattern_lst[i] + '</li>');
|
||||
}
|
||||
$('#path_prefix').text(data.path_prefix);
|
||||
});
|
||||
}
|
||||
|
||||
function saveAll() {
|
||||
let path = $('#file_path').text();
|
||||
let line_no = $('#line_no').text();
|
||||
|
||||
let deps = new Array();
|
||||
for (let i = 0; i < counter; i++) {
|
||||
if ($('#dep' + i).length) {
|
||||
deps.push($('#dep' + i).text());
|
||||
}
|
||||
}
|
||||
let codes = new Array();
|
||||
for (let i = 0; i < ccounter; i++) {
|
||||
if ($('#code' + i).length) {
|
||||
codes.push($('#code' + i).text());
|
||||
}
|
||||
}
|
||||
|
||||
if (path == '' || line_no == '') {
|
||||
return false;
|
||||
}
|
||||
if (deps.length > 0) {
|
||||
current_item.className = 'list-group-item list-group-item-success';
|
||||
} else {
|
||||
current_item.className = 'list-group-item list-group-item-danger';
|
||||
}
|
||||
$.getJSON('/save_all', {
|
||||
label: $(current_item).text(),
|
||||
deps: JSON.stringify(deps),
|
||||
codes: JSON.stringify(codes)
|
||||
});
|
||||
let target = $(current_item).text().split(':')[2];
|
||||
let children = $('#item_list')[0].children;
|
||||
let len = children.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
let tt = children[i].getElementsByTagName('a')[0].innerHTML;
|
||||
if (tt == $(current_item).text()) {
|
||||
continue;
|
||||
}
|
||||
if (children[i].getElementsByTagName('a')[0].className ==
|
||||
'list-group-item list-group-item-success' ) {
|
||||
continue;
|
||||
}
|
||||
let content = tt.split(':')[2];
|
||||
if (content == target) {
|
||||
moveToTop(i);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function setBrowsingFile(path) {
|
||||
$('#browsing_file_path').text(path);
|
||||
$.getJSON('/get_file', {
|
||||
path: path
|
||||
}, function (data) {
|
||||
$('#browsing_file').children().first().text(data.result);
|
||||
let obj = $('#browsing_file').children().first();
|
||||
Prism.highlightElement($('#code')[0]);
|
||||
});
|
||||
}
|
||||
|
||||
function setHighlightLine(line_no) {
|
||||
$('#browsing_file').attr('data-line', line_no);
|
||||
}
|
||||
|
||||
function setGotoPatternLine(line_no) {
|
||||
$('#goto_pattern_line').attr('href', '#browsing_file.' + line_no);
|
||||
}
|
||||
|
||||
function unsetHighlightLine() {
|
||||
$('#browsing_file').removeAttr('data-line');
|
||||
// Add this line to ensure that all highlight divs are removed
|
||||
$('.line-highlight').remove();
|
||||
}
|
||||
|
||||
function removeAnchor() {
|
||||
// Remove the # from the hash,
|
||||
// as different browsers may or may not include it
|
||||
var hash = location.hash.replace('#','');
|
||||
if (hash != '') {
|
||||
// Clear the hash in the URL
|
||||
location.hash = '';
|
||||
}
|
||||
};
|
||||
|
||||
function setItem(evt) {
|
||||
removeAnchor();
|
||||
let item = evt.target;
|
||||
current_item = item;
|
||||
let name = $(item).text().split(':');
|
||||
let file = name[0];
|
||||
let line_no = name[1];
|
||||
$('#file_path').text(file);
|
||||
$('#line_no').text(line_no);
|
||||
|
||||
$.getJSON('/load_file', {
|
||||
path: $(item).text()
|
||||
}, function (data) {
|
||||
let deps = JSON.parse(data.deps);
|
||||
let codes = JSON.parse(data.codes);
|
||||
let okays = JSON.parse(data.okays);
|
||||
setTask(deps);
|
||||
setCode(codes, okays);
|
||||
});
|
||||
|
||||
setBrowsingFile(file);
|
||||
setHighlightLine(line_no);
|
||||
setGotoPatternLine(line_no);
|
||||
$('#selected_text').val('');
|
||||
$('#code_file_path').val('');
|
||||
$('#enter_deps').val('');
|
||||
$('html,body').scrollTop(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
$('#go_form').submit(function () {
|
||||
// get all the inputs into an array.
|
||||
const $inputs = $('#go_form :input');
|
||||
let values = {};
|
||||
$inputs.each(function () {
|
||||
values[this.name] = $(this).val();
|
||||
});
|
||||
let path = $('input[name="browsing_path"]').val();
|
||||
setBrowsingFile(path);
|
||||
unsetHighlightLine();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#add_pattern').submit(function () {
|
||||
const $inputs = $('#add_pattern :input');
|
||||
let values = {};
|
||||
$inputs.each(function () {
|
||||
values[this.name] = $(this).val();
|
||||
});
|
||||
$.getJSON('/add_pattern', {
|
||||
pattern: values['pattern'],
|
||||
is_regex: $('#is_regex').is(':checked') ? 1 : 0
|
||||
});
|
||||
return true;
|
||||
});
|
||||
|
||||
$('#temporary_search').submit(function() {
|
||||
const $inputs = $('#temporary_search :input');
|
||||
let values = {};
|
||||
$inputs.each(function () {
|
||||
values[this.name] = $(this).val();
|
||||
});
|
||||
$('#modal_title').text(values['pattern']);
|
||||
$.getJSON('/temporary_search', {
|
||||
path: $('#file_path').text(),
|
||||
pattern: values['pattern'],
|
||||
is_regex: $('#is_regex2').is(':checked') ? 1 : 0
|
||||
}, function (data) {
|
||||
$('#modal_body').append(grepResultHtml(JSON.parse(data.result)));
|
||||
$('#myModal').modal('show');
|
||||
});
|
||||
return false;
|
||||
});
|
||||
// clear previous html code in modal on hide
|
||||
$('#myModal').on('hidden.bs.modal', function () {
|
||||
$('#modal_body').empty();
|
||||
})
|
||||
|
||||
$('#add_deps').submit(enterTask);
|
||||
$('#add_code').submit(enterCode);
|
||||
$('#save_all').submit(saveAll);
|
||||
$('#get_selection').click(getSelText);
|
||||
})();
|
||||
@@ -1,308 +0,0 @@
|
||||
/* http://prismjs.com/download.html?themes=prism-coy&languages=clike+c&plugins=line-highlight+line-numbers */
|
||||
/**
|
||||
* prism.js Coy theme for JavaScript, CoffeeScript, CSS and HTML
|
||||
* Based on https://github.com/tshedor/workshop-wp-theme (Example: http://workshop.kansan.com/category/sessions/basics or http://workshop.timshedor.com/category/sessions/basics);
|
||||
* @author Tim Shedor
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: black;
|
||||
background: none;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
position: relative;
|
||||
margin: .5em 0;
|
||||
box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf;
|
||||
border-left: 10px solid #358ccb;
|
||||
background-color: #fdfdfd;
|
||||
background-image: linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%);
|
||||
background-size: 3em 3em;
|
||||
background-origin: content-box;
|
||||
overflow: visible;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
code[class*="language"] {
|
||||
max-height: inherit;
|
||||
height: 100%;
|
||||
padding: 0 1em;
|
||||
display: block;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* Margin bottom to accomodate shadow */
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background-color: #fdfdfd;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
position: relative;
|
||||
padding: .2em;
|
||||
border-radius: 0.3em;
|
||||
color: #c92c2c;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
display: inline;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
pre[class*="language-"]:before,
|
||||
pre[class*="language-"]:after {
|
||||
content: '';
|
||||
z-index: -2;
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0.75em;
|
||||
left: 0.18em;
|
||||
width: 40%;
|
||||
height: 20%;
|
||||
max-height: 13em;
|
||||
box-shadow: 0px 13px 8px #979797;
|
||||
-webkit-transform: rotate(-2deg);
|
||||
-moz-transform: rotate(-2deg);
|
||||
-ms-transform: rotate(-2deg);
|
||||
-o-transform: rotate(-2deg);
|
||||
transform: rotate(-2deg);
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"]:after,
|
||||
pre[class*="language-"]:after {
|
||||
right: 0.75em;
|
||||
left: auto;
|
||||
-webkit-transform: rotate(2deg);
|
||||
-moz-transform: rotate(2deg);
|
||||
-ms-transform: rotate(2deg);
|
||||
-o-transform: rotate(2deg);
|
||||
transform: rotate(2deg);
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.block-comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: #7D8B99;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #5F6364;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.function-name,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #c92c2c;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.function,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #2f9c0a;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.token.variable {
|
||||
color: #a67f59;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword,
|
||||
.token.class-name {
|
||||
color: #1990b8;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #a67f59;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.token.important {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
pre[class*="language-"]:before,
|
||||
pre[class*="language-"]:after {
|
||||
bottom: 14px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Plugin styles */
|
||||
.token.tab:not(:empty):before,
|
||||
.token.cr:before,
|
||||
.token.lf:before {
|
||||
color: #e0d7d1;
|
||||
}
|
||||
|
||||
/* Plugin styles: Line Numbers */
|
||||
pre[class*="language-"].line-numbers {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
pre[class*="language-"].line-numbers code {
|
||||
padding-left: 3.8em;
|
||||
}
|
||||
|
||||
pre[class*="language-"].line-numbers .line-numbers-rows {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/* Plugin styles: Line Highlight */
|
||||
pre[class*="language-"][data-line] {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
pre[data-line] code {
|
||||
position: relative;
|
||||
padding-left: 4em;
|
||||
}
|
||||
pre .line-highlight {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
pre[data-line] {
|
||||
position: relative;
|
||||
padding: 1em 0 1em 3em;
|
||||
}
|
||||
|
||||
.line-highlight {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: inherit 0;
|
||||
margin-top: 1em; /* Same as .prism’s padding-top */
|
||||
|
||||
background: hsla(24, 20%, 50%,.08);
|
||||
background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
line-height: inherit;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.line-highlight:before,
|
||||
.line-highlight[data-end]:after {
|
||||
content: attr(data-start);
|
||||
position: absolute;
|
||||
top: .4em;
|
||||
left: .6em;
|
||||
min-width: 1em;
|
||||
padding: 0 .5em;
|
||||
background-color: hsla(24, 20%, 50%,.4);
|
||||
color: hsl(24, 20%, 95%);
|
||||
font: bold 65%/1.5 sans-serif;
|
||||
text-align: center;
|
||||
vertical-align: .3em;
|
||||
border-radius: 999px;
|
||||
text-shadow: none;
|
||||
box-shadow: 0 1px white;
|
||||
}
|
||||
|
||||
.line-highlight[data-end]:after {
|
||||
content: attr(data-end);
|
||||
top: auto;
|
||||
bottom: .4em;
|
||||
}
|
||||
|
||||
pre.line-numbers {
|
||||
position: relative;
|
||||
padding-left: 3.8em;
|
||||
counter-reset: linenumber;
|
||||
}
|
||||
|
||||
pre.line-numbers > code {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.line-numbers .line-numbers-rows {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
font-size: 100%;
|
||||
left: -3.8em;
|
||||
width: 3em; /* works for line-numbers below 1000 lines */
|
||||
letter-spacing: -1px;
|
||||
border-right: 1px solid #999;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
}
|
||||
|
||||
.line-numbers-rows > span {
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
counter-increment: linenumber;
|
||||
}
|
||||
|
||||
.line-numbers-rows > span:before {
|
||||
content: counter(linenumber);
|
||||
color: #999;
|
||||
display: block;
|
||||
padding-right: 0.8em;
|
||||
text-align: right;
|
||||
}
|
||||
@@ -1,824 +0,0 @@
|
||||
/* http://prismjs.com/download.html?themes=prism-coy&languages=clike+c&plugins=line-highlight+line-numbers */
|
||||
var _self = (typeof window !== 'undefined')
|
||||
? window // if in browser
|
||||
: (
|
||||
(typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
|
||||
? self // if in worker
|
||||
: {} // if in node js
|
||||
);
|
||||
|
||||
/**
|
||||
* Prism: Lightweight, robust, elegant syntax highlighting
|
||||
* MIT license http://www.opensource.org/licenses/mit-license.php/
|
||||
* @author Lea Verou http://lea.verou.me
|
||||
*/
|
||||
|
||||
var Prism = (function(){
|
||||
|
||||
// Private helper vars
|
||||
var lang = /\blang(?:uage)?-(\w+)\b/i;
|
||||
var uniqueId = 0;
|
||||
|
||||
var _ = _self.Prism = {
|
||||
manual: _self.Prism && _self.Prism.manual,
|
||||
util: {
|
||||
encode: function (tokens) {
|
||||
if (tokens instanceof Token) {
|
||||
return new Token(tokens.type, _.util.encode(tokens.content), tokens.alias);
|
||||
} else if (_.util.type(tokens) === 'Array') {
|
||||
return tokens.map(_.util.encode);
|
||||
} else {
|
||||
return tokens.replace(/&/g, '&').replace(/</g, '<').replace(/\u00a0/g, ' ');
|
||||
}
|
||||
},
|
||||
|
||||
type: function (o) {
|
||||
return Object.prototype.toString.call(o).match(/\[object (\w+)\]/)[1];
|
||||
},
|
||||
|
||||
objId: function (obj) {
|
||||
if (!obj['__id']) {
|
||||
Object.defineProperty(obj, '__id', { value: ++uniqueId });
|
||||
}
|
||||
return obj['__id'];
|
||||
},
|
||||
|
||||
// Deep clone a language definition (e.g. to extend it)
|
||||
clone: function (o) {
|
||||
var type = _.util.type(o);
|
||||
|
||||
switch (type) {
|
||||
case 'Object':
|
||||
var clone = {};
|
||||
|
||||
for (var key in o) {
|
||||
if (o.hasOwnProperty(key)) {
|
||||
clone[key] = _.util.clone(o[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return clone;
|
||||
|
||||
case 'Array':
|
||||
// Check for existence for IE8
|
||||
return o.map && o.map(function(v) { return _.util.clone(v); });
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
},
|
||||
|
||||
languages: {
|
||||
extend: function (id, redef) {
|
||||
var lang = _.util.clone(_.languages[id]);
|
||||
|
||||
for (var key in redef) {
|
||||
lang[key] = redef[key];
|
||||
}
|
||||
|
||||
return lang;
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert a token before another token in a language literal
|
||||
* As this needs to recreate the object (we cannot actually insert before keys in object literals),
|
||||
* we cannot just provide an object, we need anobject and a key.
|
||||
* @param inside The key (or language id) of the parent
|
||||
* @param before The key to insert before. If not provided, the function appends instead.
|
||||
* @param insert Object with the key/value pairs to insert
|
||||
* @param root The object that contains `inside`. If equal to Prism.languages, it can be omitted.
|
||||
*/
|
||||
insertBefore: function (inside, before, insert, root) {
|
||||
root = root || _.languages;
|
||||
var grammar = root[inside];
|
||||
|
||||
if (arguments.length == 2) {
|
||||
insert = arguments[1];
|
||||
|
||||
for (var newToken in insert) {
|
||||
if (insert.hasOwnProperty(newToken)) {
|
||||
grammar[newToken] = insert[newToken];
|
||||
}
|
||||
}
|
||||
|
||||
return grammar;
|
||||
}
|
||||
|
||||
var ret = {};
|
||||
|
||||
for (var token in grammar) {
|
||||
|
||||
if (grammar.hasOwnProperty(token)) {
|
||||
|
||||
if (token == before) {
|
||||
|
||||
for (var newToken in insert) {
|
||||
|
||||
if (insert.hasOwnProperty(newToken)) {
|
||||
ret[newToken] = insert[newToken];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret[token] = grammar[token];
|
||||
}
|
||||
}
|
||||
|
||||
// Update references in other language definitions
|
||||
_.languages.DFS(_.languages, function(key, value) {
|
||||
if (value === root[inside] && key != inside) {
|
||||
this[key] = ret;
|
||||
}
|
||||
});
|
||||
|
||||
return root[inside] = ret;
|
||||
},
|
||||
|
||||
// Traverse a language definition with Depth First Search
|
||||
DFS: function(o, callback, type, visited) {
|
||||
visited = visited || {};
|
||||
for (var i in o) {
|
||||
if (o.hasOwnProperty(i)) {
|
||||
callback.call(o, i, o[i], type || i);
|
||||
|
||||
if (_.util.type(o[i]) === 'Object' && !visited[_.util.objId(o[i])]) {
|
||||
visited[_.util.objId(o[i])] = true;
|
||||
_.languages.DFS(o[i], callback, null, visited);
|
||||
}
|
||||
else if (_.util.type(o[i]) === 'Array' && !visited[_.util.objId(o[i])]) {
|
||||
visited[_.util.objId(o[i])] = true;
|
||||
_.languages.DFS(o[i], callback, i, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {},
|
||||
|
||||
highlightAll: function(async, callback) {
|
||||
var env = {
|
||||
callback: callback,
|
||||
selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'
|
||||
};
|
||||
|
||||
_.hooks.run("before-highlightall", env);
|
||||
|
||||
var elements = env.elements || document.querySelectorAll(env.selector);
|
||||
|
||||
for (var i=0, element; element = elements[i++];) {
|
||||
_.highlightElement(element, async === true, env.callback);
|
||||
}
|
||||
},
|
||||
|
||||
highlightElement: function(element, async, callback) {
|
||||
// Find language
|
||||
var language, grammar, parent = element;
|
||||
|
||||
while (parent && !lang.test(parent.className)) {
|
||||
parent = parent.parentNode;
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
language = (parent.className.match(lang) || [,''])[1].toLowerCase();
|
||||
grammar = _.languages[language];
|
||||
}
|
||||
|
||||
// Set language on the element, if not present
|
||||
element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
|
||||
|
||||
// Set language on the parent, for styling
|
||||
parent = element.parentNode;
|
||||
|
||||
if (/pre/i.test(parent.nodeName)) {
|
||||
parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
|
||||
}
|
||||
|
||||
var code = element.textContent;
|
||||
|
||||
var env = {
|
||||
element: element,
|
||||
language: language,
|
||||
grammar: grammar,
|
||||
code: code
|
||||
};
|
||||
|
||||
_.hooks.run('before-sanity-check', env);
|
||||
|
||||
if (!env.code || !env.grammar) {
|
||||
if (env.code) {
|
||||
_.hooks.run('before-highlight', env);
|
||||
env.element.textContent = env.code;
|
||||
_.hooks.run('after-highlight', env);
|
||||
}
|
||||
_.hooks.run('complete', env);
|
||||
return;
|
||||
}
|
||||
|
||||
_.hooks.run('before-highlight', env);
|
||||
|
||||
if (async && _self.Worker) {
|
||||
var worker = new Worker(_.filename);
|
||||
|
||||
worker.onmessage = function(evt) {
|
||||
env.highlightedCode = evt.data;
|
||||
|
||||
_.hooks.run('before-insert', env);
|
||||
|
||||
env.element.innerHTML = env.highlightedCode;
|
||||
|
||||
callback && callback.call(env.element);
|
||||
_.hooks.run('after-highlight', env);
|
||||
_.hooks.run('complete', env);
|
||||
};
|
||||
|
||||
worker.postMessage(JSON.stringify({
|
||||
language: env.language,
|
||||
code: env.code,
|
||||
immediateClose: true
|
||||
}));
|
||||
}
|
||||
else {
|
||||
env.highlightedCode = _.highlight(env.code, env.grammar, env.language);
|
||||
|
||||
_.hooks.run('before-insert', env);
|
||||
|
||||
env.element.innerHTML = env.highlightedCode;
|
||||
|
||||
callback && callback.call(element);
|
||||
|
||||
_.hooks.run('after-highlight', env);
|
||||
_.hooks.run('complete', env);
|
||||
}
|
||||
},
|
||||
|
||||
highlight: function (text, grammar, language) {
|
||||
var tokens = _.tokenize(text, grammar);
|
||||
return Token.stringify(_.util.encode(tokens), language);
|
||||
},
|
||||
|
||||
matchGrammar: function (text, strarr, grammar, index, startPos, oneshot, target) {
|
||||
var Token = _.Token;
|
||||
|
||||
for (var token in grammar) {
|
||||
if(!grammar.hasOwnProperty(token) || !grammar[token]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == target) {
|
||||
return;
|
||||
}
|
||||
|
||||
var patterns = grammar[token];
|
||||
patterns = (_.util.type(patterns) === "Array") ? patterns : [patterns];
|
||||
|
||||
for (var j = 0; j < patterns.length; ++j) {
|
||||
var pattern = patterns[j],
|
||||
inside = pattern.inside,
|
||||
lookbehind = !!pattern.lookbehind,
|
||||
greedy = !!pattern.greedy,
|
||||
lookbehindLength = 0,
|
||||
alias = pattern.alias;
|
||||
|
||||
if (greedy && !pattern.pattern.global) {
|
||||
// Without the global flag, lastIndex won't work
|
||||
var flags = pattern.pattern.toString().match(/[imuy]*$/)[0];
|
||||
pattern.pattern = RegExp(pattern.pattern.source, flags + "g");
|
||||
}
|
||||
|
||||
pattern = pattern.pattern || pattern;
|
||||
|
||||
// Don’t cache length as it changes during the loop
|
||||
for (var i = index, pos = startPos; i < strarr.length; pos += strarr[i].length, ++i) {
|
||||
|
||||
var str = strarr[i];
|
||||
|
||||
if (strarr.length > text.length) {
|
||||
// Something went terribly wrong, ABORT, ABORT!
|
||||
return;
|
||||
}
|
||||
|
||||
if (str instanceof Token) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pattern.lastIndex = 0;
|
||||
|
||||
var match = pattern.exec(str),
|
||||
delNum = 1;
|
||||
|
||||
// Greedy patterns can override/remove up to two previously matched tokens
|
||||
if (!match && greedy && i != strarr.length - 1) {
|
||||
pattern.lastIndex = pos;
|
||||
match = pattern.exec(text);
|
||||
if (!match) {
|
||||
break;
|
||||
}
|
||||
|
||||
var from = match.index + (lookbehind ? match[1].length : 0),
|
||||
to = match.index + match[0].length,
|
||||
k = i,
|
||||
p = pos;
|
||||
|
||||
for (var len = strarr.length; k < len && (p < to || (!strarr[k].type && !strarr[k - 1].greedy)); ++k) {
|
||||
p += strarr[k].length;
|
||||
// Move the index i to the element in strarr that is closest to from
|
||||
if (from >= p) {
|
||||
++i;
|
||||
pos = p;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If strarr[i] is a Token, then the match starts inside another Token, which is invalid
|
||||
* If strarr[k - 1] is greedy we are in conflict with another greedy pattern
|
||||
*/
|
||||
if (strarr[i] instanceof Token || strarr[k - 1].greedy) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Number of tokens to delete and replace with the new match
|
||||
delNum = k - i;
|
||||
str = text.slice(pos, p);
|
||||
match.index -= pos;
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
if (oneshot) {
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(lookbehind) {
|
||||
lookbehindLength = match[1].length;
|
||||
}
|
||||
|
||||
var from = match.index + lookbehindLength,
|
||||
match = match[0].slice(lookbehindLength),
|
||||
to = from + match.length,
|
||||
before = str.slice(0, from),
|
||||
after = str.slice(to);
|
||||
|
||||
var args = [i, delNum];
|
||||
|
||||
if (before) {
|
||||
++i;
|
||||
pos += before.length;
|
||||
args.push(before);
|
||||
}
|
||||
|
||||
var wrapped = new Token(token, inside? _.tokenize(match, inside) : match, alias, match, greedy);
|
||||
|
||||
args.push(wrapped);
|
||||
|
||||
if (after) {
|
||||
args.push(after);
|
||||
}
|
||||
|
||||
Array.prototype.splice.apply(strarr, args);
|
||||
|
||||
if (delNum != 1)
|
||||
_.matchGrammar(text, strarr, grammar, i, pos, true, token);
|
||||
|
||||
if (oneshot)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
tokenize: function(text, grammar, language) {
|
||||
var strarr = [text];
|
||||
|
||||
var rest = grammar.rest;
|
||||
|
||||
if (rest) {
|
||||
for (var token in rest) {
|
||||
grammar[token] = rest[token];
|
||||
}
|
||||
|
||||
delete grammar.rest;
|
||||
}
|
||||
|
||||
_.matchGrammar(text, strarr, grammar, 0, 0, false);
|
||||
|
||||
return strarr;
|
||||
},
|
||||
|
||||
hooks: {
|
||||
all: {},
|
||||
|
||||
add: function (name, callback) {
|
||||
var hooks = _.hooks.all;
|
||||
|
||||
hooks[name] = hooks[name] || [];
|
||||
|
||||
hooks[name].push(callback);
|
||||
},
|
||||
|
||||
run: function (name, env) {
|
||||
var callbacks = _.hooks.all[name];
|
||||
|
||||
if (!callbacks || !callbacks.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i=0, callback; callback = callbacks[i++];) {
|
||||
callback(env);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var Token = _.Token = function(type, content, alias, matchedStr, greedy) {
|
||||
this.type = type;
|
||||
this.content = content;
|
||||
this.alias = alias;
|
||||
// Copy of the full string this token was created from
|
||||
this.length = (matchedStr || "").length|0;
|
||||
this.greedy = !!greedy;
|
||||
};
|
||||
|
||||
Token.stringify = function(o, language, parent) {
|
||||
if (typeof o == 'string') {
|
||||
return o;
|
||||
}
|
||||
|
||||
if (_.util.type(o) === 'Array') {
|
||||
return o.map(function(element) {
|
||||
return Token.stringify(element, language, o);
|
||||
}).join('');
|
||||
}
|
||||
|
||||
var env = {
|
||||
type: o.type,
|
||||
content: Token.stringify(o.content, language, parent),
|
||||
tag: 'span',
|
||||
classes: ['token', o.type],
|
||||
attributes: {},
|
||||
language: language,
|
||||
parent: parent
|
||||
};
|
||||
|
||||
if (env.type == 'comment') {
|
||||
env.attributes['spellcheck'] = 'true';
|
||||
}
|
||||
|
||||
if (o.alias) {
|
||||
var aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias];
|
||||
Array.prototype.push.apply(env.classes, aliases);
|
||||
}
|
||||
|
||||
_.hooks.run('wrap', env);
|
||||
|
||||
var attributes = Object.keys(env.attributes).map(function(name) {
|
||||
return name + '="' + (env.attributes[name] || '').replace(/"/g, '"') + '"';
|
||||
}).join(' ');
|
||||
|
||||
return '<' + env.tag + ' class="' + env.classes.join(' ') + '"' + (attributes ? ' ' + attributes : '') + '>' + env.content + '</' + env.tag + '>';
|
||||
|
||||
};
|
||||
|
||||
if (!_self.document) {
|
||||
if (!_self.addEventListener) {
|
||||
// in Node.js
|
||||
return _self.Prism;
|
||||
}
|
||||
// In worker
|
||||
_self.addEventListener('message', function(evt) {
|
||||
var message = JSON.parse(evt.data),
|
||||
lang = message.language,
|
||||
code = message.code,
|
||||
immediateClose = message.immediateClose;
|
||||
|
||||
_self.postMessage(_.highlight(code, _.languages[lang], lang));
|
||||
if (immediateClose) {
|
||||
_self.close();
|
||||
}
|
||||
}, false);
|
||||
|
||||
return _self.Prism;
|
||||
}
|
||||
|
||||
//Get current script and highlight
|
||||
var script = document.currentScript || [].slice.call(document.getElementsByTagName("script")).pop();
|
||||
|
||||
if (script) {
|
||||
_.filename = script.src;
|
||||
|
||||
if (document.addEventListener && !_.manual && !script.hasAttribute('data-manual')) {
|
||||
if(document.readyState !== "loading") {
|
||||
if (window.requestAnimationFrame) {
|
||||
window.requestAnimationFrame(_.highlightAll);
|
||||
} else {
|
||||
window.setTimeout(_.highlightAll, 16);
|
||||
}
|
||||
}
|
||||
else {
|
||||
document.addEventListener('DOMContentLoaded', _.highlightAll);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _self.Prism;
|
||||
|
||||
})();
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = Prism;
|
||||
}
|
||||
|
||||
// hack for components to work correctly in node.js
|
||||
if (typeof global !== 'undefined') {
|
||||
global.Prism = Prism;
|
||||
}
|
||||
;
|
||||
Prism.languages.clike = {
|
||||
'comment': [
|
||||
{
|
||||
pattern: /(^|[^\\])\/\*[\s\S]*?\*\//,
|
||||
lookbehind: true
|
||||
},
|
||||
{
|
||||
pattern: /(^|[^\\:])\/\/.*/,
|
||||
lookbehind: true
|
||||
}
|
||||
],
|
||||
'string': {
|
||||
pattern: /(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
|
||||
greedy: true
|
||||
},
|
||||
'class-name': {
|
||||
pattern: /((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,
|
||||
lookbehind: true,
|
||||
inside: {
|
||||
punctuation: /(\.|\\)/
|
||||
}
|
||||
},
|
||||
'keyword': /\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,
|
||||
'boolean': /\b(true|false)\b/,
|
||||
'function': /[a-z0-9_]+(?=\()/i,
|
||||
'number': /\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,
|
||||
'operator': /--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,
|
||||
'punctuation': /[{}[\];(),.:]/
|
||||
};
|
||||
|
||||
Prism.languages.c = Prism.languages.extend('clike', {
|
||||
'keyword': /\b(asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/,
|
||||
'operator': /\-[>-]?|\+\+?|!=?|<<?=?|>>?=?|==?|&&?|\|?\||[~^%?*\/]/,
|
||||
'number': /\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)[ful]*\b/i
|
||||
});
|
||||
|
||||
Prism.languages.insertBefore('c', 'string', {
|
||||
'macro': {
|
||||
// allow for multiline macro definitions
|
||||
// spaces after the # character compile fine with gcc
|
||||
pattern: /(^\s*)#\s*[a-z]+([^\r\n\\]|\\.|\\(?:\r\n?|\n))*/im,
|
||||
lookbehind: true,
|
||||
alias: 'property',
|
||||
inside: {
|
||||
// highlight the path of the include statement as a string
|
||||
'string': {
|
||||
pattern: /(#\s*include\s*)(<.+?>|("|')(\\?.)+?\3)/,
|
||||
lookbehind: true
|
||||
},
|
||||
// highlight macro directives as keywords
|
||||
'directive': {
|
||||
pattern: /(#\s*)\b(define|elif|else|endif|error|ifdef|ifndef|if|import|include|line|pragma|undef|using)\b/,
|
||||
lookbehind: true,
|
||||
alias: 'keyword'
|
||||
}
|
||||
}
|
||||
},
|
||||
// highlight predefined macros as constants
|
||||
'constant': /\b(__FILE__|__LINE__|__DATE__|__TIME__|__TIMESTAMP__|__func__|EOF|NULL|stdin|stdout|stderr)\b/
|
||||
});
|
||||
|
||||
delete Prism.languages.c['class-name'];
|
||||
delete Prism.languages.c['boolean'];
|
||||
|
||||
(function(){
|
||||
|
||||
if (typeof self === 'undefined' || !self.Prism || !self.document || !document.querySelector) {
|
||||
return;
|
||||
}
|
||||
|
||||
function $$(expr, con) {
|
||||
return Array.prototype.slice.call((con || document).querySelectorAll(expr));
|
||||
}
|
||||
|
||||
function hasClass(element, className) {
|
||||
className = " " + className + " ";
|
||||
return (" " + element.className + " ").replace(/[\n\t]/g, " ").indexOf(className) > -1
|
||||
}
|
||||
|
||||
// Some browsers round the line-height, others don't.
|
||||
// We need to test for it to position the elements properly.
|
||||
var isLineHeightRounded = (function() {
|
||||
var res;
|
||||
return function() {
|
||||
if(typeof res === 'undefined') {
|
||||
var d = document.createElement('div');
|
||||
d.style.fontSize = '13px';
|
||||
d.style.lineHeight = '1.5';
|
||||
d.style.padding = 0;
|
||||
d.style.border = 0;
|
||||
d.innerHTML = ' <br /> ';
|
||||
document.body.appendChild(d);
|
||||
// Browsers that round the line-height should have offsetHeight === 38
|
||||
// The others should have 39.
|
||||
res = d.offsetHeight === 38;
|
||||
document.body.removeChild(d);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}());
|
||||
|
||||
function getOffsetById(id) {
|
||||
var element = document.getElementById(id);
|
||||
var bodyRect = document.body.getBoundingClientRect();
|
||||
var elemRect = element.getBoundingClientRect();
|
||||
var elementOffset = elemRect.top - bodyRect.top;
|
||||
return elementOffset;
|
||||
}
|
||||
|
||||
function highlightLines(pre, lines, classes) {
|
||||
var ranges = lines.replace(/\s+/g, '').split(',');
|
||||
var offset = getOffsetById('browsing_file');
|
||||
|
||||
var parseMethod = isLineHeightRounded() ? parseInt : parseFloat;
|
||||
var lineHeight = parseMethod(getComputedStyle(pre).lineHeight);
|
||||
|
||||
for (var i=0, range; range = ranges[i++];) {
|
||||
range = range.split('-');
|
||||
|
||||
var start = +range[0],
|
||||
end = +range[1] || start;
|
||||
|
||||
var line = document.createElement('div');
|
||||
|
||||
line.textContent = Array(end - start + 2).join(' \n');
|
||||
line.setAttribute('aria-hidden', 'true');
|
||||
line.className = (classes || '') + ' line-highlight';
|
||||
|
||||
//if the line-numbers plugin is enabled, then there is no reason for this plugin to display the line numbers
|
||||
if(!hasClass(pre, 'line-numbers')) {
|
||||
line.setAttribute('data-start', start);
|
||||
|
||||
if(end > start) {
|
||||
line.setAttribute('data-end', end);
|
||||
}
|
||||
}
|
||||
|
||||
line.style.top = (getOffsetById('line_no' + start) - offset) + 'px';
|
||||
|
||||
//allow this to play nicely with the line-numbers plugin
|
||||
if(hasClass(pre, 'line-numbers')) {
|
||||
//need to attack to pre as when line-numbers is enabled, the code tag is relatively which screws up the positioning
|
||||
pre.appendChild(line);
|
||||
} else {
|
||||
(pre.querySelector('code') || pre).appendChild(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyHash() {
|
||||
var hash = location.hash.slice(1);
|
||||
|
||||
// Remove pre-existing temporary lines
|
||||
$$('.temporary.line-highlight').forEach(function (line) {
|
||||
line.parentNode.removeChild(line);
|
||||
});
|
||||
|
||||
var range = (hash.match(/\.([\d,-]+)$/) || [,''])[1];
|
||||
|
||||
if (!range || document.getElementById(hash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var id = hash.slice(0, hash.lastIndexOf('.')),
|
||||
pre = document.getElementById(id);
|
||||
|
||||
if (!pre) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pre.hasAttribute('data-line')) {
|
||||
pre.setAttribute('data-line', '');
|
||||
}
|
||||
|
||||
highlightLines(pre, range, 'temporary ');
|
||||
|
||||
document.querySelector('.temporary.line-highlight').scrollIntoView();
|
||||
}
|
||||
|
||||
var fakeTimer = 0; // Hack to limit the number of times applyHash() runs
|
||||
|
||||
Prism.hooks.add('before-sanity-check', function(env) {
|
||||
var pre = env.element.parentNode;
|
||||
var lines = pre && pre.getAttribute('data-line');
|
||||
|
||||
if (!pre || !lines || !/pre/i.test(pre.nodeName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Cleanup for other plugins (e.g. autoloader).
|
||||
*
|
||||
* Sometimes <code> blocks are highlighted multiple times. It is necessary
|
||||
* to cleanup any left-over tags, because the whitespace inside of the <div>
|
||||
* tags change the content of the <code> tag.
|
||||
*/
|
||||
var num = 0;
|
||||
$$('.line-highlight', pre).forEach(function (line) {
|
||||
num += line.textContent.length;
|
||||
line.parentNode.removeChild(line);
|
||||
});
|
||||
|
||||
// Remove extra whitespace
|
||||
if (num && /^( \n)+$/.test(env.code.slice(-num))) {
|
||||
env.code = env.code.slice(0, -num);
|
||||
}
|
||||
});
|
||||
|
||||
Prism.hooks.add('complete', function (env) {
|
||||
if (!env.code) {
|
||||
return;
|
||||
}
|
||||
|
||||
// works only for <code> wrapped inside <pre> (not inline)
|
||||
var pre = env.element.parentNode;
|
||||
var clsReg = /\s*\bline-numbers\b\s*/;
|
||||
if (
|
||||
!pre || !/pre/i.test(pre.nodeName) ||
|
||||
// Abort only if nor the <pre> nor the <code> have the class
|
||||
(!clsReg.test(pre.className) && !clsReg.test(env.element.className))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (env.element.querySelector(".line-numbers-rows")) {
|
||||
// Abort if line numbers already exists
|
||||
return;
|
||||
}
|
||||
|
||||
if (clsReg.test(env.element.className)) {
|
||||
// Remove the class "line-numbers" from the <code>
|
||||
env.element.className = env.element.className.replace(clsReg, '');
|
||||
}
|
||||
if (!clsReg.test(pre.className)) {
|
||||
// Add the class "line-numbers" to the <pre>
|
||||
pre.className += ' line-numbers';
|
||||
}
|
||||
|
||||
var match = env.code.match(/\n(?!$)/g);
|
||||
var linesNum = match ? match.length + 1 : 1;
|
||||
var lineNumbersWrapper;
|
||||
|
||||
var lines = '';
|
||||
for (let i = 1; i < linesNum + 1; i++) {
|
||||
lines += '<span id="line_no' + i + '"></span>';
|
||||
}
|
||||
|
||||
lineNumbersWrapper = document.createElement('span');
|
||||
lineNumbersWrapper.setAttribute('aria-hidden', 'true');
|
||||
lineNumbersWrapper.className = 'line-numbers-rows';
|
||||
lineNumbersWrapper.innerHTML = lines;
|
||||
|
||||
if (pre.hasAttribute('data-start')) {
|
||||
pre.style.counterReset = 'linenumber ' + (parseInt(pre.getAttribute('data-start'), 10) - 1);
|
||||
}
|
||||
|
||||
env.element.appendChild(lineNumbersWrapper);
|
||||
|
||||
});
|
||||
|
||||
Prism.hooks.add('complete', function(env) {
|
||||
var pre = env.element.parentNode;
|
||||
var lines = pre && pre.getAttribute('data-line');
|
||||
|
||||
if (!pre || !lines || !/pre/i.test(pre.nodeName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(fakeTimer);
|
||||
|
||||
highlightLines(pre, lines);
|
||||
|
||||
fakeTimer = setTimeout(applyHash, 1);
|
||||
});
|
||||
|
||||
if(window.addEventListener) {
|
||||
window.addEventListener('hashchange', applyHash);
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
(function() {
|
||||
|
||||
if (typeof self === 'undefined' || !self.Prism || !self.document) {
|
||||
return;
|
||||
}
|
||||
|
||||
}());
|
||||
@@ -1,111 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Source Deps Reviewer</title>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="static/prism/css/prism.css"/>
|
||||
<link rel="stylesheet" href="static/css/main.css"/>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||
<!-- Added for sortable list -->
|
||||
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row content">
|
||||
<h2 style="padding-left:20px;">Code review tool</h2>
|
||||
<ol id="item_list" class="col-sm-3"></ol>
|
||||
|
||||
<div class="col-sm-5">
|
||||
<h3>Browsing:</h3>
|
||||
<pre><h4 id="browsing_file_path"></h4></pre><br>
|
||||
<form id="go_form">
|
||||
<div class="input-group" style="margin-bottom:10px;">
|
||||
<span class="input-group-addon" id="path_prefix"></span>
|
||||
<input type="text" class="form-control" name="browsing_path" placeholder="Enter file path here" aria-describedby="path_prefix">
|
||||
</div>
|
||||
<input class="btn btn-primary" type="submit" name="go" value="GO"/>
|
||||
<a class="btn btn-link" id="goto_pattern_line">goto pattern line</a>
|
||||
<pre id="browsing_file" class="line-numbers"><code id="code" class="language-C" style="display:inline-block;"></code></pre>
|
||||
</form>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<div class="col-sm-4" data-spy="affix">
|
||||
|
||||
<div class="well">
|
||||
<h3>Temporary search</h3>
|
||||
<form id="temporary_search" class="input-group" style="padding-left:20px;">
|
||||
<span class="input-group-addon">is regex</span>
|
||||
<span class="input-group-addon">
|
||||
<input type="checkbox" name="is_regex2" id="is_regex2">
|
||||
</span>
|
||||
<input type="text" name="pattern" class="form-control">
|
||||
</form>
|
||||
<h3>Add patterns to grep</h3>
|
||||
<form id="add_pattern" class="input-group" style="padding-left:20px;">
|
||||
<span class="input-group-addon">is regex</span>
|
||||
<span class="input-group-addon">
|
||||
<input type="checkbox" name="is_regex" id="is_regex">
|
||||
</span>
|
||||
<input type="text" name="pattern" class="form-control">
|
||||
</form>
|
||||
<ul id="pattern_list"></ul>
|
||||
</div>
|
||||
<div class="well">
|
||||
<h3>File labeling:</h3>
|
||||
<pre style="padding-left:20px;"><h4 id="file_path"></h4></pre>
|
||||
<h3>Pattern line number:</h3>
|
||||
<h3 id="line_no"></h3><br>
|
||||
<h3>Library Dependencies</h3>
|
||||
<form id="add_deps" class="input-group">
|
||||
<input type="text" class="form-control" id="enter_deps" placeholder="Fill in * if undetermined"/>
|
||||
<span class="input-group-btn">
|
||||
<input class="btn btn-secondary" type="submit" value="Add"/>
|
||||
</span>
|
||||
</form>
|
||||
<ul id="deps_list"></ul>
|
||||
<h3>Code Dependencies</h3>
|
||||
<form id="add_code">
|
||||
<input class="btn btn-secondary" type="button" id="get_selection" value="Get selection"/>
|
||||
<input class="btn btn-secondary" type="submit" id="add_code" value="Add"/><br>
|
||||
<input type="text" id="code_file_path" style="margin: 0px; width: 100%;"/>
|
||||
<textarea id="selected_text" name="selectedtext" rows="5" style="margin: 0px; width: 100%; height: 106px;"></textarea>
|
||||
</form>
|
||||
<ul id="code_list"></ul>
|
||||
<form id="save_all">
|
||||
<input class="btn btn-secondary" type="submit" value="Save All"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="myModal" role="dialog">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 id="modal_title" class="modal-title"></h4>
|
||||
</div>
|
||||
<div id="modal_body" class="modal-body">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="static/js/main.js"></script>
|
||||
<!-- for code prettyify -->
|
||||
<script src="static/prism/js/prism.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from sourcedr.map import (
|
||||
link_build_dep_and_review_data, load_build_dep_file_from_path,
|
||||
load_build_dep_ninja, load_review_data)
|
||||
|
||||
|
||||
TESTDATA_DIR = os.path.join(os.path.dirname(__file__), 'testdata')
|
||||
|
||||
|
||||
class MapTest(unittest.TestCase):
|
||||
MAP_TESTDATA_DIR = os.path.join(TESTDATA_DIR, 'map')
|
||||
DEP_PATH = os.path.join(MAP_TESTDATA_DIR, 'build_dep.json')
|
||||
REVIEW_DB_PATH = os.path.join(MAP_TESTDATA_DIR, 'data.json')
|
||||
NINJA_PATH = os.path.join(MAP_TESTDATA_DIR, 'build.ninja')
|
||||
NINJA_DEP_PATH = os.path.join(MAP_TESTDATA_DIR, 'ninja_deps')
|
||||
|
||||
|
||||
def test_load_build_dep_file(self):
|
||||
dep = load_build_dep_file_from_path(self.DEP_PATH)
|
||||
|
||||
self.assertIn('liba.so', dep)
|
||||
self.assertIn('libb.so', dep)
|
||||
self.assertIn('libc.so', dep)
|
||||
|
||||
self.assertSetEqual({'a.h', 'a1.c', 'a1.o', 'a2.c', 'a2.o'},
|
||||
dep['liba.so'])
|
||||
self.assertSetEqual({'a.h', 'b.c', 'b.o'}, dep['libb.so'])
|
||||
self.assertSetEqual({'c.c', 'c.o'}, dep['libc.so'])
|
||||
|
||||
|
||||
def test_load_build_dep_ninja(self):
|
||||
dep = load_build_dep_ninja(self.NINJA_PATH, self.MAP_TESTDATA_DIR,
|
||||
self.NINJA_DEP_PATH)
|
||||
|
||||
self.assertIn('liba.so', dep)
|
||||
self.assertIn('libb.so', dep)
|
||||
self.assertIn('libc.so', dep)
|
||||
|
||||
self.assertSetEqual({'a.h', 'a1.c', 'a1.o', 'a2.c', 'a2.o'},
|
||||
dep['liba.so'])
|
||||
self.assertSetEqual({'a.h', 'b.c', 'b.o'}, dep['libb.so'])
|
||||
self.assertSetEqual({'c.c', 'c.o'}, dep['libc.so'])
|
||||
|
||||
|
||||
def test_load_review_data(self):
|
||||
data = load_review_data(self.REVIEW_DB_PATH)
|
||||
self.assertIn('a.h', data)
|
||||
self.assertEqual(['libx.so'], data['a.h'])
|
||||
|
||||
|
||||
def test_link_build_dep_and_review_data(self):
|
||||
dep = load_build_dep_file_from_path(self.DEP_PATH)
|
||||
data = load_review_data(self.REVIEW_DB_PATH)
|
||||
result = link_build_dep_and_review_data(dep, data)
|
||||
|
||||
self.assertIn('liba.so', result)
|
||||
self.assertIn('libb.so', result)
|
||||
self.assertIn('libc.so', result)
|
||||
|
||||
self.assertEqual(['libx.so'], result['liba.so'])
|
||||
self.assertEqual(['libx.so'], result['libb.so'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from sourcedr.project import Config
|
||||
|
||||
|
||||
TESTDATA_DIR = os.path.join(os.path.dirname(__file__), 'testdata')
|
||||
|
||||
|
||||
class ConfigTest(unittest.TestCase):
|
||||
PROJECT_DIR = os.path.join(TESTDATA_DIR, 'project')
|
||||
CONFIG_PATH = os.path.join(PROJECT_DIR, Config.DEFAULT_NAME)
|
||||
|
||||
|
||||
def test_load(self):
|
||||
config = Config(self.CONFIG_PATH)
|
||||
config.load()
|
||||
self.assertEqual('path/to/android/src', config.source_dir)
|
||||
|
||||
|
||||
def test_save(self):
|
||||
with tempfile.TemporaryDirectory(prefix='test_sourcedr_') as tmp_dir:
|
||||
config_path = Config.get_default_path(tmp_dir)
|
||||
config = Config(config_path)
|
||||
config.source_dir = 'path/to/android/src'
|
||||
config.save()
|
||||
with open(config_path, 'r') as actual_fp:
|
||||
actual = actual_fp.read().strip()
|
||||
with open(self.CONFIG_PATH, 'r') as expected_fp:
|
||||
expected = expected_fp.read().strip()
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,34 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from sourcedr.codesearch import CodeSearch
|
||||
from sourcedr.review_db import ReviewDB
|
||||
|
||||
|
||||
TESTDATA_DIR = os.path.join(os.path.dirname(__file__), 'testdata')
|
||||
ANDROID_DIR = os.path.join(TESTDATA_DIR, 'android_src')
|
||||
|
||||
|
||||
class ReviewDBTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.csearch_index_path = 'csearchindex'
|
||||
self.review_db_path = ReviewDB.DEFAULT_NAME
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
os.remove(self.csearch_index_path)
|
||||
os.remove(self.review_db_path)
|
||||
|
||||
|
||||
def test_preprocess(self):
|
||||
codesearch = CodeSearch(ANDROID_DIR, self.csearch_index_path)
|
||||
codesearch.build_index()
|
||||
review_db = ReviewDB(ReviewDB.DEFAULT_NAME, codesearch)
|
||||
review_db.find(patterns=['dlopen'], is_regexs=[False])
|
||||
self.assertTrue(os.path.exists(ReviewDB.DEFAULT_NAME))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,80 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import flask_testing
|
||||
|
||||
from sourcedr.project import Project
|
||||
from sourcedr.review_db import ReviewDB
|
||||
from sourcedr.server import create_app
|
||||
|
||||
|
||||
TESTDATA_DIR = os.path.join(os.path.dirname(__file__), 'testdata')
|
||||
ANDROID_DIR = os.path.join(TESTDATA_DIR, 'android_src')
|
||||
|
||||
|
||||
class ViewTest(flask_testing.TestCase):
|
||||
def create_app(self):
|
||||
self.tmp_dir = tempfile.TemporaryDirectory(prefix='test_sourcedr_')
|
||||
project = Project.get_or_create_project_dir(
|
||||
self.tmp_dir.name, ANDROID_DIR)
|
||||
project.update_csearch_index(True)
|
||||
self.project = project
|
||||
|
||||
app = create_app(project)
|
||||
app.config['TESTING'] = True
|
||||
self.app = app
|
||||
return app
|
||||
|
||||
|
||||
def setUp(self):
|
||||
review_db = self.project.review_db
|
||||
review_db.find(patterns=['dlopen'], is_regexs=[False])
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
self.tmp_dir.cleanup()
|
||||
|
||||
|
||||
def test_get_file(self):
|
||||
test_arg = 'example.c'
|
||||
response = self.client.get('/get_file',
|
||||
query_string=dict(path=test_arg))
|
||||
ret = response.json['result']
|
||||
with open(os.path.join(ANDROID_DIR, test_arg), 'r') as f:
|
||||
self.assertEqual(ret, f.read())
|
||||
|
||||
|
||||
def test_load_file(self):
|
||||
test_arg = 'dlopen/test.c'
|
||||
test_arg += ':10: handle = dlopen("libm.so.6", RTLD_LAZY);'
|
||||
response = self.client.get('/load_file',
|
||||
query_string=dict(path=test_arg))
|
||||
deps = json.loads(response.json['deps'])
|
||||
codes = json.loads(response.json['codes'])
|
||||
with open(self.project.review_db.path, 'r') as f:
|
||||
cdata = json.load(f)
|
||||
|
||||
self.assertEqual(deps, cdata[test_arg][0])
|
||||
self.assertEqual(codes, cdata[test_arg][1])
|
||||
|
||||
|
||||
def test_save_all(self):
|
||||
label = os.path.abspath('sourcedr/test/dlopen/test.c')
|
||||
label += ':10: handle = dlopen("libm.so.6", RTLD_LAZY);'
|
||||
test_arg = {
|
||||
'label': label,
|
||||
'deps': json.dumps(['this_is_a_test.so']),
|
||||
'codes': json.dumps(['arr_0', 'arr_1'])
|
||||
}
|
||||
response = self.client.get('/save_all', query_string=test_arg)
|
||||
cdata = ReviewDB(self.project.review_db.path, None).data
|
||||
self.assertEqual(['this_is_a_test.so'], cdata[test_arg['label']][0])
|
||||
self.assertEqual(['arr_0', 'arr_1'], cdata[test_arg['label']][1])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,15 +0,0 @@
|
||||
rule cc
|
||||
command = gcc -c -o $out $in -MMD -MF $out.d
|
||||
deps = gcc
|
||||
depfile = $out.d
|
||||
|
||||
rule ld
|
||||
command = gcc -o $out $in
|
||||
|
||||
build example.o: cc example.c
|
||||
|
||||
build example.so: ld example.o
|
||||
|
||||
build dlopen/test.o: cc dlopen/test.c
|
||||
|
||||
build dlopen/test.so: ld dlopen/test.o
|
||||
@@ -1,24 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
void *handle;
|
||||
double (*cosine)(double);
|
||||
char *error;
|
||||
|
||||
handle = dlopen("libm.so.6", RTLD_LAZY);
|
||||
if (!handle) {
|
||||
fputs (dlerror(), stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
cosine = dlsym(handle, "cos");
|
||||
if ((error = dlerror()) != NULL) {
|
||||
fputs(error, stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf ("%f\n", (*cosine)(2.0));
|
||||
dlclose(handle);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
int main() {
|
||||
printf("This is a simple testing file\n");
|
||||
int dlopen_analysis = 1;
|
||||
"This line with dlopen shouldn't be found"
|
||||
/*
|
||||
* This dlopen shouldn't be found
|
||||
*/
|
||||
dlopen("dlopen");
|
||||
handle = dlopen("libm.so.6", RTLD_LAZY);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
dlopen() in .txt file should not be matched
|
||||
@@ -1,21 +0,0 @@
|
||||
rule cc
|
||||
command = gcc -c -o $out $in -MMD -MF $out.d
|
||||
deps = gcc
|
||||
depfile = $out.d
|
||||
|
||||
rule ld
|
||||
command = gcc -o $out $in
|
||||
|
||||
build liba.so: ld a1.o a2.o
|
||||
|
||||
build libb.so: ld b.o
|
||||
|
||||
build libc.so: ld c.o
|
||||
|
||||
build a1.o: cc a1.c
|
||||
|
||||
build a2.o: cc a2.c
|
||||
|
||||
build b.o: cc b.c
|
||||
|
||||
build c.o: cc c.c
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"liba.so": ["libb.so", "libc.so", "a1.o", "a2.o"],
|
||||
"libb.so": ["b.o"],
|
||||
"libc.so": ["c.o"],
|
||||
"a1.o": ["a.h", "a1.c"],
|
||||
"a2.o": ["a.h", "a2.c"],
|
||||
"b.o": ["a.h", "b.c"],
|
||||
"c.o": ["c.c"]
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"a.h:2:dlopen(\"libx.so\",": [
|
||||
["libx.so"],
|
||||
["a.h:2:dlopen(\"libx.so\","]
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"source_dir": "path/to/android/src",
|
||||
"file_ext_blacklist": [],
|
||||
"file_name_blacklist": [],
|
||||
"path_component_blacklist": []
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Utility functions or classes."""
|
||||
|
||||
import os
|
||||
|
||||
|
||||
class LockedFile(object): # pylint: disable=too-few-public-methods
|
||||
"""Open a file with `.lock` file and rename it if everything goes well."""
|
||||
|
||||
|
||||
def __init__(self, path, mode):
|
||||
assert 'x' in mode
|
||||
self._path = path
|
||||
self._mode = mode
|
||||
self._fp = None
|
||||
|
||||
|
||||
def __enter__(self):
|
||||
"""Open the file at the specified path and with specified mode."""
|
||||
self._fp = open(self._get_locked_path(self._path), self._mode)
|
||||
return self._fp
|
||||
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Close the file object and rename the file if there are no
|
||||
exceptions."""
|
||||
self._fp.close()
|
||||
self._fp = None
|
||||
if exc_val is None:
|
||||
os.rename(self._get_locked_path(self._path), self._path)
|
||||
|
||||
|
||||
@classmethod
|
||||
def _get_locked_path(cls, path):
|
||||
"""Get the file path for the `.lock` file."""
|
||||
return path + '.lock'
|
||||
|
||||
|
||||
@classmethod
|
||||
def is_locked(cls, path):
|
||||
"""Check whether a path is locked."""
|
||||
return os.path.exists(cls._get_locked_path(path))
|
||||
Reference in New Issue
Block a user