def _generate_html(testsuites): values = { 'total_tests': 0, 'total_errors': 0, 'total_failures': 0, 'total_skipped': 0, 'total_time': 0.0 } for testsuite in testsuites: values['total_tests'] += testsuite.tests values['total_errors'] += testsuite.errors values['total_failures'] += testsuite.failures values['total_skipped'] += testsuite.skipped values['total_time'] += testsuite.time values['total_success'] = ReportTestSuite.success_rate( values['total_tests'], values['total_errors'], values['total_failures'], values['total_skipped']) values['summary_icon_class'] = ReportTestSuite.icon_class( values['total_tests'], values['total_errors'], values['total_failures'], values['total_skipped']) values['testsuites'] = [ts.as_dict() for ts in testsuites] package_name, _, _ = __name__.rpartition('.') renderer = MustacheRenderer(package_name=package_name) html = renderer.render_name('junit_report.html', values) return html
def _generate_html(testsuites): values = { "total_tests": 0, "total_errors": 0, "total_failures": 0, "total_skipped": 0, "total_time": 0.0, } for testsuite in testsuites: values["total_tests"] += testsuite.tests values["total_errors"] += testsuite.errors values["total_failures"] += testsuite.failures values["total_skipped"] += testsuite.skipped values["total_time"] += testsuite.time values["total_success"] = ReportTestSuite.success_rate( values["total_tests"], values["total_errors"], values["total_failures"], values["total_skipped"], ) values["summary_icon_class"] = ReportTestSuite.icon_class( values["total_tests"], values["total_errors"], values["total_failures"], values["total_skipped"], ) values["testsuites"] = [ts.as_dict() for ts in testsuites] package_name, _, _ = __name__.rpartition(".") renderer = MustacheRenderer(package_name=package_name) html = renderer.render_name("junit_report.html", values) return html
def _generate_html(testsuites): values = { 'total_tests': 0, 'total_errors': 0, 'total_failures': 0, 'total_skipped': 0, 'total_time': 0.0 } for testsuite in testsuites: values['total_tests'] += testsuite.tests values['total_errors'] += testsuite.errors values['total_failures'] += testsuite.failures values['total_skipped'] += testsuite.skipped values['total_time'] += testsuite.time values['total_success'] = ReportTestSuite.success_rate(values['total_tests'], values['total_errors'], values['total_failures'], values['total_skipped']) values['summary_icon_class'] = ReportTestSuite.icon_class(values['total_tests'], values['total_errors'], values['total_failures'], values['total_skipped']) values['testsuites'] = map(lambda ts: ts.as_dict(), testsuites) package_name, _, _ = __name__.rpartition('.') renderer = MustacheRenderer(package_name=package_name) html = renderer.render_name('junit_report.html', values) return html
def __init__(self, *args, **kwargs): super(EnsimeGen, self).__init__(*args, **kwargs) self._renderer = MustacheRenderer( self.get_options().template_directory, __name__) self.project_filename = os.path.join(self.cwd, '.ensime') self.ensime_output_dir = os.path.join(self.gen_project_workdir, 'out')
def _do_render(self, filename, args): package_name, _, _ = __name__.rpartition('.') renderer = MustacheRenderer(package_name=package_name) output_path = os.path.join(self._outdir, os.path.basename(filename)) self.context.log.info('Generating {}'.format(output_path)) html = renderer.render_name(filename, args) with safe_open(output_path, 'w') as outfile: outfile.write(html.encode('utf8'))
def _do_render(self, filename, args): package_name, _, _ = __name__.rpartition(".") renderer = MustacheRenderer(package_name=package_name) output_path = os.path.join(self._outdir, os.path.basename(filename)) self.context.log.info("Generating {}".format(output_path)) html = renderer.render_name(filename, args) with safe_open(output_path, "w") as outfile: outfile.write(html) return output_path
def __init__(self, run_tracker, settings): Reporter.__init__(self, run_tracker, settings) # The main report, and associated tool outputs, go under this dir. self._html_dir = settings.html_dir # We render HTML from mustache templates. self._renderer = MustacheRenderer(settings.template_dir, __name__) # We serve files relative to the build root. self._buildroot = get_buildroot() self._html_path_base = os.path.relpath(self._html_dir, self._buildroot) # We write the main report body to this file object. self._report_file = None # We redirect stdout, stderr etc. of tool invocations to these files. self._output_files = defaultdict(dict) # workunit_id -> {path -> fileobj}.
def __init__(self, port, settings): renderer = MustacheRenderer(settings.template_dir, __name__) class MyHandler(PantsHandler): def __init__(self, request, client_address, server): PantsHandler.__init__(self, settings, renderer, request, client_address, server) self._httpd = BaseHTTPServer.HTTPServer(('', port), MyHandler) self._httpd.timeout = 0.1 # Not the network timeout, but how often handle_request yields.
def generate_html(self, testsuites): values = {"total_tests": 0, "total_errors": 0, "total_failures": 0, "total_skipped": 0, "total_time": 0.0} for testsuite in testsuites: values["total_tests"] += testsuite.tests values["total_errors"] += testsuite.errors values["total_failures"] += testsuite.failures values["total_skipped"] += testsuite.skipped values["total_time"] += testsuite.time values["total_success"] = ReportTestSuite.success_rate( values["total_tests"], values["total_errors"], values["total_failures"], values["total_skipped"] ) values["summary_icon_class"] = ReportTestSuite.icon_class( values["total_tests"], values["total_errors"], values["total_failures"], values["total_skipped"] ) values["testsuites"] = map(lambda ts: ts.as_dict(), testsuites) package_name, _, _ = __name__.rpartition(".") renderer = MustacheRenderer(package_name=package_name) html = renderer.render_name("junit_report.html", values) return html
def __init__(self, run_tracker, settings): super(HtmlReporter, self).__init__(run_tracker, settings) # The main report, and associated tool outputs, go under this dir. self._html_dir = settings.html_dir # We render HTML from mustache templates. self._renderer = MustacheRenderer(settings.template_dir, __name__) # We serve files relative to the build root. self._buildroot = get_buildroot() self._html_path_base = os.path.relpath(self._html_dir, self._buildroot) # We write the main report body to this file object. self._report_file = None # We redirect stdout, stderr etc. of tool invocations to these files. self._output_files = defaultdict(dict) # workunit_id -> {path -> fileobj}. self._linkify_memo = {} # Map from filename to timestamp (ms since the epoch) of when we last overwrote that file. # Useful for preventing too-frequent overwrites of, e.g., timing stats, # which can noticeably slow down short pants runs with many workunits. self._last_overwrite_time = {}
def __init__(self, **kwargs): super(TemplateData, self).__init__(MustacheRenderer.expand(kwargs))
def __init__(self, **kwargs): dict.__init__(self, MustacheRenderer.expand(kwargs))
class HtmlReporter(Reporter): """HTML reporting to files. The files are intended to be served by the ReportingServer, not accessed directly from the filesystem. """ # HTML reporting settings. # html_dir: Where the report files go. # template_dir: Where to find mustache templates. Settings = namedtuple('Settings', Reporter.Settings._fields + ('html_dir', 'template_dir')) def __init__(self, run_tracker, settings): Reporter.__init__(self, run_tracker, settings) # The main report, and associated tool outputs, go under this dir. self._html_dir = settings.html_dir # We render HTML from mustache templates. self._renderer = MustacheRenderer(settings.template_dir, __name__) # We serve files relative to the build root. self._buildroot = get_buildroot() self._html_path_base = os.path.relpath(self._html_dir, self._buildroot) # We write the main report body to this file object. self._report_file = None # We redirect stdout, stderr etc. of tool invocations to these files. self._output_files = defaultdict(dict) # workunit_id -> {path -> fileobj}. self._linkify_memo = {} def report_path(self): """The path to the main report file.""" return os.path.join(self._html_dir, 'build.html') def open(self): """Implementation of Reporter callback.""" safe_mkdir(os.path.dirname(self._html_dir)) self._report_file = open(self.report_path(), 'w') def close(self): """Implementation of Reporter callback.""" self._report_file.close() # Make sure everything's closed. for files in self._output_files.values(): for f in files.values(): f.close() def start_workunit(self, workunit): """Implementation of Reporter callback.""" # We use these properties of the workunit to decide how to render information about it. is_bootstrap = workunit.has_label(WorkUnit.BOOTSTRAP) is_tool = workunit.has_label(WorkUnit.TOOL) is_multitool = workunit.has_label(WorkUnit.MULTITOOL) is_test = workunit.has_label(WorkUnit.TEST) # Get useful properties from the workunit. workunit_dict = workunit.to_dict() if workunit_dict['cmd']: workunit_dict['cmd'] = linkify(self._buildroot, workunit_dict['cmd'].replace('$', '\\\\$'), self._linkify_memo) # Create the template arguments. args = { 'indent': len(workunit.ancestors()) * 10, 'html_path_base': self._html_path_base, 'workunit': workunit_dict, 'header_text': workunit.name, 'initially_open': is_test or not (is_bootstrap or is_tool or is_multitool), 'is_tool': is_tool, 'is_multitool': is_multitool } args.update({ 'collapsible': lambda x: self._renderer.render_callable('collapsible', x, args) }) # Render the workunit's div. s = self._renderer.render_name('workunit_start', args) if is_tool: # This workunit is a tool invocation, so render the appropriate content. # We use the same args, slightly modified. del args['initially_open'] if is_test: # Have test framework stdout open by default, but not that of other tools. # This is an arbitrary choice, but one that turns out to be useful to users in practice. args['stdout_initially_open'] = True s += self._renderer.render_name('tool_invocation_start', args) # ... and we're done. self._emit(s) # CSS classes from pants.css that we use to style the header text to reflect the outcome. _outcome_css_classes = ['aborted', 'failure', 'warning', 'success', 'unknown'] def end_workunit(self, workunit): """Implementation of Reporter callback.""" # Create the template arguments. duration = workunit.duration() timing = '{:.3f}'.format(duration) unaccounted_time = None # Background work may be idle a lot, no point in reporting that as unaccounted. if self.is_under_main_root(workunit): unaccounted_time_secs = workunit.unaccounted_time() if unaccounted_time_secs >= 1 and unaccounted_time_secs > 0.05 * duration: unaccounted_time = '{:.3f}'.format(unaccounted_time_secs) args = { 'workunit': workunit.to_dict(), 'status': HtmlReporter._outcome_css_classes[workunit.outcome()], 'timing': timing, 'unaccounted_time': unaccounted_time, 'aborted': workunit.outcome() == WorkUnit.ABORTED } s = '' if workunit.has_label(WorkUnit.TOOL): s += self._renderer.render_name('tool_invocation_end', args) s += self._renderer.render_name('workunit_end', args) self._emit(s) # Update the timings. def render_timings(timings): timings_dict = timings.get_all() for item in timings_dict: item['timing_string'] = '{:.3f}'.format(item['timing']) args = { 'timings': timings_dict } return self._renderer.render_name('aggregated_timings', args) self._overwrite('cumulative_timings', render_timings(self.run_tracker.cumulative_timings)) self._overwrite('self_timings', render_timings(self.run_tracker.self_timings)) # Update the artifact cache stats. def render_cache_stats(artifact_cache_stats): def fix_detail_id(e, _id): return e if isinstance(e, string_types) else e + (_id, ) msg_elements = [] for cache_name, stat in artifact_cache_stats.stats_per_cache.items(): msg_elements.extend([ cache_name + ' artifact cache: ', # Explicitly set the detail ids, so their displayed/hidden state survives a refresh. fix_detail_id(items_to_report_element(stat.hit_targets, 'hit'), 'cache-hit-details'), ', ', fix_detail_id(items_to_report_element(stat.miss_targets, 'miss'), 'cache-miss-details'), '.' ]) if not msg_elements: msg_elements = ['No artifact cache use.'] return self._render_message(*msg_elements) self._overwrite('artifact_cache_stats', render_cache_stats(self.run_tracker.artifact_cache_stats)) for f in self._output_files[workunit.id].values(): f.close() def handle_output(self, workunit, label, s): """Implementation of Reporter callback.""" if os.path.exists(self._html_dir): # Make sure we're not immediately after a clean-all. path = os.path.join(self._html_dir, '{}.{}'.format(workunit.id, label)) output_files = self._output_files[workunit.id] if path not in output_files: f = open(path, 'w') output_files[path] = f else: f = output_files[path] f.write(self._htmlify_text(s).encode('utf-8')) # We must flush in the same thread as the write. f.flush() _log_level_css_map = { Report.FATAL: 'fatal', Report.ERROR: 'error', Report.WARN: 'warn', Report.INFO: 'info', Report.DEBUG: 'debug' } def do_handle_log(self, workunit, level, *msg_elements): """Implementation of Reporter callback.""" content = '<span class="{}">{}</span>'.format( HtmlReporter._log_level_css_map[level], self._render_message(*msg_elements)) # Generate some javascript that appends the content to the workunit's div. args = { 'content_id': uuid.uuid4(), # Identifies this content. 'workunit_id': workunit.id, # The workunit this reporting content belongs to. 'content': content, # The content to append. } s = self._renderer.render_name('append_to_workunit', args) # Emit that javascript to the main report body. self._emit(s) def _render_message(self, *msg_elements): elements = [] detail_ids = [] for element in msg_elements: # Each element can be a message or a (message, detail) pair, as received by handle_log(). # # However, as an internal implementation detail, we also allow an element to be a tuple # (message, detail, detail_initially_visible[, detail_id]) # # - If the detail exists, clicking on the text will toggle display of the detail and close # all other details in this message. # - If detail_initially_visible is True, the detail will be displayed by default. # # Toggling is managed via detail_ids: when clicking on a detail, it closes all details # in this message with detail_ids different than that of the one being clicked on. # We allow detail_id to be explicitly specified, so that the open/closed state can be # preserved through refreshes. For example, when looking at the artifact cache stats, # if "hits" are open and "misses" are closed, we want to remember that even after # the cache stats are updated and the message re-rendered. if isinstance(element, string_types): element = [element] defaults = ('', None, None, False) # Map assumes None for missing values, so this will pick the default for those. (text, detail, detail_id, detail_initially_visible) = \ map(lambda x, y: x or y, element, defaults) element_args = {'text': self._htmlify_text(text) } if detail is not None: detail_id = detail_id or uuid.uuid4() detail_ids.append(detail_id) element_args.update({ 'detail': self._htmlify_text(detail), 'detail_initially_visible': detail_initially_visible, 'detail-id': detail_id }) elements.append(element_args) args = { 'elements': elements, 'all-detail-ids': detail_ids } return self._renderer.render_name('message', args) def _emit(self, s): """Append content to the main report file.""" if os.path.exists(self._html_dir): # Make sure we're not immediately after a clean-all. self._report_file.write(s) self._report_file.flush() # We must flush in the same thread as the write. def _overwrite(self, filename, s): """Overwrite a file with the specified contents.""" if os.path.exists(self._html_dir): # Make sure we're not immediately after a clean-all. with open(os.path.join(self._html_dir, filename), 'w') as f: f.write(s) def _htmlify_text(self, s): """Make text HTML-friendly.""" colored = self._handle_ansi_color_codes(cgi.escape(s.decode('utf-8'))) return linkify(self._buildroot, colored, self._linkify_memo).replace('\n', '</br>') _ANSI_COLOR_CODE_RE = re.compile(r'\033\[((?:\d|;)*)m') def _handle_ansi_color_codes(self, s): """Replace ansi escape sequences with spans of appropriately named css classes.""" parts = HtmlReporter._ANSI_COLOR_CODE_RE.split(s) ret = [] span_depth = 0 # Note that len(parts) is always odd: text, code, text, code, ..., text. for i in range(0, len(parts), 2): ret.append(parts[i]) if i + 1 < len(parts): for code in parts[i + 1].split(';'): if code == 0: # Reset. while span_depth > 0: ret.append('</span>') span_depth -= 1 else: ret.append('<span class="ansi-{}">'.format(code)) span_depth += 1 while span_depth > 0: ret.append('</span>') span_depth -= 1 return ''.join(ret)
def __init__(self, template_text, **template_data): self._template = MustacheRenderer.parse_template(template_text) self.template_data = template_data
def __init__(self, **kwargs): super().__init__(MustacheRenderer.expand(kwargs))
class HtmlReporter(Reporter): """HTML reporting to files. The files are intended to be served by the ReportingServer, not accessed directly from the filesystem. """ # HTML reporting settings. # html_dir: Where the report files go. # template_dir: Where to find mustache templates. Settings = namedtuple( 'Settings', Reporter.Settings._fields + ('html_dir', 'template_dir')) def __init__(self, run_tracker, settings): Reporter.__init__(self, run_tracker, settings) # The main report, and associated tool outputs, go under this dir. self._html_dir = settings.html_dir # We render HTML from mustache templates. self._renderer = MustacheRenderer(settings.template_dir, __name__) # We serve files relative to the build root. self._buildroot = get_buildroot() self._html_path_base = os.path.relpath(self._html_dir, self._buildroot) # We write the main report body to this file object. self._report_file = None # We redirect stdout, stderr etc. of tool invocations to these files. self._output_files = defaultdict( dict) # workunit_id -> {path -> fileobj}. self._linkify_memo = {} def report_path(self): """The path to the main report file.""" return os.path.join(self._html_dir, 'build.html') def open(self): """Implementation of Reporter callback.""" safe_mkdir(os.path.dirname(self._html_dir)) self._report_file = open(self.report_path(), 'w') def close(self): """Implementation of Reporter callback.""" self._report_file.close() # Make sure everything's closed. for files in self._output_files.values(): for f in files.values(): f.close() def start_workunit(self, workunit): """Implementation of Reporter callback.""" # We use these properties of the workunit to decide how to render information about it. is_bootstrap = workunit.has_label(WorkUnitLabel.BOOTSTRAP) is_tool = workunit.has_label(WorkUnitLabel.TOOL) is_multitool = workunit.has_label(WorkUnitLabel.MULTITOOL) is_test = workunit.has_label(WorkUnitLabel.TEST) # Get useful properties from the workunit. workunit_dict = workunit.to_dict() if workunit_dict['cmd']: workunit_dict['cmd'] = linkify( self._buildroot, workunit_dict['cmd'].replace('$', '\\\\$'), self._linkify_memo) # Create the template arguments. args = { 'indent': len(workunit.ancestors()) * 10, 'html_path_base': self._html_path_base, 'workunit': workunit_dict, 'header_text': workunit.name, 'initially_open': is_test or not (is_bootstrap or is_tool or is_multitool), 'is_tool': is_tool, 'is_multitool': is_multitool } args.update({ 'collapsible': lambda x: self._renderer.render_callable('collapsible', x, args) }) # Render the workunit's div. s = self._renderer.render_name('workunit_start', args) if is_tool: # This workunit is a tool invocation, so render the appropriate content. # We use the same args, slightly modified. del args['initially_open'] if is_test: # Have test framework stdout open by default, but not that of other tools. # This is an arbitrary choice, but one that turns out to be useful to users in practice. args['stdout_initially_open'] = True s += self._renderer.render_name('tool_invocation_start', args) # ... and we're done. self._emit(s) # CSS classes from pants.css that we use to style the header text to reflect the outcome. _outcome_css_classes = [ 'aborted', 'failure', 'warning', 'success', 'unknown' ] def end_workunit(self, workunit): """Implementation of Reporter callback.""" # Create the template arguments. duration = workunit.duration() timing = '{:.3f}'.format(duration) unaccounted_time = None # Background work may be idle a lot, no point in reporting that as unaccounted. if self.is_under_main_root(workunit): unaccounted_time_secs = workunit.unaccounted_time() if unaccounted_time_secs >= 1 and unaccounted_time_secs > 0.05 * duration: unaccounted_time = '{:.3f}'.format(unaccounted_time_secs) args = { 'workunit': workunit.to_dict(), 'status': HtmlReporter._outcome_css_classes[workunit.outcome()], 'timing': timing, 'unaccounted_time': unaccounted_time, 'aborted': workunit.outcome() == WorkUnit.ABORTED } s = '' if workunit.has_label(WorkUnitLabel.TOOL): s += self._renderer.render_name('tool_invocation_end', args) s += self._renderer.render_name('workunit_end', args) self._emit(s) # Update the timings. def render_timings(timings): timings_dict = timings.get_all() for item in timings_dict: item['timing_string'] = '{:.3f}'.format(item['timing']) args = {'timings': timings_dict} return self._renderer.render_name('aggregated_timings', args) self._overwrite('cumulative_timings', render_timings(self.run_tracker.cumulative_timings)) self._overwrite('self_timings', render_timings(self.run_tracker.self_timings)) # Update the artifact cache stats. def render_cache_stats(artifact_cache_stats): def fix_detail_id(e, _id): return e if isinstance(e, string_types) else e + (_id, ) msg_elements = [] for cache_name, stat in artifact_cache_stats.stats_per_cache.items( ): msg_elements.extend([ cache_name + ' artifact cache: ', # Explicitly set the detail ids, so their displayed/hidden state survives a refresh. fix_detail_id( items_to_report_element(stat.hit_targets, 'hit'), 'cache-hit-details'), ', ', fix_detail_id( items_to_report_element(stat.miss_targets, 'miss'), 'cache-miss-details'), '.' ]) if not msg_elements: msg_elements = ['No artifact cache use.'] return self._render_message(*msg_elements) self._overwrite( 'artifact_cache_stats', render_cache_stats(self.run_tracker.artifact_cache_stats)) for f in self._output_files[workunit.id].values(): f.close() def handle_output(self, workunit, label, s): """Implementation of Reporter callback.""" if os.path.exists( self._html_dir ): # Make sure we're not immediately after a clean-all. path = os.path.join(self._html_dir, '{}.{}'.format(workunit.id, label)) output_files = self._output_files[workunit.id] if path not in output_files: f = open(path, 'w') output_files[path] = f else: f = output_files[path] f.write(self._htmlify_text(s).encode('utf-8')) # We must flush in the same thread as the write. f.flush() _log_level_css_map = { Report.FATAL: 'fatal', Report.ERROR: 'error', Report.WARN: 'warn', Report.INFO: 'info', Report.DEBUG: 'debug' } def do_handle_log(self, workunit, level, *msg_elements): """Implementation of Reporter callback.""" content = '<span class="{}">{}</span>'.format( HtmlReporter._log_level_css_map[level], self._render_message(*msg_elements)) # Generate some javascript that appends the content to the workunit's div. args = { 'content_id': uuid.uuid4(), # Identifies this content. 'workunit_id': workunit.id, # The workunit this reporting content belongs to. 'content': content, # The content to append. } s = self._renderer.render_name('append_to_workunit', args) # Emit that javascript to the main report body. self._emit(s) def _render_message(self, *msg_elements): elements = [] detail_ids = [] for element in msg_elements: # Each element can be a message or a (message, detail) pair, as received by handle_log(). # # However, as an internal implementation detail, we also allow an element to be a tuple # (message, detail, detail_initially_visible[, detail_id]) # # - If the detail exists, clicking on the text will toggle display of the detail and close # all other details in this message. # - If detail_initially_visible is True, the detail will be displayed by default. # # Toggling is managed via detail_ids: when clicking on a detail, it closes all details # in this message with detail_ids different than that of the one being clicked on. # We allow detail_id to be explicitly specified, so that the open/closed state can be # preserved through refreshes. For example, when looking at the artifact cache stats, # if "hits" are open and "misses" are closed, we want to remember that even after # the cache stats are updated and the message re-rendered. if isinstance(element, string_types): element = [element] defaults = ('', None, None, False) # Map assumes None for missing values, so this will pick the default for those. (text, detail, detail_id, detail_initially_visible) = \ map(lambda x, y: x or y, element, defaults) element_args = {'text': self._htmlify_text(text)} if detail is not None: detail_id = detail_id or uuid.uuid4() detail_ids.append(detail_id) element_args.update({ 'detail': self._htmlify_text(detail), 'detail_initially_visible': detail_initially_visible, 'detail-id': detail_id }) elements.append(element_args) args = {'elements': elements, 'all-detail-ids': detail_ids} return self._renderer.render_name('message', args) def _emit(self, s): """Append content to the main report file.""" if os.path.exists( self._html_dir ): # Make sure we're not immediately after a clean-all. self._report_file.write(s) self._report_file.flush( ) # We must flush in the same thread as the write. def _overwrite(self, filename, s): """Overwrite a file with the specified contents.""" if os.path.exists( self._html_dir ): # Make sure we're not immediately after a clean-all. with open(os.path.join(self._html_dir, filename), 'w') as f: f.write(s) def _htmlify_text(self, s): """Make text HTML-friendly.""" colored = self._handle_ansi_color_codes(cgi.escape(s.decode('utf-8'))) return linkify(self._buildroot, colored, self._linkify_memo).replace('\n', '</br>') _ANSI_COLOR_CODE_RE = re.compile(r'\033\[((?:\d|;)*)m') def _handle_ansi_color_codes(self, s): """Replace ansi escape sequences with spans of appropriately named css classes.""" parts = HtmlReporter._ANSI_COLOR_CODE_RE.split(s) ret = [] span_depth = 0 # Note that len(parts) is always odd: text, code, text, code, ..., text. for i in range(0, len(parts), 2): ret.append(parts[i]) if i + 1 < len(parts): for code in parts[i + 1].split(';'): if code == 0: # Reset. while span_depth > 0: ret.append('</span>') span_depth -= 1 else: ret.append('<span class="ansi-{}">'.format(code)) span_depth += 1 while span_depth > 0: ret.append('</span>') span_depth -= 1 return ''.join(ret)
class EnsimeGen(IdeGen): """Create an Ensime project from the given targets.""" @classmethod def register_options(cls, register): super(EnsimeGen, cls).register_options(register) register( '--excluded-deps', type=list, advanced=True, help= 'Exclude these targets from dependency resolution during workspace generation' ) register( '--template-directory', type=str, advanced=True, default=None, help= 'Exclude these targets from dependency resolution during workspace generation' ) def __init__(self, *args, **kwargs): super(EnsimeGen, self).__init__(*args, **kwargs) self._renderer = MustacheRenderer( self.get_options().template_directory, __name__) self.project_filename = os.path.join(self.cwd, '.ensime') self.ensime_output_dir = os.path.join(self.gen_project_workdir, 'out') def resolve_jars(self, targets): excluded_targets = set() for exclusion in self.get_options().excluded_deps: for target in self.context.resolve(exclusion): excluded_targets.add(target) synthetic_target = self.context.add_new_target(address=Address( '', 'exlusions'), target_type=JavaLibrary, dependencies=list(), sources=list(), excludes=[]) filtered_targets = [ target for target in targets if not target in excluded_targets ] + [synthetic_target] return super(EnsimeGen, self).resolve_jars(filtered_targets) def generate_project(self, project): def linked_folder_id(source_set): return source_set.source_base.replace(os.path.sep, '.') def base_path(source_set): return os.path.join(source_set.root_dir, source_set.source_base, source_set.path) def create_source_base_template(source_set): source_base = base_path(source_set) return SourceBase(id=linked_folder_id(source_set), path=source_base) source_sets = project.sources[:] if project.has_python: source_sets.extend(project.py_sources) source_bases = frozenset(map(create_source_base_template, source_sets)) libs = [] def add_jarlibs(classpath_entries): for classpath_entry in classpath_entries: libs.append((classpath_entry.jar, classpath_entry.source_jar)) add_jarlibs(project.internal_jars) add_jarlibs(project.external_jars) scala_full_version = scala_platform.scala_build_info[ self.context.options['scala-platform']['version']].full_version scala = { 'language_level': scala_full_version, 'compiler_classpath': project.scala_compiler_classpath } outdir = os.path.abspath(self.ensime_output_dir) if not os.path.exists(outdir): os.makedirs(outdir) java_platform = JvmPlatform.global_instance().default_platform jdk_home = JvmPlatform.preferred_jvm_distribution([java_platform], strict=True).home configured_project = { 'name': self.project_name, 'java_home': jdk_home, 'scala': scala, 'source_bases': source_bases, 'has_tests': project.has_tests, 'internal_jars': [cp_entry.jar for cp_entry in project.internal_jars], 'internal_source_jars': [ cp_entry.source_jar for cp_entry in project.internal_jars if cp_entry.source_jar ], 'external_jars': [cp_entry.jar for cp_entry in project.external_jars], 'external_javadoc_jars': [ cp_entry.javadoc_jar for cp_entry in project.external_jars if cp_entry.javadoc_jar ], 'external_source_jars': [ cp_entry.source_jar for cp_entry in project.external_jars if cp_entry.source_jar ], 'libs': libs, 'outdir': os.path.relpath(outdir, get_buildroot()), 'root_dir': get_buildroot(), 'cache_dir': os.path.join(self.cwd, '.ensime_cache') } with safe_open(self.project_filename, 'w') as output: output.write( self._renderer.render_name(_TEMPLATE, {'project': configured_project})) print('\nGenerated ensime project at {}{}'.format( self.gen_project_workdir, os.sep))