feat: set up FFI
Signed-off-by: Sphericalkat <me@kat.bio>
This commit is contained in:
commit
a5b8aa83d6
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# https://dart.dev/guides/libraries/private-files
|
||||||
|
# Created by `dart pub`
|
||||||
|
.dart_tool/
|
||||||
|
|
||||||
|
# Avoid committing pubspec.lock for library packages; see
|
||||||
|
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
pubspec.lock
|
||||||
|
|
||||||
|
# FVM Version Cache
|
||||||
|
.fvm/
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"dart.flutterSdkPath": ".fvm/versions/stable"
|
||||||
|
}
|
3
CHANGELOG.md
Normal file
3
CHANGELOG.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Initial version.
|
39
README.md
Normal file
39
README.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<!--
|
||||||
|
This README describes the package. If you publish this package to pub.dev,
|
||||||
|
this README's contents appear on the landing page for your package.
|
||||||
|
|
||||||
|
For information about how to write a good package README, see the guide for
|
||||||
|
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
|
||||||
|
|
||||||
|
For general information about developing packages, see the Dart guide for
|
||||||
|
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
|
||||||
|
and the Flutter guide for
|
||||||
|
[developing packages and plugins](https://flutter.dev/developing-packages).
|
||||||
|
-->
|
||||||
|
|
||||||
|
TODO: Put a short description of the package here that helps potential users
|
||||||
|
know whether this package might be useful for them.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
TODO: List what your package can do. Maybe include images, gifs, or videos.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
TODO: List prerequisites and provide or point to information on how to
|
||||||
|
start using the package.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
TODO: Include short and useful examples for package users. Add longer examples
|
||||||
|
to `/example` folder.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
const like = 'sample';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional information
|
||||||
|
|
||||||
|
TODO: Tell users more about the package: where to find more information, how to
|
||||||
|
contribute to the package, how to file issues, what response they can expect
|
||||||
|
from the package authors, and more.
|
30
analysis_options.yaml
Normal file
30
analysis_options.yaml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# This file configures the static analysis results for your project (errors,
|
||||||
|
# warnings, and lints).
|
||||||
|
#
|
||||||
|
# This enables the 'recommended' set of lints from `package:lints`.
|
||||||
|
# This set helps identify many issues that may lead to problems when running
|
||||||
|
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||||
|
# style and format.
|
||||||
|
#
|
||||||
|
# If you want a smaller set of lints you can change this to specify
|
||||||
|
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||||
|
# (the recommended set includes the core lints).
|
||||||
|
# The core lints are also what is used by pub.dev for scoring packages.
|
||||||
|
|
||||||
|
include: package:lints/recommended.yaml
|
||||||
|
|
||||||
|
# Uncomment the following section to specify additional rules.
|
||||||
|
|
||||||
|
# linter:
|
||||||
|
# rules:
|
||||||
|
# - camel_case_types
|
||||||
|
|
||||||
|
# analyzer:
|
||||||
|
# exclude:
|
||||||
|
# - path/to/excluded/files/**
|
||||||
|
|
||||||
|
# For more information about the core and recommended set of lints, see
|
||||||
|
# https://dart.dev/go/core-lints
|
||||||
|
|
||||||
|
# For additional information about configuring this file, see
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
7
example/readability_example.dart
Normal file
7
example/readability_example.dart
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import 'package:readability/readability.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var parser = ReadabilityParser();
|
||||||
|
Article article = parser.parse('https://www.bbc.com/sport/football/articles/cl7y4z82z2do');
|
||||||
|
print(article.toString());
|
||||||
|
}
|
9
lib/readability.dart
Normal file
9
lib/readability.dart
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/// Support for doing something awesome.
|
||||||
|
///
|
||||||
|
/// More dartdocs go here.
|
||||||
|
library;
|
||||||
|
|
||||||
|
export 'src/readability_parser.dart';
|
||||||
|
export 'src/article.dart';
|
||||||
|
|
||||||
|
// TODO: Export any libraries intended for clients of this package.
|
33
lib/src/article.dart
Normal file
33
lib/src/article.dart
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
class Article {
|
||||||
|
final String? title;
|
||||||
|
final String? author;
|
||||||
|
final int length;
|
||||||
|
final String? excerpt;
|
||||||
|
final String? siteName;
|
||||||
|
final String? imageUrl;
|
||||||
|
final String? faviconUrl;
|
||||||
|
final String? content;
|
||||||
|
final String? textContent;
|
||||||
|
final String? language;
|
||||||
|
final String? publishedTime;
|
||||||
|
|
||||||
|
Article({
|
||||||
|
required this.title,
|
||||||
|
required this.author,
|
||||||
|
required this.length,
|
||||||
|
required this.excerpt,
|
||||||
|
required this.siteName,
|
||||||
|
required this.imageUrl,
|
||||||
|
required this.faviconUrl,
|
||||||
|
required this.content,
|
||||||
|
required this.textContent,
|
||||||
|
required this.language,
|
||||||
|
required this.publishedTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Article{title: $title,\n author: $author,\n length: $length,\n excerpt: $excerpt,\n siteName: $siteName,\n imageUrl: $imageUrl,\n faviconUrl: $faviconUrl,\n content: $content,\n textContent: $textContent,\n language: $language,\n publishedTime: $publishedTime}';
|
||||||
|
}
|
||||||
|
}
|
88
lib/src/readability_parser.dart
Normal file
88
lib/src/readability_parser.dart
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import 'dart:ffi' as ffi;
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'package:ffi/ffi.dart';
|
||||||
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
|
import 'package:readability/src/article.dart';
|
||||||
|
|
||||||
|
final class CArticle extends Struct {
|
||||||
|
external Pointer<Utf8> title;
|
||||||
|
external Pointer<Utf8> author;
|
||||||
|
@Int32()
|
||||||
|
external int length;
|
||||||
|
external Pointer<Utf8> excerpt;
|
||||||
|
external Pointer<Utf8> site_name;
|
||||||
|
external Pointer<Utf8> image_url;
|
||||||
|
external Pointer<Utf8> favicon_url;
|
||||||
|
external Pointer<Utf8> content;
|
||||||
|
external Pointer<Utf8> text_content;
|
||||||
|
external Pointer<Utf8> language;
|
||||||
|
external Pointer<Utf8> published_time;
|
||||||
|
external Pointer<Utf8> err;
|
||||||
|
|
||||||
|
@Int32()
|
||||||
|
external int success;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef ParseFunc = CArticle Function(Pointer<Utf8> url);
|
||||||
|
typedef ParseDart = CArticle Function(Pointer<Utf8> url);
|
||||||
|
|
||||||
|
typedef FreeArticleFunc = Void Function(CArticle article);
|
||||||
|
typedef FreeArticleDart = void Function(CArticle article);
|
||||||
|
|
||||||
|
class ReadabilityParser {
|
||||||
|
late final DynamicLibrary _lib;
|
||||||
|
late final ParseDart _parse;
|
||||||
|
late final FreeArticleDart _freeArticle;
|
||||||
|
|
||||||
|
ReadabilityParser() {
|
||||||
|
_lib = ffi.DynamicLibrary.open(_getDylibPath());
|
||||||
|
_parse = _lib.lookupFunction<ParseFunc, ParseDart>('Parse');
|
||||||
|
_freeArticle =
|
||||||
|
_lib.lookupFunction<FreeArticleFunc, FreeArticleDart>('FreeArticle');
|
||||||
|
}
|
||||||
|
|
||||||
|
Article parse(String url) {
|
||||||
|
// Convert the URL to a native string.
|
||||||
|
final urlPtr = url.toNativeUtf8();
|
||||||
|
|
||||||
|
// Call the native function.
|
||||||
|
final article = _parse(urlPtr);
|
||||||
|
|
||||||
|
// Free the memory allocated for the URL.
|
||||||
|
malloc.free(urlPtr);
|
||||||
|
|
||||||
|
|
||||||
|
// Convert the native article to a Dart article.
|
||||||
|
final articleDart = Article(
|
||||||
|
title: article.title == nullptr ? null : article.title.toDartString(),
|
||||||
|
author: article.author == nullptr ? null : article.author.toDartString(),
|
||||||
|
length: article.length,
|
||||||
|
excerpt: article.excerpt == nullptr ? null : article.excerpt.toDartString(),
|
||||||
|
siteName: article.site_name == nullptr ? null : article.site_name.toDartString(),
|
||||||
|
imageUrl: article.image_url == nullptr ? null : article.image_url.toDartString(),
|
||||||
|
faviconUrl: article.favicon_url == nullptr ? null : article.favicon_url.toDartString(),
|
||||||
|
content: article.content == nullptr ? null : article.content.toDartString(),
|
||||||
|
textContent: article.text_content == nullptr ? null : article.text_content.toDartString(),
|
||||||
|
language: article.language == nullptr ? null : article.language.toDartString(),
|
||||||
|
publishedTime: article.published_time == nullptr ? null : article.published_time.toDartString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Free the memory allocated for the article.
|
||||||
|
_freeArticle(article);
|
||||||
|
|
||||||
|
return articleDart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getDylibPath() {
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
return 'native/windows/x64/readability.dll';
|
||||||
|
} else if (Platform.isMacOS) {
|
||||||
|
return 'native/macos/x64/readability.dylib';
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
return 'native/linux/x86-64/libreadability.so';
|
||||||
|
} else {
|
||||||
|
throw UnsupportedError('This platform is not supported.');
|
||||||
|
}
|
||||||
|
}
|
103
native/linux/x86-64/libreadability.h
Normal file
103
native/linux/x86-64/libreadability.h
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
||||||
|
|
||||||
|
/* package command-line-arguments */
|
||||||
|
|
||||||
|
|
||||||
|
#line 1 "cgo-builtin-export-prolog"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifndef GO_CGO_EXPORT_PROLOGUE_H
|
||||||
|
#define GO_CGO_EXPORT_PROLOGUE_H
|
||||||
|
|
||||||
|
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||||
|
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Start of preamble from import "C" comments. */
|
||||||
|
|
||||||
|
|
||||||
|
#line 3 "main.go"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* title;
|
||||||
|
char* author;
|
||||||
|
int length;
|
||||||
|
char* excerpt;
|
||||||
|
char* site_name;
|
||||||
|
char* image_url;
|
||||||
|
char* favicon_url;
|
||||||
|
char* content; // HTML content
|
||||||
|
char* text_content; // text content
|
||||||
|
char* language;
|
||||||
|
char* published_time;
|
||||||
|
char* err;
|
||||||
|
int success;
|
||||||
|
} CArticle;
|
||||||
|
|
||||||
|
#line 1 "cgo-generated-wrapper"
|
||||||
|
|
||||||
|
|
||||||
|
/* End of preamble from import "C" comments. */
|
||||||
|
|
||||||
|
|
||||||
|
/* Start of boilerplate cgo prologue. */
|
||||||
|
#line 1 "cgo-gcc-export-header-prolog"
|
||||||
|
|
||||||
|
#ifndef GO_CGO_PROLOGUE_H
|
||||||
|
#define GO_CGO_PROLOGUE_H
|
||||||
|
|
||||||
|
typedef signed char GoInt8;
|
||||||
|
typedef unsigned char GoUint8;
|
||||||
|
typedef short GoInt16;
|
||||||
|
typedef unsigned short GoUint16;
|
||||||
|
typedef int GoInt32;
|
||||||
|
typedef unsigned int GoUint32;
|
||||||
|
typedef long long GoInt64;
|
||||||
|
typedef unsigned long long GoUint64;
|
||||||
|
typedef GoInt64 GoInt;
|
||||||
|
typedef GoUint64 GoUint;
|
||||||
|
typedef size_t GoUintptr;
|
||||||
|
typedef float GoFloat32;
|
||||||
|
typedef double GoFloat64;
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <complex.h>
|
||||||
|
typedef _Fcomplex GoComplex64;
|
||||||
|
typedef _Dcomplex GoComplex128;
|
||||||
|
#else
|
||||||
|
typedef float _Complex GoComplex64;
|
||||||
|
typedef double _Complex GoComplex128;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
static assertion to make sure the file is being used on architecture
|
||||||
|
at least with matching size of GoInt.
|
||||||
|
*/
|
||||||
|
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
|
||||||
|
|
||||||
|
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||||
|
typedef _GoString_ GoString;
|
||||||
|
#endif
|
||||||
|
typedef void *GoMap;
|
||||||
|
typedef void *GoChan;
|
||||||
|
typedef struct { void *t; void *v; } GoInterface;
|
||||||
|
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* End of boilerplate cgo prologue. */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern CArticle Parse(char* url);
|
||||||
|
extern void FreeArticle(CArticle article);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
BIN
native/linux/x86-64/libreadability.so
Normal file
BIN
native/linux/x86-64/libreadability.so
Normal file
Binary file not shown.
16
pubspec.yaml
Normal file
16
pubspec.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
name: readability
|
||||||
|
description: A starting point for Dart libraries or applications.
|
||||||
|
version: 1.0.0
|
||||||
|
# repository: https://github.com/my_org/my_repo
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ^3.4.3
|
||||||
|
|
||||||
|
# Add regular dependencies here.
|
||||||
|
dependencies:
|
||||||
|
ffi: ^2.1.2
|
||||||
|
# path: ^1.8.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
lints: ^3.0.0
|
||||||
|
test: ^1.24.0
|
17
test/readability_test.dart
Normal file
17
test/readability_test.dart
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:readability/readability.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('A group of tests', () {
|
||||||
|
final ReadabilityParser parser = ReadabilityParser();
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
// Additional setup goes here.
|
||||||
|
});
|
||||||
|
|
||||||
|
test('First Test', () {
|
||||||
|
final Article article = parser.parse('https://www.bbc.com/sport/football/articles/cl7y4z82z2do');
|
||||||
|
expect(article.title, isNotNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user