• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3"""SourceDR project configurations and databases.
4
5`Project` class holds configuration files, review databases, pattern databases,
6and `codesearch` index files.
7"""
8
9import collections
10import json
11import os
12import shutil
13
14from sourcedr.codesearch import CodeSearch, PathFilter
15from sourcedr.pattern_db import PatternDB
16from sourcedr.review_db import ReviewDB
17from sourcedr.utils import LockedFile
18
19
20class Config(object):
21    """SourceDR project configuration file."""
22
23    DEFAULT_NAME = 'sourcedr.json'
24
25    _PATH_TRAVERSAL_ATTRS = (
26            'file_ext_blacklist', 'file_name_blacklist',
27            'path_component_blacklist')
28
29
30    @classmethod
31    def get_default_path(cls, project_dir):
32        """Get the default path of the configuration file under a project
33        directory."""
34        return os.path.join(project_dir, cls.DEFAULT_NAME)
35
36
37    def __init__(self, path):
38        self.path = path
39
40        self.source_dir = None
41        self.file_ext_blacklist = set()
42        self.file_name_blacklist = set()
43        self.path_component_blacklist = set()
44
45
46    def load(self):
47        """Load the project configuration from the JSON file."""
48        with open(self.path, 'r') as config_fp:
49            config_json = json.load(config_fp)
50            for key, value in config_json.items():
51                if key == 'source_dir':
52                    self.source_dir = value
53                elif key in self._PATH_TRAVERSAL_ATTRS:
54                    setattr(self, key, set(value))
55                else:
56                    raise ValueError('unknown config name: ' + key)
57
58
59    def save(self):
60        """Save the project configuration to the JSON file."""
61        with LockedFile(self.path, 'x') as config_fp:
62            config = collections.OrderedDict()
63            config['source_dir'] = self.source_dir
64            for key in self._PATH_TRAVERSAL_ATTRS:
65                config[key] = sorted(getattr(self, key))
66            json.dump(config, config_fp, indent=2)
67
68
69class Project(object):
70    """SourceDR project configuration files and databases."""
71
72    def __init__(self, project_dir):
73        """Load a project from a given project directory."""
74
75        project_dir = os.path.abspath(project_dir)
76        self.project_dir = project_dir
77
78        if not os.path.isdir(project_dir):
79            raise ValueError('project directory not found: ' + project_dir)
80
81        # Load configuration files
82        config_path = Config.get_default_path(project_dir)
83        self.config = Config(config_path)
84        self.config.load()
85
86        # Recalculate source directory
87        self.source_dir = os.path.abspath(
88                os.path.join(project_dir, self.config.source_dir))
89
90        # csearchindex file
91        path_filter = PathFilter(self.config.file_ext_blacklist,
92                                 self.config.file_name_blacklist,
93                                 self.config.path_component_blacklist)
94        csearch_index_path = CodeSearch.get_default_path(project_dir)
95        self.codesearch = CodeSearch(self.source_dir, csearch_index_path,
96                                     path_filter)
97        self.codesearch.add_default_filters()
98
99        # Review database file
100        review_db_path = ReviewDB.get_default_path(project_dir)
101        self.review_db = ReviewDB(review_db_path, self.codesearch)
102
103        # Pattern database file
104        pattern_db_path = PatternDB.get_default_path(project_dir)
105        self.pattern_db = PatternDB(pattern_db_path)
106
107        # Sanity checks
108        self._check_source_dir()
109        self._check_lock_files()
110
111
112    def update_csearch_index(self, remove_existing_index):
113        """Create or update codesearch index."""
114        self.codesearch.build_index(remove_existing_index)
115
116
117    def update_review_db(self):
118        """Update the entries in the review database."""
119        patterns, is_regexs = self.pattern_db.load()
120        self.review_db.find(patterns, is_regexs)
121
122
123    def _check_source_dir(self):
124        """Check the availability of the source directory."""
125        if not os.path.isdir(self.source_dir):
126            raise ValueError('source directory not found: ' + self.source_dir)
127
128
129    def _check_lock_files(self):
130        """Check whether there are some lock files."""
131        for path in (self.config.path, self.review_db.path,
132                     self.pattern_db.path):
133            if LockedFile.is_locked(path):
134                raise ValueError('file locked: ' + path)
135
136
137    @classmethod
138    def create_project_dir(cls, project_dir, source_dir):
139        """Create a directory for a new project and setup default
140        configurations."""
141
142        if not os.path.isdir(source_dir):
143            raise ValueError('source directory not found: ' + source_dir)
144
145        os.makedirs(project_dir, exist_ok=True)
146
147        # Compute the relative path between project_dir and source_dir
148        project_dir = os.path.abspath(project_dir)
149        source_dir = os.path.relpath(os.path.abspath(source_dir), project_dir)
150
151        # Copy default files
152        defaults_dir = os.path.join(os.path.dirname(__file__), 'defaults')
153        for name in (Config.DEFAULT_NAME, PatternDB.DEFAULT_NAME):
154            shutil.copyfile(os.path.join(defaults_dir, name),
155                            os.path.join(project_dir, name))
156
157        # Update the source directory in the configuration file
158        config_path = Config.get_default_path(project_dir)
159        config = Config(config_path)
160        config.load()
161        config.source_dir = source_dir
162        config.save()
163
164        return Project(project_dir)
165
166
167    @classmethod
168    def get_or_create_project_dir(cls, project_dir, source_dir):
169        config_file_path = Config.get_default_path(project_dir)
170        if os.path.exists(config_file_path):
171            return Project(project_dir)
172        else:
173            return cls.create_project_dir(project_dir, source_dir)
174