Skip to content

Commit

Permalink
Add example RSS reader.
Browse files Browse the repository at this point in the history
  • Loading branch information
renggli committed Jul 3, 2023
1 parent 4c10d7a commit e15a710
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 0 deletions.
9 changes: 9 additions & 0 deletions example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ dart example/ip_api.dart --help
dart example/ip_api.dart --fields=query,city,country
```

### feeds

Simple RSS feed reader that manages a list of feeds in `feeds.xml` and that can retrieve and print them to the command line.

```bash
dart example/feeds.dart --help
dart example/feeds.dart read
```

### xml_flatten

This example contains a command-line application that flattens an XML documents from the file-system into a list of events that are printed to the console. For example:
Expand Down
137 changes: 137 additions & 0 deletions example/feeds.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import 'dart:convert';
import 'dart:io';

import 'package:args/args.dart';
import 'package:args/command_runner.dart';
import 'package:xml/xml.dart';

T? getArgument<T>(ArgResults? results, String name) {
if (results == null) return null;
return results[name] as T?;
}

abstract class RssCommand extends Command<void> {
String get feedsName =>
getArgument(globalResults, 'feeds') ?? 'example/feeds.xml';

File get feedsFile => File(feedsName);

Future<XmlDocument> readFeeds() async {
try {
final input = await feedsFile.readAsString();
return XmlDocument.parse(input);
} catch (exception) {
stdout.writeln('Unable to read "$feedsName" due to $exception');
exit(1);
}
}

Future<void> writeFeeds(XmlDocument document) async {
await feedsFile.writeAsString(document.toXmlString(pretty: true));
}
}

class AddCommand extends RssCommand {
@override
String get name => 'add';

@override
String get description => 'Adds a feed URL.';

@override
Future<void> run() async {
final document = await readFeeds();
for (final feed in argResults!.rest) {
final builder = XmlBuilder()..element('url', nest: feed);
document.rootElement.children.add(builder.buildFragment());
}
writeFeeds(document);
}
}

class RemoveCommand extends RssCommand {
@override
String get name => 'rm';

@override
String get description => 'Removes a feed URL.';

@override
Future<void> run() async {
final urls = {...argResults!.rest};
final document = await readFeeds();
for (final element in document.findAllElements('url').toList()) {
if (urls.contains(element.innerText.trim())) {
element.remove();
}
}
writeFeeds(document);
}
}

class ListCommand extends RssCommand {
@override
String get name => 'list';

@override
String get description => 'Lists the feed URLs.';

@override
Future<void> run() async {
final document = await readFeeds();
for (final element in document.findAllElements('url')) {
stdout.writeln(element.innerText.trim());
}
}
}

class ReadCommand extends RssCommand {
@override
String get name => 'read';

@override
String get description => 'Reads the feeds.';

@override
Future<void> run() async {
final feeds = await readFeeds();
final documents = feeds
.findAllElements('url')
.map((element) => _get(Uri.parse(element.innerText.trim())));
await Stream.fromFutures(documents).listen((document) {
for (final item in document.findAllElements('item')) {
final title = item.findElements('title').firstOrNull?.innerText;
final link = item.findElements('link').firstOrNull?.innerText;
if (title != null && link != null) {
stdout.writeln('$title ($link)');
}
}
}, onError: (error) {
stderr.writeln(error.toString());
}).asFuture<void>();
}

final httpClient = HttpClient();

Future<XmlDocument> _get(Uri uri) async {
final request = await httpClient.getUrl(uri);
final response = await request.close();
final input = await response.transform(utf8.decoder).join();
return XmlDocument.parse(input);
}
}

final runner = CommandRunner<void>('feeds', 'Manages and reads RSS feeds.')
..addCommand(AddCommand())
..addCommand(RemoveCommand())
..addCommand(ListCommand())
..addCommand(ReadCommand());

Future<void> main(List<String> arguments) async {
runner.argParser.addOption('feeds', help: 'path to current feeds');

await runner.run(arguments).catchError((Object error) {
stdout.writeln(error);
exit(1);
}, test: (error) => error is UsageException);
}
6 changes: 6 additions & 0 deletions example/feeds.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0"?>
<feeds>
<url>https://www.lukas-renggli.ch/blog?view=PBEntriesRssView</url>
<url>https://flowingdata.com/feed</url>
<url>https://xkcd.com/rss.xml</url>
</feeds>

0 comments on commit e15a710

Please sign in to comment.