@@ -7,11 +7,13 @@ import 'dart:convert';
77import 'dart:io' ;
88
99import 'package:archive/archive.dart' ;
10+ import 'package:charcode/charcode.dart' ;
1011import 'package:collection/collection.dart' ;
1112import 'package:grinder/grinder.dart' ;
1213import 'package:http/http.dart' as http;
1314import 'package:node_preamble/preamble.dart' as preamble;
1415import 'package:pub_semver/pub_semver.dart' ;
16+ import 'package:source_span/source_span.dart' ;
1517import 'package:xml/xml.dart' as xml;
1618import 'package:yaml/yaml.dart' ;
1719
@@ -127,12 +129,66 @@ void _writeNpmPackage(String destination, Map<String, dynamic> json) {
127129 dir.createSync (recursive: true );
128130
129131 log ("copying package/package.json to $destination " );
130- new File (p.join (dir.path , 'package.json' ))
132+ new File (p.join (destination , 'package.json' ))
131133 .writeAsStringSync (JSON .encode (json));
132134
133135 copy (new File (p.join ('package' , 'sass.js' )), dir);
134136 copy (new File (p.join ('build' , 'sass.dart.js' )), dir);
135- copy (new File ('README.md' ), dir);
137+
138+ log ("copying package/README.npm.md to $destination " );
139+ new File (p.join (destination, 'README.md' ))
140+ .writeAsStringSync (_readAndResolveMarkdown ('package/README.npm.md' ));
141+ }
142+
143+ final _readAndResolveRegExp = new RegExp (
144+ r"^<!-- +#include +([^\s]+) +"
145+ '"([^"\n ]+)"'
146+ r" +-->$" ,
147+ multiLine: true );
148+
149+ /// Reads a Markdown file from [path] and resolves include directives.
150+ ///
151+ /// Include directives have the syntax `"<!-- #include" PATH HEADER "-->"` ,
152+ /// which must appear on its own line. PATH is a relative file: URL to another
153+ /// Markdown file, and HEADER is the name of a header in that file whose
154+ /// contents should be included as-is.
155+ String _readAndResolveMarkdown (String path) => new File (path)
156+ .readAsStringSync ()
157+ .replaceAllMapped (_readAndResolveRegExp, (match) {
158+ String included;
159+ try {
160+ included = new File (p.join (p.dirname (path), p.fromUri (match[1 ])))
161+ .readAsStringSync ();
162+ } catch (error) {
163+ _matchError (match, error.toString (), url: p.toUri (path));
164+ }
165+
166+ Match headerMatch;
167+ try {
168+ headerMatch = "# ${match [2 ]}\n " .allMatches (included).first;
169+ } on StateError {
170+ _matchError (match, "Could not find header." , url: p.toUri (path));
171+ }
172+
173+ var headerLevel = 0 ;
174+ var index = headerMatch.start;
175+ while (index >= 0 && included.codeUnitAt (index) == $hash) {
176+ headerLevel++ ;
177+ index-- ;
178+ }
179+
180+ // The section goes until the next header of the same level, or the end
181+ // of the document.
182+ var sectionEnd = included.indexOf ("#" * headerLevel, headerMatch.end);
183+ if (sectionEnd == - 1 ) sectionEnd = included.length;
184+
185+ return included.substring (headerMatch.end, sectionEnd).trim ();
186+ });
187+
188+ /// Throws a nice [SourceSpanException] associated with [match] .
189+ void _matchError (Match match, String message, {url}) {
190+ var file = new SourceFile .fromString (match.input, url: url);
191+ throw new SourceSpanException (message, file.span (match.start, match.end));
136192}
137193
138194@Task ('Build a Chocolatey package.' )
0 commit comments