Merge pull request #142 from althonos/doctests

Add tests for the guide book using `docmatic`
This commit is contained in:
Martin Larralde 2018-05-02 10:43:03 +02:00 committed by GitHub
commit b64977fa8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 301 additions and 109 deletions

View File

@ -25,6 +25,9 @@ spin = "0.4.6"
num-traits = "0.2"
pyo3cls = { path = "pyo3cls", version = "^0.2.1" }
[dev-dependencies]
docmatic = "^0.1.2"
[build-dependencies]
regex = "0.2"
version_check = "0.1"

View File

@ -13,12 +13,15 @@ extern crate pyo3;
## Define new class
To define python custom class, rust struct needs to be annotated with `#[py::class]` attribute.
To define python custom class, rust struct needs to be annotated with `#[class]` attribute.
```rust
use pyo3::prelude::*;
# #![feature(proc_macro, specialization, const_fn)]
# extern crate pyo3;
# use pyo3::prelude::*;
use pyo3::py::class;
#[py::class]
#[class]
struct MyClass {
num: i32,
debug: bool,
@ -44,11 +47,11 @@ are generated only if struct contains `PyToken` attribute.
TODO - continue
## py::class macro
## class macro
Python class generation is powered by [Procedural Macros](https://doc.rust-lang.org/book/first-edition/procedural-macros.html).
To define python custom class, rust struct needs to be annotated with `#[py::class]` attribute.
`py::class` macro accepts following parameters:
To define python custom class, rust struct needs to be annotated with `#[class]` attribute.
`class` macro accepts following parameters:
* `name=XXX` - customize class name visible to python code. By default struct name is used as
a class name.
@ -72,14 +75,29 @@ To declare constructor, you need to define class method and annotate it with `#[
attribute. Only python `__new__` method can be specified, `__init__` is not available.
```rust
#[py::methods]
# #![feature(proc_macro, specialization, const_fn)]
#
# extern crate pyo3;
# use pyo3::prelude::*;
# use pyo3::py::class;
#
# #[class]
# struct MyClass {
# num: i32,
# debug: bool,
# token: PyToken,
# }
#
use pyo3::py::methods;
#[methods]
impl MyClass {
#[new]
fn __new__(obj: &PyRawObject, ...) -> PyResult<()> {
fn __new__(obj: &PyRawObject, num: Option<i32>) -> PyResult<()> {
obj.init(|token| {
MyClass {
num: 10,
num: num.unwrap_or(10),
debug: false,
token: token
}
@ -93,7 +111,7 @@ Some rules of `new` method
* If no method marked with `#[new]` is declared, object instances can only be created
from Rust, but not from Python.
* The first parameter is the raw object, custom `new` method must initialize object
with value of struct using `init` method. Type of the object may be the type object of
with value of struct using `init` method. Type of the object may be the type object of
a derived class declared in Python.
* The first parameter implicitly has type `&PyRawObject`.
* For details on `parameter-list`, see the documentation of `Method arguments` section.
@ -104,42 +122,48 @@ Some rules of `new` method
## Inheritance
By default `PyObject` is used as default base class. To override default base class
`base` parameter for `py::class` needs to be used. Value is full path to base class.
`base` parameter for `class` needs to be used. Value is full path to base class.
`__new__` method accepts `PyRawObject` object. `obj` instance must be initialized
with value of custom class struct. Subclass must call parent's `__new__` method.
```rust
#[py::class]
# #![feature(proc_macro, specialization, const_fn)]
# extern crate pyo3;
# use pyo3::prelude::*;
# use pyo3::py::*;
#
#[class]
struct BaseClass {
val1: usize
val1: usize,
token: PyToken,
}
#[py::methods]
#[methods]
impl BaseClass {
#[new]
fn __new__(obj: &PyRawObject) -> PyResult<()> {
obj.init(|t| BaseClass{val1: 10})
obj.init(|token| BaseClass{val1: 10, token})
}
pub fn method(&self) -> PyResult<() {
pub fn method(&self) -> PyResult<()> {
Ok(())
}
}
#[py::class(base=BaseClass)]
#[class(base=BaseClass)]
struct SubClass {
val2: usize
val2: usize,
token: PyToken,
}
#[py::methods]
#[methods]
impl SubClass {
#[new]
fn __new__(obj: &PyRawObject) -> PyResult<()> {
obj.init(|t| SubClass{val2: 10})
obj.init(|token| SubClass{val2: 10, token});
BaseClass::__new__(obj)
}
fn method2(&self) -> PyResult<()> {
self.get_base().method()
}
@ -153,11 +177,21 @@ base class.
## Object properties
Descriptor methods can be defined in
`#[py::methods]` `impl` block only and has to be annotated with `#[getter]` or `[setter]`
`#[methods]` `impl` block only and has to be annotated with `#[getter]` or `[setter]`
attributes. i.e.
```rust
#[py::methods]
# #![feature(proc_macro, specialization, const_fn)]
# extern crate pyo3;
# use pyo3::prelude::*;
# use pyo3::py::*;
# #[class]
# struct MyClass {
# num: i32,
# token: PyToken,
# }
#
#[methods]
impl MyClass {
#[getter]
@ -175,7 +209,17 @@ Descriptor name becomes function name with prefix removed. This is useful in cas
rust's special keywords like `type`.
```rust
#[py::methods]
# #![feature(proc_macro, specialization, const_fn)]
# extern crate pyo3;
# use pyo3::prelude::*;
# use pyo3::py::*;
# #[class]
# struct MyClass {
# num: i32,
# token: PyToken,
# }
#
#[methods]
impl MyClass {
#[getter]
@ -185,7 +229,7 @@ impl MyClass {
#[setter]
fn set_num(&mut self, value: i32) -> PyResult<()> {
self.num = value
self.num = value;
Ok(())
}
}
@ -197,7 +241,17 @@ Also both `#[getter]` and `#[setter]` attributes accepts one parameter.
If parameter is specified, it is used and property name. i.e.
```rust
#[py::methods]
# #![feature(proc_macro, specialization, const_fn)]
# extern crate pyo3;
# use pyo3::prelude::*;
# use pyo3::py::*;
# #[class]
# struct MyClass {
# num: i32,
# token: PyToken,
# }
#
#[methods]
impl MyClass {
#[getter(number)]
@ -207,7 +261,7 @@ impl MyClass {
#[setter(number)]
fn set_num(&mut self, value: i32) -> PyResult<()> {
self.num = value
self.num = value;
Ok(())
}
}
@ -218,7 +272,11 @@ In this case property `number` is defined. And it is available from python code
For simple cases you can also define getters and setters in your Rust struct field definition, for example:
```rust
#[py:class]
# #![feature(proc_macro, specialization, const_fn)]
# extern crate pyo3;
# use pyo3::prelude::*;
# use pyo3::py::*;
#[class]
struct MyClass {
#[prop(get, set)]
num: i32
@ -230,12 +288,22 @@ Then it is available from Python code as `self.num`.
## Instance methods
To define python compatible method, `impl` block for struct has to be annotated
with `#[py::methods]` attribute. `pyo3` library generates python compatible
with `#[methods]` attribute. `pyo3` library generates python compatible
wrappers for all functions in this block with some variations, like descriptors,
class method static methods, etc.
```rust
#[py::methods]
# #![feature(proc_macro, specialization, const_fn)]
# extern crate pyo3;
# use pyo3::prelude::*;
# use pyo3::py::*;
# #[class]
# struct MyClass {
# num: i32,
# token: PyToken,
# }
#
#[methods]
impl MyClass {
fn method1(&self) -> PyResult<i32> {
@ -243,7 +311,7 @@ impl MyClass {
}
fn set_method(&mut self, value: i32) -> PyResult<()> {
self.num = value
self.num = value;
Ok(())
}
}
@ -256,9 +324,19 @@ The return type must be `PyResult<T>` for some `T` that implements `IntoPyObject
get injected by method wrapper. i.e
```rust
#[py::methods]
impl MyClass {
# #![feature(proc_macro, specialization, const_fn)]
# extern crate pyo3;
# use pyo3::prelude::*;
# use pyo3::py::*;
# #[class]
# struct MyClass {
# num: i32,
# debug: bool,
# token: PyToken,
# }
#[methods]
impl MyClass {
fn method2(&self, py: Python) -> PyResult<i32> {
Ok(10)
}
@ -273,9 +351,19 @@ To specify class method for custom class, method needs to be annotated
with`#[classmethod]` attribute.
```rust
#[py::methods]
impl MyClass {
# #![feature(proc_macro, specialization, const_fn)]
# extern crate pyo3;
# use pyo3::prelude::*;
# use pyo3::py::*;
# #[class]
# struct MyClass {
# num: i32,
# debug: bool,
# token: PyToken,
# }
#[methods]
impl MyClass {
#[classmethod]
fn cls_method(cls: &PyType) -> PyResult<i32> {
Ok(10)
@ -298,9 +386,19 @@ with `#[staticmethod]` attribute. The return type must be `PyResult<T>`
for some `T` that implements `IntoPyObject`.
```rust
#[py::methods]
impl MyClass {
# #![feature(proc_macro, specialization, const_fn)]
# extern crate pyo3;
# use pyo3::prelude::*;
# use pyo3::py::*;
# #[class]
# struct MyClass {
# num: i32,
# debug: bool,
# token: PyToken,
# }
#[methods]
impl MyClass {
#[staticmethod]
fn static_method(param1: i32, param2: &str) -> PyResult<i32> {
Ok(10)
@ -314,9 +412,19 @@ To specify custom `__call__` method for custom class, call method needs to be an
with `#[call]` attribute. Arguments of the method are specified same as for instance method.
```rust
#[py::methods]
impl MyClass {
# #![feature(proc_macro, specialization, const_fn)]
# extern crate pyo3;
# use pyo3::prelude::*;
# use pyo3::py::*;
# #[class]
# struct MyClass {
# num: i32,
# debug: bool,
# token: PyToken,
# }
#[methods]
impl MyClass {
#[call]
#[args(args="*")]
fn __call__(&self, args: &PyTuple) -> PyResult<i32> {
@ -350,9 +458,19 @@ Each parameter could one of following type:
Example:
```rust
#[py::methods]
# #![feature(proc_macro, specialization, const_fn)]
# extern crate pyo3;
# use pyo3::prelude::*;
# use pyo3::py::*;
# #[class]
# struct MyClass {
# num: i32,
# debug: bool,
# token: PyToken,
# }
#
#[methods]
impl MyClass {
#[args(arg1=true, args="*", arg2=10, kwargs="**")]
fn method(&self, arg1: bool, args: &PyTuple, arg2: i32, kwargs: Option<&PyDict>) -> PyResult<i32> {
Ok(1)
@ -367,7 +485,7 @@ Python object model defines several protocols for different object behavior,
like sequence, mapping or number protocols. pyo3 library defines separate trait for each
of them. To provide specific python object behavior you need to implement specific trait
for your struct. Important note, each protocol implementation block has to be annotated
with `#[py::proto]` attribute.
with `#[proto]` attribute.
### Basic object customization
@ -444,17 +562,19 @@ These correspond to the slots `tp_traverse` and `tp_clear` in the Python C API.
as every cycle must contain at least one mutable reference.
Example:
```rust
#![feature(proc_macro, specialization)]
#![feature(proc_macro, specialization, const_fn)]
extern crate pyo3;
use pyo3::{py, PyObject, PyGCProtocol, PyVisit, PyTraverseError};
use pyo3::prelude::*;
use pyo3::py::*;
#[py::class]
#[class]
struct ClassWithGCSupport {
obj: Option<PyObject>,
token: PyToken,
}
#[py::proto]
#[proto]
impl PyGCProtocol for ClassWithGCSupport {
fn __traverse__(&self, visit: PyVisit) -> Result<(), PyTraverseError> {
if let Some(ref obj) = self.obj {
@ -472,10 +592,10 @@ impl PyGCProtocol for ClassWithGCSupport {
}
```
Special protocol trait implementation has to be annotated with `#[py::proto]` attribute.
Special protocol trait implementation has to be annotated with `#[proto]` attribute.
It is also possible to enable gc for custom class using `gc` parameter for `py::class` annotation.
i.e. `#[py::class(gc)]`. In that case instances of custom class participate in python garbage
It is also possible to enable gc for custom class using `gc` parameter for `class` annotation.
i.e. `#[class(gc)]`. In that case instances of custom class participate in python garbage
collector, and it is possible to track them with `gc` module methods.
### Iterator Types
@ -490,18 +610,20 @@ It includes two methods `__iter__` and `__next__`:
Example:
```rust
#![feature(proc_macro, specialization)]
#![feature(proc_macro, specialization, const_fn)]
extern crate pyo3;
use pyo3::{py, PyObject, PyResult, PyIterProtocol};
use pyo3::prelude::*;
use pyo3::py::*;
#[py::class]
#[class]
struct MyIterator {
iter: Box<Iterator<Item=PyObject> + Send>
iter: Box<Iterator<Item=PyObject> + Send>,
token: PyToken,
}
#[py::proto]
impl PyIterProtocol {
#[proto]
impl PyIterProtocol for MyIterator {
fn __iter__(&mut self) -> PyResult<PyObject> {
Ok(self.into())

View File

@ -4,7 +4,7 @@
## `ToPyObject` and `IntoPyObject` trait
[`ToPyObject`][ToPyObject] trait is a conversion trait that allows various objects to be converted into [`PyObject`][PyObject]. [`IntoPyObject`][IntoPyObject] serves the same purpose except it consumes `self`.
[`ToPyObject`] trait is a conversion trait that allows various objects to be converted into [`PyObject`][PyObject]. [`IntoPyObject`][IntoPyObject] serves the same purpose except it consumes `self`.
## `IntoPyTuple` trait
@ -14,7 +14,6 @@ For example, [`IntoPyTuple`][IntoPyTuple] trait is implemented for `()` so that
```rust
extern crate pyo3;
use pyo3::{Python, IntoPyTuple};
fn main() {
@ -39,66 +38,89 @@ Both methods accept `args` and `kwargs` arguments. `args` argument is generate o
[`IntoPyTuple`][IntoPyTuple] trait. So args could be `PyTuple` instance or
rust tuple with up to 10 elements. Or `NoArgs` object which represents empty tuple object.
```rust,ignore
```rust
extern crate pyo3;
use pyo3::prelude::*;
# struct SomeObject;
# impl SomeObject {
# fn new(py: Python) -> PyObject {
# pyo3::PyDict::new(py).to_object(py)
# }
# }
#
fn main() {
# let arg1 = "arg1";
# let arg2 = "arg2";
# let arg3 = "arg3";
let gil = Python::acquire_gil();
let py = gil.python();
let obj = SomeObject::new();
let obj = SomeObject::new(py);
// call object without empty arguments
obj.call(NoArgs, NoArg);
obj.call0(py);
// call object with PyTuple
let args = PyTuple::new(py, &[arg1, arg2, arg3]);
obj.call(args, NoArg);
obj.call1(py, args);
// pass arguments as rust tuple
let args = (arg1, arg2, arg3);
obj.call(args, NoArg);
obj.call1(py, args);
}
```
`kwargs` argument is generate over
[`IntoPyDictPointer`][IntoPyDictPointer] trait. `HashMap` or `BTreeMap` could be used as
keyword arguments. rust tuple with up to 10 elements where each element is tuple with size 2
could be used as kwargs as well. Or `NoArgs` object can be used to indicate that
could be used as kwargs as well. Or `NoArgs` object can be used to indicate that
no keywords arguments are provided.
```rust,ignore
```rust
extern crate pyo3;
use std::collections::HashMap;
use pyo3::prelude::*;
# struct SomeObject;
# impl SomeObject {
# fn new(py: Python) -> PyObject {
# pyo3::PyDict::new(py).to_object(py)
# }
# }
fn main() {
# let key1 = "key1";
# let val1 = 1;
# let key2 = "key2";
# let val2 = 2;
let gil = Python::acquire_gil();
let py = gil.python();
let obj = SomeObject::new();
let obj = SomeObject::new(py);
// call object with PyDict
let kwargs = PyDict::new(py);
kwargs.set_item(key, value);
obj.call(NoArg, kwargs);
kwargs.set_item(key1, val1);
obj.call(py, NoArgs, kwargs);
// pass arguments as rust tuple
let kwargs = ((key1, val1), (key2, val2), (key3, val3));
obj.call(args, kwargs);
let kwargs = ((key1, val1), (key2, val2));
obj.call(py, NoArgs, kwargs);
// pass arguments as HashMap
let mut kwargs = HashMap::<i32, i32>::new();
kwargs.insert(1, 1);
obj.call(args, kwargs);
let mut kwargs = HashMap::<&str, i32>::new();
kwargs.insert(key1, 1);
obj.call(py, NoArgs, kwargs);
}
```
TODO
[ToPyObject]: https://pyo3.github.io/pyo3/pyo3/trait.ToPyObject.html
[`ToPyObject`]: https://pyo3.github.io/pyo3/pyo3/trait.ToPyObject.html
[IntoPyObject]: https://pyo3.github.io/pyo3/pyo3/trait.IntoPyObject.html
[PyObject]: https://pyo3.github.io/pyo3/pyo3/struct.PyObject.html
[IntoPyTuple]: https://pyo3.github.io/pyo3/pyo3/trait.IntoPyTuple.html

View File

@ -5,6 +5,8 @@
You can use the `py_exception!` macro to define a new exception type:
```rust
#[macro_use] extern crate pyo3;
py_exception!(module, MyError);
```
@ -25,7 +27,7 @@ fn main() {
let py = gil.python();
let ctx = PyDict::new(py);
ctx.set_item(py, "CustomError", py.get_type::<CustomError>()).unwrap();
ctx.set_item("CustomError", py.get_type::<CustomError>()).unwrap();
py.run("assert str(CustomError) == \"<class 'mymodule.CustomError'>\"", None, Some(&ctx)).unwrap();
py.run("assert CustomError('oops').args == ('oops',)", None, Some(&ctx)).unwrap();
@ -52,7 +54,7 @@ fn main() {
If you already have a Python exception instance, you can simply call [`PyErr::from_instance()`](https://pyo3.github.io/pyo3/pyo3/struct.PyErr.html#method.from_instance).
```rust
```rust,ignore
PyErr::from_instance(py, err).restore(py);
```
@ -62,7 +64,9 @@ has corresponding rust type, exceptions defined by `py_exception!` and `import_e
have rust type as well.
```rust
# extern crate pyo3;
# use pyo3::prelude::*;
# fn check_for_error() -> bool {false}
fn my_func(arg: PyObject) -> PyResult<()> {
if check_for_error() {
Err(exc::ValueError::new("argument is wrong"))
@ -70,7 +74,6 @@ fn my_func(arg: PyObject) -> PyResult<()> {
Ok(())
}
}
```
## Check exception type
@ -86,10 +89,10 @@ use pyo3::{Python, PyBool, PyList};
fn main() {
let gil = Python::acquire_gil();
let py = gil.python();
assert!(py.is_instance::<PyBool>(py.True().as_ref()).unwrap());
assert!(py.is_instance::<PyBool, _>(PyBool::new(py, true)).unwrap());
let list = PyList::new(py, &[1, 2, 3, 4]);
assert!(!py.is_instance::<PyBool>(list.as_ref()).unwrap());
assert!(py.is_instance::<PyList>(list.as_ref()).unwrap());
assert!(!py.is_instance::<PyBool, _>(list.as_ref()).unwrap());
assert!(py.is_instance::<PyList, _>(list.as_ref()).unwrap());
}
```
@ -98,7 +101,14 @@ fn main() {
To check the type of an exception, you can simply do:
```rust
let ret = py.is_instance::<exc::TypeError>(&err.instance(py)).expect("Error calling is_instance");
# extern crate pyo3;
# use pyo3::prelude::*;
# fn main() {
# let gil = Python::acquire_gil();
# let py = gil.python();
# let err = exc::TypeError::new(NoArgs);
err.is_instance::<exc::TypeError>(py);
# }
```
## Handle Rust Error
@ -120,10 +130,12 @@ more complex arguments are required [`PyErrArgument`](https://pyo3.github.io/pyo
trait can be implemented. In that case actual exception arguments creation get delayed
until `Python` object is available.
```rust
use std;
```rust,ignore
#![feature(proc_macro, specialization)]
extern crate pyo3;
use std::net::TcpListener;
use pyo3::{PyErr, PyResult, ToPyErr, exc};
use pyo3::{PyErr, PyResult, exc};
impl std::convert::From<std::io::Error> for PyErr {
fn from(err: std::io::Error) -> PyErr {
@ -133,7 +145,6 @@ impl std::convert::From<std::io::Error> for PyErr {
fn connect(s: String) -> PyResult<bool> {
TcpListener::bind("127.0.0.1:80")?;
Ok(true)
}
```
@ -144,7 +155,8 @@ The code snippet above will raise `OSError` in Python if `TcpListener::bind()` r
types so `try!` macro or `?` operator can be used.
```rust
use pyo3::*;
# extern crate pyo3;
use pyo3::prelude::*;
fn parse_int(s: String) -> PyResult<usize> {
Ok(s.parse::<usize>()?)
@ -156,21 +168,26 @@ The code snippet above will raise `ValueError` in Python if `String::parse()` re
## Using exceptions defined in python code
It is possible to use exception defined in python code as native rust types.
It is possible to use exception defined in python code as native rust types.
`import_exception!` macro allows to import specific exception class and defined zst type
for that exception.
```rust
use pyo3::{PyErr, PyResult, exc};
#[macro_use] extern crate pyo3;
use pyo3::prelude::*;
import_exception!(asyncio, CancelledError)
import_exception!(io, UnsupportedOperation);
fn cancel(fut: PyFuture) -> PyResult<()> {
if fut.cancelled() {
Err(CancelledError.into())
}
fn tell(file: PyObject) -> PyResult<u64> {
use pyo3::exc::*;
Ok(())
let gil = Python::acquire_gil();
let py = gil.python();
match file.call_method0(py, "tell") {
Err(_) => Err(UnsupportedOperation::new("not supported: tell")),
Ok(x) => x.extract::<u64>(py),
}
}
```

View File

@ -40,7 +40,7 @@ fn init_mod(py: Python, m: &PyModule) -> PyResult<()> {
// Note that the `#[pyfn()]` annotation automatically converts the arguments from
// Python objects to Rust values; and the Rust return value back into a Python object.
#[pyfn(m, "sum_as_string")]
fn sum_as_string_py(_: Python, a:i64, b:i64) -> PyResult<String> {
fn sum_as_string_py(_py: Python, a:i64, b:i64) -> PyResult<String> {
let out = sum_as_string(a, b);
Ok(out)
}

View File

@ -29,7 +29,7 @@ Example program displaying the value of `sys.version`:
```rust
extern crate pyo3;
use pyo3::{Python, PyDict, PyResult};
use pyo3::prelude::*;
fn main() {
let gil = Python::acquire_gil();
@ -76,7 +76,7 @@ features = ["extension-module"]
#![feature(proc_macro, specialization)]
extern crate pyo3;
use pyo3::{py, PyResult, Python, PyModule};
use pyo3::prelude::*;
use pyo3::py::modinit as pymodinit;
@ -90,7 +90,7 @@ fn init_mod(py: Python, m: &PyModule) -> PyResult<()> {
// pyo3 aware function. All of our python interface could be declared in a separate module.
// Note that the `#[pyfn()]` annotation automatically converts the arguments from
// Python objects to Rust values; and the Rust return value back into a Python object.
fn sum_as_string_py(_: Python, a:i64, b:i64) -> PyResult<String> {
fn sum_as_string_py(_py: Python, a:i64, b:i64) -> PyResult<String> {
let out = sum_as_string(a, b);
Ok(out)
}

View File

@ -8,7 +8,7 @@ use ffi;
static NO_PY_METHODS: &'static [PyMethodDefType] = &[];
/// `PyMethodDefType` represents different types of python callable objects.
/// It is used by `#[py::methods]` and `#[py::proto]` annotations.
/// It is used by `#[pymethods]` and `#[pyproto]` annotations.
#[derive(Debug)]
pub enum PyMethodDefType {
/// Represents class `__new__` method

View File

@ -103,12 +103,15 @@ impl<'a, T: ?Sized> PyTypeInfo for &'a T where T: PyTypeInfo {
///
/// Example of custom class implementation with `__new__` method:
/// ```rust,ignore
/// #[py::class]
/// use pyo3::py::class as pyclass;
/// use pyo3::py::methods as pymethods;
///
/// #[pyclass]
/// struct MyClass {
/// token: PyToken
/// }
///
/// #[py::methods]
/// #[pymethods]
/// impl MyClass {
/// #[new]
/// fn __new__(obj: &PyRawObject) -> PyResult<()> {

25
tests/test_doc.rs Normal file
View File

@ -0,0 +1,25 @@
extern crate docmatic;
use std::path::{Path, PathBuf};
use std::default::Default;
fn assert_file<P: AsRef<Path>>(path: P) {
let mut doc = docmatic::Assert::default();
if cfg!(windows) {
doc.library_path(option_env!("PYTHON").map(|py| PathBuf::from(py).join("libs")).unwrap());
}
doc.test_file(path.as_ref())
}
#[test]
fn test_guide() {
let guide_path = PathBuf::from("guide").join("src");
for entry in guide_path.read_dir().unwrap() {
assert_file(entry.unwrap().path())
}
}
#[test]
fn test_readme() {
assert_file("README.md")
}