blob: 80621207774c9ec6296ff04957da8e8012a1c531 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library dart_pad.summarize;
import 'package:crypto/crypto.dart';
import 'protos/dart_services.pb.dart' as proto;
/// Instances of this class take string input of dart code as well as an
/// analysis result, and output a text description ofthe code's size, packages,
/// and other useful information.
class Summarizer {
final String dart;
final String html;
final String css;
final proto.AnalysisResults analysis;
_SummarizeToken storage;
int _randomizer;
static Map<String, List<int>> cuttoffs = <String, List<int>>{
'size': <int>[8, 30, 1000], //0-7 = small, 8 - 29 = decent, 29+ = gigantic
'errorCount': <int>[1, 10, 100]
};
static Map<String, String> codeKeyWords = <String, String>{
'await': 'await',
'async': 'async',
'rpc': 'RESTful serverside app'
};
static Map<String, String> additionKeyWords = <String, String>{
'pirate': 'pirates',
'bird': 'birds',
'llama': 'llamas',
'dog': 'dogs'
};
static Map<String, List<String>> categories = <String, List<String>>{
/// This [size] [codeQuantifier] contains [error] errors and warnings.
'size-2': <String>[
'gigantic',
'Jupiterian sized',
'immense',
'massive',
'enormous',
'huge',
'epic',
'humongous'
],
'size-1': <String>[
'decently sized',
'exceptional',
'awesome',
'amazing',
'visionary',
'legendary'
],
'size-0': <String>['itty-bitty', 'miniature', 'tiny', 'pint-sized'],
'compiledQuantifier': <String>['Dart program', 'pad'],
'failedQuantifier': <String>[
'assemblage of characters',
'series of strings',
'grouping of letters'
],
'errorCount-2': <String>[
'many',
'a motherload of',
'copious amounts of',
'unholy quantities of'
],
'errorCount-1': <String>[
'some',
'a few',
'sparse amounts of',
'very few instances of'
],
'errorCount-0': <String>['zero', 'no', 'a nonexistent amount of', '0'],
'use': <String>['demonstrates', 'illustrates', 'depicts'],
'code-0': <String>['it'],
'code-1': <String>['It'],
};
Summarizer({this.dart, this.html, this.css, this.analysis}) {
if (dart == null) throw ArgumentError('Input cannot be null.');
_randomizer = _sumList(md5.convert(dart.codeUnits).bytes);
storage = _SummarizeToken(dart, analysis: analysis);
}
bool get hasAnalysisResults => analysis != null;
int _sumList(List<int> list) => list.reduce((int a, int b) => a + b);
String _categorySelector(String category, int itemCount) {
if (category == 'size' || category == 'errorCount') {
final maxField = cuttoffs[category];
for (var counter = 0; counter < maxField.length; counter++) {
if (itemCount < maxField[counter]) return '$category-$counter';
}
return '$category-${maxField.length - 1}';
} else {
return null;
}
}
String _wordSelector(String category) {
if (categories.containsKey(category)) {
final returnSet = categories[category];
return returnSet.elementAt(_randomizer % returnSet.length);
} else {
return null;
}
}
String _sentenceFiller(String word, [int size]) {
if (size != null) {
return _wordSelector(_categorySelector(word, size));
} else {
return _wordSelector(word);
}
}
String _additionList(List<String> list) {
if (list.isEmpty) return '';
var englishList = ' Also, mentions ';
for (var i = 0; i < list.length; i++) {
englishList += list[i];
if (i < list.length - 2) englishList += ', ';
if (i == list.length - 2) {
if (i != 0) englishList += ',';
englishList += ' and ';
}
}
englishList += '. ';
return englishList;
// TODO: Tokenize features instead of returning as string.
}
List<String> additionSearch() => _additionSearch();
bool _usedInDartSource(String feature) => dart.contains(feature);
List<String> _additionSearch() {
final features = <String>[];
for (final feature in additionKeyWords.keys) {
if (_usedInDartSource(feature)) features.add(additionKeyWords[feature]);
}
return features;
}
List<String> _codeSearch() {
final features = <String>[];
for (final feature in codeKeyWords.keys) {
if (_usedInDartSource(feature)) features.add(codeKeyWords[feature]);
}
return features;
}
String _featureList(List<String> list) {
if (list.isEmpty) return '. ';
var englishList = ', and ${_sentenceFiller('use')} use of ';
for (var i = 0; i < list.length; i++) {
englishList += list[i];
if (i < list.length - 2) englishList += ', ';
if (i == list.length - 2) {
if (i != 0) englishList += ',';
englishList += ' and ';
}
}
englishList += ' features. ';
return englishList;
}
String _packageList(List<String> list, {String source}) {
if (list.isEmpty) {
return source == null ? '' : '. ';
}
var englishList = '';
if (source == 'packages') {
englishList += ', and ${_sentenceFiller('code-0')} imports ';
} else {
englishList += '${_sentenceFiller('code-1')} imports the ';
}
for (var i = 0; i < list.length; i++) {
englishList += "'${list[i]}'";
if (i < list.length - 2) englishList += ', ';
if (i == list.length - 2) {
if (i != 0) englishList += ',';
englishList += ' and ';
}
}
if (source == 'packages') {
englishList += ' from external packages. ';
} else {
englishList += ' packages as well. ';
}
return englishList;
}
String _htmlCSS() {
var htmlCSS = 'This code has ';
if (_hasCSS && _hasHtml) {
htmlCSS += 'associated html and css';
return htmlCSS;
}
if (!_hasCSS && !_hasHtml) {
htmlCSS += 'no associated html or css';
return htmlCSS;
}
if (!_hasHtml) {
htmlCSS += 'no ';
} else {
htmlCSS += 'some ';
}
htmlCSS += 'associated html and ';
if (!_hasCSS) {
htmlCSS += 'no ';
} else {
htmlCSS += 'some ';
}
htmlCSS += 'associated css';
return htmlCSS;
}
bool get _hasHtml => html != null && html.isNotEmpty;
bool get _hasCSS => css != null && css.isNotEmpty;
String returnAsSimpleSummary() {
if (hasAnalysisResults) {
var summary = '';
summary += 'This ${_sentenceFiller('size', storage.linesCode)} ';
if (storage.errorPresent) {
summary += '${_sentenceFiller('failedQuantifier')} contains ';
} else {
summary += '${_sentenceFiller('compiledQuantifier')} contains ';
}
summary += '${_sentenceFiller('errorCount', storage.errorCount)} ';
summary += 'errors and warnings';
summary += '${_featureList(_codeSearch())}';
summary += '${_htmlCSS()}';
summary += '${_packageList(storage.packageImports, source: 'packages')}';
summary += '${_additionList(_additionSearch())}';
return summary.trim();
} else {
var summary = 'Summary: ';
summary += 'This is a ${_sentenceFiller('size', storage.linesCode)} ';
summary += '${_sentenceFiller('compiledQuantifier')}';
summary += '${_featureList(_codeSearch())}';
summary += '${_htmlCSS()}';
summary += '${_additionList(_additionSearch())}';
return summary.trim();
}
}
String returnAsMarkDown() {
// For now, we're just returning plain text. This might change at some point
// to include some markdown styling as well.
return returnAsSimpleSummary();
}
}
class _SummarizeToken {
int linesCode;
int packageCount;
int errorCount;
int warningCount;
bool errorPresent;
bool warningPresent;
List<String> packageImports;
List<proto.AnalysisIssue> errors;
_SummarizeToken(String input, {proto.AnalysisResults analysis}) {
linesCode = _linesOfCode(input);
if (analysis != null) {
errorPresent = analysis.issues
.any((proto.AnalysisIssue issue) => issue.kind == 'error');
warningPresent = analysis.issues
.any((proto.AnalysisIssue issue) => issue.kind == 'warning');
packageCount = analysis.packageImports.length;
packageImports = analysis.packageImports;
errors = analysis.issues;
errorCount = errors.length;
}
}
int _linesOfCode(String input) => input.split('\n').length;
}