-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
152 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |