mirror of
https://github.com/felixblaschke/shelf_plus.git
synced 2025-08-03 17:43:18 +08:00
new documentation system
This commit is contained in:
114
README.md
114
README.md
@ -4,6 +4,7 @@
|
||||
the Shelf platform. It's a great base to **start off** your apps fast, while
|
||||
**maintaining full compatibility** with the **Shelf** ecosystem.
|
||||
|
||||
<!-- #code doc_files/quickstart.dart -->
|
||||
```dart
|
||||
import 'package:shelf_plus/shelf_plus.dart';
|
||||
|
||||
@ -17,15 +18,18 @@ Handler init() {
|
||||
return app;
|
||||
}
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
It comes with a lot of awesome features, like **zero-configuration** initializer, build-in **hot-reload**
|
||||
and a **super powerful** and **intuitive router upgrade**. Continue reading and get to know why
|
||||
you can't ever code without **Shelf Plus**.
|
||||
|
||||
<!-- #space 1 -->
|
||||
|
||||
|
||||
|
||||
## Table of contents
|
||||
<!-- // end of #space -->
|
||||
<!-- #toc -->
|
||||
## Table of Contents
|
||||
|
||||
[**Router Plus**](#router-plus)
|
||||
- [Routes API](#routes-api)
|
||||
@ -47,16 +51,19 @@ you can't ever code without **Shelf Plus**.
|
||||
|
||||
[**Examples**](#examples)
|
||||
- [Rest Service](#rest-service)
|
||||
<!-- // end of #toc -->
|
||||
|
||||
<!-- #space 1 -->
|
||||
|
||||
|
||||
|
||||
<!-- // end of #space -->
|
||||
## Router Plus
|
||||
|
||||
Router Plus is a **high-level abstraction layer** sitting directly on [shelf_router](https://pub.dev/packages/shelf_router).
|
||||
It **shares the same [routing logic](https://pub.dev/documentation/shelf_router/latest/shelf_router/Router-class.html)**
|
||||
but allows you to handle responses in a very simple way.
|
||||
|
||||
<!-- #code doc_files/router_plus_intro.dart -->
|
||||
```dart
|
||||
var app = Router().plus;
|
||||
|
||||
@ -72,6 +79,7 @@ app.get('/file', () => File('path/to/file.zip'));
|
||||
|
||||
app.get('/person', () => Person(name: 'John', age: 42));
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
The core mechanic is called **ResponseHandler** which continuously refines a data structure,
|
||||
until it resolves in a [Shelf Response](https://pub.dev/documentation/shelf/latest/shelf/Response-class.html).
|
||||
@ -79,22 +87,27 @@ This extensible system comes with support for text, json, binaries, files, json
|
||||
|
||||
You can access the **Router Plus** by calling the **`.plus`** getter on a regular Shelf Router.
|
||||
|
||||
<!-- #code doc_files/router_plus_upgrade.dart -->
|
||||
```dart
|
||||
var app = Router().plus;
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
<!-- #space 1 -->
|
||||
|
||||
|
||||
|
||||
<!-- // end of #space -->
|
||||
### Routes API
|
||||
|
||||
The API mimics the [Shelf Router](https://pub.dev/documentation/shelf_router/latest/shelf_router/Router-class.html)
|
||||
methods. You basically use an HTTP verb, define a route to match and specify a handler,
|
||||
that generates the response.
|
||||
|
||||
<!-- #code doc_files/routes_api_verb.dart -->
|
||||
```dart
|
||||
app.get('/path/to/match', () => 'a response');
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
You can return any type, as long the **ResponseHandler** mechanism has a capable
|
||||
resolver to handle that type.
|
||||
@ -103,6 +116,7 @@ If you need the [Shelf Request](https://pub.dev/documentation/shelf/latest/shelf
|
||||
object, specify it as the first parameter. Any other parameter will match the
|
||||
route parameters, if defined.
|
||||
|
||||
<!-- #code doc_files/routes_api_signature.dart -->
|
||||
```dart
|
||||
app.get('/minimalistic', () => 'response');
|
||||
|
||||
@ -115,14 +129,17 @@ app.get('/customer/<id>', (Request request) {
|
||||
return 'response: ${request.routeParameter('id')}';
|
||||
});
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
<!-- #space 1 -->
|
||||
|
||||
|
||||
|
||||
<!-- // end of #space -->
|
||||
### Middleware
|
||||
|
||||
Router Plus provides several options to place your middleware ([Shelf Middleware](https://pub.dev/documentation/shelf/latest/shelf/Middleware.html)).
|
||||
|
||||
<!-- #code doc_files/middleware_intro.dart -->
|
||||
```dart
|
||||
var app = Router().plus;
|
||||
|
||||
@ -134,18 +151,22 @@ app.get('/request1', () => 'response', use: middlewareB);
|
||||
// combine middleware with + operator
|
||||
app.get('/request2', () => 'response', use: middlewareB + middlewareC);
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
You can also apply middleware dynamically inside a route handler, using the `>>` operator.
|
||||
|
||||
<!-- #code doc_files/middleware_in_requesthandler.dart -->
|
||||
```dart
|
||||
app.get('/request/<value>', (Request request, String value) {
|
||||
return middleware(value) >> 'response';
|
||||
});
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
<!-- #space 1 -->
|
||||
|
||||
|
||||
|
||||
<!-- // end of #space -->
|
||||
### ResponseHandler
|
||||
|
||||
ResponseHandler process the **return value** of a route handler, until it matches a
|
||||
@ -164,6 +185,7 @@ ResponseHandler process the **return value** of a route handler, until it matche
|
||||
|
||||
*Example:*
|
||||
|
||||
<!-- #code doc_files/response_handler_example.dart -->
|
||||
```dart
|
||||
import 'dart:io';
|
||||
|
||||
@ -202,12 +224,14 @@ class Person {
|
||||
Map<String, dynamic> toJson() => {'name': name};
|
||||
}
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
#### Custom ResponseHandler
|
||||
|
||||
You can add your own ResponseHandler by using a [Shelf Middleware](https://pub.dev/documentation/shelf/latest/shelf/Middleware.html)
|
||||
created with the `.middleware` getter on a ResponseHandler function.
|
||||
|
||||
<!-- #code doc_files/response_handler_custom.dart -->
|
||||
```dart
|
||||
// define custom ResponseHandler
|
||||
ResponseHandler catResponseHandler = (Request request, dynamic maybeCat) =>
|
||||
@ -218,21 +242,26 @@ app.use(catResponseHandler.middleware);
|
||||
|
||||
app.get('/cat', () => Cat());
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
<!-- #code doc_files/response_handler_custom_cat.dart -->
|
||||
```dart
|
||||
class Cat {
|
||||
String interact() => 'Purrrrr!';
|
||||
}
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
<!-- #space 1 -->
|
||||
|
||||
|
||||
|
||||
<!-- // end of #space -->
|
||||
### Cascading multiple routers
|
||||
|
||||
Router Plus is compatible to a [Shelf Handler](https://pub.dev/documentation/shelf/latest/shelf/Handler.html).
|
||||
So, you can also use it in a [Shelf Cascade](https://pub.dev/documentation/shelf/latest/shelf/Pipeline-class.html).
|
||||
This package provides a `cascade()` function, to quickly set up a cascade.
|
||||
|
||||
<!-- #code doc_files/cascade.dart -->
|
||||
```dart
|
||||
import 'package:shelf_plus/shelf_plus.dart';
|
||||
|
||||
@ -249,53 +278,56 @@ Handler init() {
|
||||
return cascade([app1, app2]);
|
||||
}
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
|
||||
|
||||
<!-- #space 2 -->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- // end of #space -->
|
||||
## Middleware collection
|
||||
|
||||
This package comes with additional [Shelf Middleware](https://pub.dev/documentation/shelf/latest/shelf/Middleware.html)
|
||||
to simplify common tasks.
|
||||
|
||||
<!-- #space 1 -->
|
||||
|
||||
|
||||
|
||||
<!-- // end of #space -->
|
||||
### setContentType
|
||||
|
||||
Sets the `content-type` header of a `Response` to the specified **mime-type**.
|
||||
|
||||
<!-- #code doc_files/mw_set_content_type.dart -->
|
||||
```dart
|
||||
app.get('/one', () => setContentType('application/json') >> '1');
|
||||
|
||||
app.get('/two', () => '2', use: setContentType('application/json'));
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- #space1 -->
|
||||
### typeByExtension
|
||||
|
||||
Sets the `content-type` header of a `Response` to the **mime-type** of the
|
||||
specified **file extension**.
|
||||
|
||||
<!-- #code doc_files/mw_type_by_extension.dart -->
|
||||
```dart
|
||||
app.get('/', () => '<h1>Hi!</h1>', use: typeByExtension('html'));
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- #space1 -->
|
||||
### download
|
||||
|
||||
Sets the `content-disposition` header of a `Response`, so browsers will download the
|
||||
server response instead of displaying it. Optionally you can define a specific **file name**.
|
||||
|
||||
<!-- #code doc_files/mw_download.dart -->
|
||||
```dart
|
||||
app.get('/wallpaper/download', () => File('image.jpg'), use: download());
|
||||
|
||||
@ -304,14 +336,15 @@ app.get('/invoice/<id>', (Request request, String id) {
|
||||
return download(filename: 'invoice_$id.pdf') >> document;
|
||||
});
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
|
||||
<!-- #space 2 -->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- // end of #space -->
|
||||
## Request body handling
|
||||
|
||||
Shelf Plus provides an extensible mechanism to process the HTTP body of a request.
|
||||
@ -319,6 +352,7 @@ You can access it by calling the `.body` getter on a [Shelf Request](https://pub
|
||||
|
||||
It comes with build-in support for text, JSON and binary.
|
||||
|
||||
<!-- #code doc_files/request_body_intro.dart -->
|
||||
```dart
|
||||
app.post('/text', (Request request) async {
|
||||
var text = await request.body.asString;
|
||||
@ -330,18 +364,20 @@ app.post('/json', (Request request) async {
|
||||
return 'You send me: ${person.name}';
|
||||
});
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- #space1 -->
|
||||
### Object deserialization
|
||||
|
||||
A recommended way to deserialize a json-encoded object is to provide a
|
||||
**reviver function**, that can be generated by code generators.
|
||||
|
||||
<!-- #code doc_files/request_body_deserialize1.dart -->
|
||||
```dart
|
||||
var person = await request.body.as(Person.fromJson);
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
<!-- #code doc_files/request_body_deserialize2.dart -->
|
||||
```dart
|
||||
class Person {
|
||||
final String name;
|
||||
@ -354,36 +390,38 @@ class Person {
|
||||
}
|
||||
}
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- #space1 -->
|
||||
### Custom accessors for model classes
|
||||
|
||||
You can add own accessors for model classes by creating an
|
||||
extension on `RequestBodyAccessor`.
|
||||
|
||||
<!-- #code doc_files/request_body_ext_model1.dart -->
|
||||
```dart
|
||||
extension PersonAccessor on RequestBodyAccessor {
|
||||
Future<Person> get asPerson async => Person.fromJson(await asJson);
|
||||
}
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
<!-- #code doc_files/request_body_ext_model2.dart -->
|
||||
```dart
|
||||
app.post('/person', (Request request) async {
|
||||
var person = await request.body.asPerson;
|
||||
return 'You send me: ${person.name}';
|
||||
});
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- #space1 -->
|
||||
### Custom accessors for third party body parser
|
||||
|
||||
You can plug-in any other body parser by creating an
|
||||
extension on `RequestBodyAccessor`.
|
||||
|
||||
<!-- #code doc_files/request_body_ext_third_party.dart -->
|
||||
```dart
|
||||
extension OtherFormatBodyParserAccessor on RequestBodyAccessor {
|
||||
Future<OtherBodyFormat> get asOtherFormat async {
|
||||
@ -391,19 +429,21 @@ extension OtherFormatBodyParserAccessor on RequestBodyAccessor {
|
||||
}
|
||||
}
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
|
||||
|
||||
<!-- #space 2 -->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- // end of #space -->
|
||||
## Shelf Run
|
||||
|
||||
Shelf Run is **zero-configuration** web-server initializer with **hot-reload** support.
|
||||
|
||||
<!-- #code doc_files/shelf_run_intro.dart -->
|
||||
```dart
|
||||
import 'package:shelf_plus/shelf_plus.dart';
|
||||
|
||||
@ -413,6 +453,7 @@ Handler init() {
|
||||
return (Request request) => Response.ok('Hello!');
|
||||
}
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
It's important to use a dedicated `init` function, returning a [Shelf Handler](https://pub.dev/documentation/shelf/latest/shelf/Handler.html),
|
||||
for hot-reload to work properly.
|
||||
@ -432,17 +473,14 @@ Shelf Run uses a default configuration, that can be modified via **environment v
|
||||
|
||||
You can override the default values with optional parameters in the `shelfRun()` function.
|
||||
|
||||
<!-- #code doc_files/shelf_run_override_default.dart -->
|
||||
```dart
|
||||
void main() => shelfRun(init, defaultBindPort: 3000);
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- #space2 -->
|
||||
## Examples
|
||||
|
||||
### Rest Service
|
||||
@ -452,6 +490,7 @@ Implementation of a CRUD, rest-like backend service. ([Full sources](/example/ex
|
||||
|
||||
|
||||
**example_rest.dart**
|
||||
<!-- #code example/example_rest/bin/example_rest.dart -->
|
||||
```dart
|
||||
import 'dart:io';
|
||||
|
||||
@ -504,8 +543,10 @@ Handler init() {
|
||||
return app;
|
||||
}
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
||||
**person.dart**
|
||||
<!-- #code example/example_rest/bin/person.dart -->
|
||||
```dart
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
@ -534,4 +575,5 @@ class Person {
|
||||
|
||||
static Person fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
|
||||
}
|
||||
```
|
||||
```
|
||||
<!-- // end of #code -->
|
||||
|
@ -1,7 +1 @@
|
||||
include: package:lints/recommended.yaml
|
||||
|
||||
analyzer:
|
||||
exclude: [build/**]
|
||||
#strong-mode:
|
||||
#implicit-casts: false
|
||||
#implicit-dynamic: false
|
||||
|
@ -7,10 +7,10 @@ late dynamic middleware;
|
||||
Handler init() {
|
||||
var app = Router().plus;
|
||||
|
||||
//@start
|
||||
// #begin
|
||||
app.get('/request/<value>', (Request request, String value) {
|
||||
return middleware(value) >> 'response';
|
||||
});
|
||||
//@end
|
||||
// #end
|
||||
return app;
|
||||
}
|
@ -7,7 +7,7 @@ late Middleware middlewareB;
|
||||
late Middleware middlewareC;
|
||||
|
||||
Handler init() {
|
||||
//@start
|
||||
// #begin
|
||||
var app = Router().plus;
|
||||
|
||||
app.use(middlewareA); // apply to all routes
|
||||
@ -17,6 +17,6 @@ Handler init() {
|
||||
|
||||
// combine middleware with + operator
|
||||
app.get('/request2', () => 'response', use: middlewareB + middlewareC);
|
||||
//@end
|
||||
// #end
|
||||
return app;
|
||||
}
|
@ -8,13 +8,13 @@ dynamic pdfService;
|
||||
|
||||
Handler init() {
|
||||
var app = Router().plus;
|
||||
//@start
|
||||
// #begin
|
||||
app.get('/wallpaper/download', () => File('image.jpg'), use: download());
|
||||
|
||||
app.get('/invoice/<id>', (Request request, String id) {
|
||||
File document = pdfService.generateInvoice(id);
|
||||
return download(filename: 'invoice_$id.pdf') >> document;
|
||||
});
|
||||
//@end
|
||||
// #end
|
||||
return app;
|
||||
}
|
@ -4,10 +4,10 @@ void main() => shelfRun(init);
|
||||
|
||||
Handler init() {
|
||||
var app = Router().plus;
|
||||
//@start
|
||||
// #begin
|
||||
app.get('/one', () => setContentType('application/json') >> '1');
|
||||
|
||||
app.get('/two', () => '2', use: setContentType('application/json'));
|
||||
//@end
|
||||
// #end
|
||||
return app;
|
||||
}
|
@ -4,8 +4,8 @@ void main() => shelfRun(init);
|
||||
|
||||
Handler init() {
|
||||
var app = Router().plus;
|
||||
//@start
|
||||
// #begin
|
||||
app.get('/', () => '<h1>Hi!</h1>', use: typeByExtension('html'));
|
||||
//@end
|
||||
// #end
|
||||
return app;
|
||||
}
|
@ -8,9 +8,9 @@ Handler init() {
|
||||
var app = Router().plus;
|
||||
|
||||
app.post('/json', (Request request) async {
|
||||
//@start
|
||||
// #begin
|
||||
var person = await request.body.as(Person.fromJson);
|
||||
//@end
|
||||
// #end
|
||||
return 'You send me: ${person.name}';
|
||||
});
|
||||
|
@ -2,8 +2,8 @@ import 'package:shelf_plus/shelf_plus.dart';
|
||||
|
||||
import 'other_code.dart';
|
||||
|
||||
//@start
|
||||
// #begin
|
||||
extension PersonAccessor on RequestBodyAccessor {
|
||||
Future<Person> get asPerson async => Person.fromJson(await asJson);
|
||||
}
|
||||
//@end
|
||||
// #end
|
@ -6,12 +6,12 @@ void main() => shelfRun(init);
|
||||
|
||||
Handler init() {
|
||||
var app = Router().plus;
|
||||
//@start
|
||||
// #begin
|
||||
app.post('/person', (Request request) async {
|
||||
var person = await request.body.asPerson;
|
||||
return 'You send me: ${person.name}';
|
||||
});
|
||||
//@end
|
||||
// #end
|
||||
return app;
|
||||
}
|
||||
|
@ -6,10 +6,10 @@ class OtherBodyFormat {}
|
||||
|
||||
dynamic ThirdPartyLib;
|
||||
|
||||
//@start
|
||||
// #begin
|
||||
extension OtherFormatBodyParserAccessor on RequestBodyAccessor {
|
||||
Future<OtherBodyFormat> get asOtherFormat async {
|
||||
return ThirdPartyLib().process(request.read());
|
||||
}
|
||||
}
|
||||
//@end
|
||||
// #end
|
@ -6,7 +6,7 @@ void main() => shelfRun(init);
|
||||
|
||||
Handler init() {
|
||||
var app = Router().plus;
|
||||
//@start
|
||||
// #begin
|
||||
app.post('/text', (Request request) async {
|
||||
var text = await request.body.asString;
|
||||
return 'You send me: $text';
|
||||
@ -16,6 +16,6 @@ Handler init() {
|
||||
var person = Person.fromJson(await request.body.asJson);
|
||||
return 'You send me: ${person.name}';
|
||||
});
|
||||
//@end
|
||||
// #end
|
||||
return app;
|
||||
}
|
@ -6,7 +6,7 @@ void main() => shelfRun(init);
|
||||
|
||||
Handler init() {
|
||||
var app = Router().plus;
|
||||
//@start
|
||||
// #begin
|
||||
// define custom ResponseHandler
|
||||
ResponseHandler catResponseHandler = (Request request, dynamic maybeCat) =>
|
||||
maybeCat is Cat ? maybeCat.interact() : null;
|
||||
@ -15,7 +15,7 @@ Handler init() {
|
||||
app.use(catResponseHandler.middleware);
|
||||
|
||||
app.get('/cat', () => Cat());
|
||||
//@end
|
||||
// #end
|
||||
return app;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ void main() => shelfRun(init);
|
||||
late dynamic middleware;
|
||||
|
||||
Handler init() {
|
||||
//@start
|
||||
// #begin
|
||||
var app = Router().plus;
|
||||
|
||||
app.use(middleware());
|
||||
@ -23,6 +23,6 @@ Handler init() {
|
||||
app.get('/file', () => File('path/to/file.zip'));
|
||||
|
||||
app.get('/person', () => Person(name: 'John', age: 42));
|
||||
//@end
|
||||
// #end
|
||||
return app;
|
||||
}
|
@ -3,8 +3,8 @@ import 'package:shelf_plus/shelf_plus.dart';
|
||||
void main() => shelfRun(init);
|
||||
|
||||
Handler init() {
|
||||
//@start
|
||||
// #begin
|
||||
var app = Router().plus;
|
||||
//@end
|
||||
// #end
|
||||
return app;
|
||||
}
|
@ -4,7 +4,7 @@ void main() => shelfRun(init);
|
||||
|
||||
Handler init() {
|
||||
var app = Router().plus;
|
||||
//@start
|
||||
// #begin
|
||||
app.get('/minimalistic', () => 'response');
|
||||
|
||||
app.get('/with/request', (Request request) => 'response');
|
||||
@ -15,6 +15,6 @@ Handler init() {
|
||||
// alternative access to route parameters
|
||||
return 'response: ${request.routeParameter('id')}';
|
||||
});
|
||||
//@end
|
||||
// #end
|
||||
return app;
|
||||
}
|
@ -4,8 +4,8 @@ void main() => shelfRun(init);
|
||||
|
||||
Handler init() {
|
||||
var app = Router().plus;
|
||||
//@start
|
||||
// #begin
|
||||
app.get('/path/to/match', () => 'a response');
|
||||
//@end
|
||||
// #end
|
||||
return app;
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import 'package:shelf_plus/shelf_plus.dart';
|
||||
|
||||
//@start
|
||||
// #begin
|
||||
void main() => shelfRun(init, defaultBindPort: 3000);
|
||||
//@end
|
||||
// #end
|
||||
|
||||
Handler init() {
|
||||
return (Request request) => Response.ok('Hello!');
|
@ -1,118 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
void main() {
|
||||
process(File('tool/templates/README.md'), File('README.md'));
|
||||
}
|
||||
|
||||
void process(File file, File to) {
|
||||
print('Processing $file...');
|
||||
var lines = file.readAsLinesSync();
|
||||
|
||||
lines = gapMacro(file, lines);
|
||||
lines = codeMacro(file, lines);
|
||||
lines = indexMacro(file, lines);
|
||||
|
||||
to.writeAsStringSync(lines.join('\n'));
|
||||
}
|
||||
|
||||
List<String> gapMacro(File docFile, List<String> lines) {
|
||||
final result = <String>[];
|
||||
|
||||
for (var line in lines) {
|
||||
if (line.trim().startsWith('@gap')) {
|
||||
var size = int.parse(
|
||||
line.substring(line.indexOf('@gap') + '@gap'.length).trim());
|
||||
|
||||
for (var i = 0; i < size; i++) {
|
||||
result.add('');
|
||||
result.add(' ');
|
||||
result.add('');
|
||||
}
|
||||
} else {
|
||||
result.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
List<String> indexMacro(File docFile, List<String> lines) {
|
||||
final result = <String>[];
|
||||
|
||||
for (var line in lines) {
|
||||
if (line.trim().startsWith('@index')) {
|
||||
result.add('## Table of contents');
|
||||
|
||||
lines
|
||||
.map((line) => line.trim())
|
||||
.where((line) => line.startsWith('##'))
|
||||
.forEach((line) {
|
||||
var depth = line.indexOf(' ');
|
||||
var title = line.substring(depth + 1);
|
||||
var link = '#' + title.toLowerCase().replaceAll(' ', '-');
|
||||
|
||||
print('$depth $title');
|
||||
|
||||
if (depth == 2) {
|
||||
result.add('');
|
||||
result.add('[**$title**]($link)');
|
||||
} else if (depth == 3) {
|
||||
result.add(' - [$title]($link)');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
result.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
List<String> codeMacro(File docFile, List<String> lines) {
|
||||
final result = <String>[];
|
||||
|
||||
for (var line in lines) {
|
||||
if (line.trim().startsWith('@code')) {
|
||||
var path = line.substring(line.indexOf('@code') + '@code'.length).trim();
|
||||
var file = File(path);
|
||||
var extension =
|
||||
file.path.substring(file.path.lastIndexOf('.') + '.'.length);
|
||||
var code = file.readAsStringSync().trim().split('\n');
|
||||
|
||||
code =
|
||||
code.where((line) => !line.trim().startsWith('// ignore:')).toList();
|
||||
|
||||
code = manualTrim(code);
|
||||
|
||||
result.add('```$extension');
|
||||
result.addAll(code);
|
||||
result.add('```');
|
||||
} else {
|
||||
result.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
List<String> manualTrim(List<String> code) {
|
||||
if (code.any((line) => line.contains('//@start'))) {
|
||||
var startLine = code.firstWhere((line) => line.contains('//@start'));
|
||||
var endLine = code.firstWhere((line) => line.contains('//@end'));
|
||||
var indent = startLine.indexOf('//');
|
||||
|
||||
// Cut out lines
|
||||
var result = code
|
||||
.getRange(code.indexOf(startLine) + 1, code.indexOf(endLine))
|
||||
.toList();
|
||||
|
||||
// Intend based on comment
|
||||
result = result
|
||||
.map((line) => line.length > indent ? line.substring(indent) : line)
|
||||
.toList();
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return code;
|
||||
}
|
||||
}
|
@ -1,212 +0,0 @@
|
||||
# Shelf Plus
|
||||
|
||||
**Shelf Plus** is a **quality of life** addon for your server-side development within
|
||||
the Shelf platform. It's a great base to **start off** your apps fast, while
|
||||
**maintaining full compatibility** with the **Shelf** ecosystem.
|
||||
|
||||
@code tool/templates/code/quickstart.dart
|
||||
|
||||
It comes with a lot of awesome features, like **zero-configuration** initializer, build-in **hot-reload**
|
||||
and a **super powerful** and **intuitive router upgrade**. Continue reading and get to know why
|
||||
you can't ever code without **Shelf Plus**.
|
||||
|
||||
@gap 1
|
||||
@index
|
||||
|
||||
@gap 1
|
||||
## Router Plus
|
||||
|
||||
Router Plus is a **high-level abstraction layer** sitting directly on [shelf_router](https://pub.dev/packages/shelf_router).
|
||||
It **shares the same [routing logic](https://pub.dev/documentation/shelf_router/latest/shelf_router/Router-class.html)**
|
||||
but allows you to handle responses in a very simple way.
|
||||
|
||||
@code tool/templates/code/router_plus_intro.dart
|
||||
|
||||
The core mechanic is called **ResponseHandler** which continuously refines a data structure,
|
||||
until it resolves in a [Shelf Response](https://pub.dev/documentation/shelf/latest/shelf/Response-class.html).
|
||||
This extensible system comes with support for text, json, binaries, files, json serialization and Shelf [Handler](https://pub.dev/documentation/shelf/latest/shelf/Handler.html).
|
||||
|
||||
You can access the **Router Plus** by calling the **`.plus`** getter on a regular Shelf Router.
|
||||
|
||||
@code tool/templates/code/router_plus_upgrade.dart
|
||||
|
||||
@gap 1
|
||||
### Routes API
|
||||
|
||||
The API mimics the [Shelf Router](https://pub.dev/documentation/shelf_router/latest/shelf_router/Router-class.html)
|
||||
methods. You basically use an HTTP verb, define a route to match and specify a handler,
|
||||
that generates the response.
|
||||
|
||||
@code tool/templates/code/routes_api_verb.dart
|
||||
|
||||
You can return any type, as long the **ResponseHandler** mechanism has a capable
|
||||
resolver to handle that type.
|
||||
|
||||
If you need the [Shelf Request](https://pub.dev/documentation/shelf/latest/shelf/Request-class.html)
|
||||
object, specify it as the first parameter. Any other parameter will match the
|
||||
route parameters, if defined.
|
||||
|
||||
@code tool/templates/code/routes_api_signature.dart
|
||||
|
||||
@gap 1
|
||||
### Middleware
|
||||
|
||||
Router Plus provides several options to place your middleware ([Shelf Middleware](https://pub.dev/documentation/shelf/latest/shelf/Middleware.html)).
|
||||
|
||||
@code tool/templates/code/middleware_intro.dart
|
||||
|
||||
You can also apply middleware dynamically inside a route handler, using the `>>` operator.
|
||||
|
||||
@code tool/templates/code/middleware_in_requesthandler.dart
|
||||
|
||||
@gap 1
|
||||
### ResponseHandler
|
||||
|
||||
ResponseHandler process the **return value** of a route handler, until it matches a
|
||||
[Shelf Response](https://pub.dev/documentation/shelf/latest/shelf/Response-class.html).
|
||||
|
||||
#### Build-in ResponseHandler
|
||||
|
||||
| Source | Result | Use case |
|
||||
| ---------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| `String` | Shelf `Response` | Respond with a text (text/plain) |
|
||||
| `Uint8List`, `Stream<List<int>>` | Shelf `Response` | Respond with binary (application/octet-stream) |
|
||||
| `Map<String, dynamic>`, `List<dynamic>>` | Shelf `Response` | Respond with JSON (application/json) |
|
||||
| Any Type having a `toJson()` method | `Map<String, dynamic>`, `List<dynamic>>` *(expected)* | Provide serialization support for classes |
|
||||
| Shelf `Handler` | Shelf `Response` | Processing Shelf-based Middleware or Handler |
|
||||
| `File` (dart:io) | Shelf `Response` | Respond with file contents (using [shelf_static](https://pub.dev/packages/shelf_static)) |
|
||||
|
||||
*Example:*
|
||||
|
||||
@code tool/templates/code/response_handler_example.dart
|
||||
|
||||
#### Custom ResponseHandler
|
||||
|
||||
You can add your own ResponseHandler by using a [Shelf Middleware](https://pub.dev/documentation/shelf/latest/shelf/Middleware.html)
|
||||
created with the `.middleware` getter on a ResponseHandler function.
|
||||
|
||||
@code tool/templates/code/response_handler_custom.dart
|
||||
@code tool/templates/code/response_handler_custom_cat.dart
|
||||
|
||||
@gap 1
|
||||
### Cascading multiple routers
|
||||
|
||||
Router Plus is compatible to a [Shelf Handler](https://pub.dev/documentation/shelf/latest/shelf/Handler.html).
|
||||
So, you can also use it in a [Shelf Cascade](https://pub.dev/documentation/shelf/latest/shelf/Pipeline-class.html).
|
||||
This package provides a `cascade()` function, to quickly set up a cascade.
|
||||
|
||||
@code tool/templates/code/cascade.dart
|
||||
|
||||
|
||||
|
||||
@gap 2
|
||||
## Middleware collection
|
||||
|
||||
This package comes with additional [Shelf Middleware](https://pub.dev/documentation/shelf/latest/shelf/Middleware.html)
|
||||
to simplify common tasks.
|
||||
|
||||
@gap 1
|
||||
### setContentType
|
||||
|
||||
Sets the `content-type` header of a `Response` to the specified **mime-type**.
|
||||
|
||||
@code tool/templates/code/mw_set_content_type.dart
|
||||
|
||||
@gap1
|
||||
### typeByExtension
|
||||
|
||||
Sets the `content-type` header of a `Response` to the **mime-type** of the
|
||||
specified **file extension**.
|
||||
|
||||
@code tool/templates/code/mw_type_by_extension.dart
|
||||
|
||||
@gap1
|
||||
### download
|
||||
|
||||
Sets the `content-disposition` header of a `Response`, so browsers will download the
|
||||
server response instead of displaying it. Optionally you can define a specific **file name**.
|
||||
|
||||
@code tool/templates/code/mw_download.dart
|
||||
|
||||
|
||||
@gap 2
|
||||
## Request body handling
|
||||
|
||||
Shelf Plus provides an extensible mechanism to process the HTTP body of a request.
|
||||
You can access it by calling the `.body` getter on a [Shelf Request](https://pub.dev/documentation/shelf/latest/shelf/Request-class.html).
|
||||
|
||||
It comes with build-in support for text, JSON and binary.
|
||||
|
||||
@code tool/templates/code/request_body_intro.dart
|
||||
|
||||
@gap1
|
||||
### Object deserialization
|
||||
|
||||
A recommended way to deserialize a json-encoded object is to provide a
|
||||
**reviver function**, that can be generated by code generators.
|
||||
|
||||
@code tool/templates/code/request_body_deserialize1.dart
|
||||
@code tool/templates/code/request_body_deserialize2.dart
|
||||
|
||||
|
||||
@gap1
|
||||
### Custom accessors for model classes
|
||||
|
||||
You can add own accessors for model classes by creating an
|
||||
extension on `RequestBodyAccessor`.
|
||||
|
||||
@code tool/templates/code/request_body_ext_model1.dart
|
||||
@code tool/templates/code/request_body_ext_model2.dart
|
||||
|
||||
@gap1
|
||||
### Custom accessors for third party body parser
|
||||
|
||||
You can plug-in any other body parser by creating an
|
||||
extension on `RequestBodyAccessor`.
|
||||
|
||||
@code tool/templates/code/request_body_ext_third_party.dart
|
||||
|
||||
|
||||
|
||||
@gap 2
|
||||
## Shelf Run
|
||||
|
||||
Shelf Run is **zero-configuration** web-server initializer with **hot-reload** support.
|
||||
|
||||
@code tool/templates/code/shelf_run_intro.dart
|
||||
|
||||
It's important to use a dedicated `init` function, returning a [Shelf Handler](https://pub.dev/documentation/shelf/latest/shelf/Handler.html),
|
||||
for hot-reload to work properly.
|
||||
|
||||
To enable hot-reload you need either run your app with the IDE's **debug profile**, or
|
||||
enable vm-service from the command line:
|
||||
|
||||
```dart run --enable-vm-service my_app.dart```
|
||||
|
||||
Shelf Run uses a default configuration, that can be modified via **environment variables**:
|
||||
|
||||
| Environment variable | Default value | Description |
|
||||
| -------------------- | ------------- | ---------------------------------------- |
|
||||
| SHELF_PORT | 8080 | Port to bind the shelf application to |
|
||||
| SHELF_ADDRESS | localhost | Address to bind the shelf application to |
|
||||
| SHELF_HOTRELOAD | true | Enable hot-reload |
|
||||
|
||||
You can override the default values with optional parameters in the `shelfRun()` function.
|
||||
|
||||
@code tool/templates/code/shelf_run_override_default.dart
|
||||
|
||||
|
||||
@gap2
|
||||
## Examples
|
||||
|
||||
### Rest Service
|
||||
|
||||
Implementation of a CRUD, rest-like backend service. ([Full sources](/example/example_rest/))
|
||||
|
||||
|
||||
|
||||
**example_rest.dart**
|
||||
@code example/example_rest/bin/example_rest.dart
|
||||
|
||||
**person.dart**
|
||||
@code example/example_rest/bin/person.dart
|
Reference in New Issue
Block a user