diff --git a/.github/workflows/conventional-prs.yml b/.github/workflows/conventional-prs.yml new file mode 100644 index 0000000..d926109 --- /dev/null +++ b/.github/workflows/conventional-prs.yml @@ -0,0 +1,16 @@ +name: PR +on: + pull_request_target: + types: + - opened + - reopened + - edited + - synchronize + +jobs: + title-format: + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v3.4.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..90e210d --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,37 @@ +on: + push: + branches: + - master + +name: release-please + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + + - uses: GoogleCloudPlatform/release-please-action@v2 + id: release + with: + release-type: rust + package-name: bio-types + + - uses: actions/checkout@v2 + if: ${{ steps.release.outputs.release_created }} + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + if: ${{ steps.release.outputs.release_created }} + with: + toolchain: stable + override: true + + - uses: Swatinem/rust-cache@v1.3.0 + if: ${{ steps.release.outputs.release_created }} + + - name: Publish crate + if: ${{ steps.release.outputs.release_created }} + uses: actions-rs/cargo@v1 + with: + command: publish + args: --token ${{ secrets.CRATES_IO_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..8d6daa3 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,112 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + Formatting: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + components: rustfmt + + - uses: Swatinem/rust-cache@v1.3.0 + + - name: Check format + run: cargo fmt -- --check + + Linting: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + components: clippy + + - uses: Swatinem/rust-cache@v1.3.0 + + - name: Lint with clippy + uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + Testing: + needs: Formatting + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + build: [linux, windows, macos] + include: + - build: macos + os: macos-latest + rust: stable + - build: windows + os: windows-latest + rust: stable + - build: linux + os: ubuntu-latest + rust: stable + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install ${{ matrix.rust }} toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + + - uses: Swatinem/rust-cache@v1.3.0 + + - name: Run tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --all --no-fail-fast + + Coverage: + needs: Formatting + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install nightly toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - uses: Swatinem/rust-cache@v1.3.0 + + - name: Install cargo-tarpaulin + uses: actions-rs/install@v0.1 + with: + crate: cargo-tarpaulin + version: latest + use-tool-cache: true + + - name: Coverage with tarpaulin + run: cargo tarpaulin --all --all-features --timeout 600 --out Lcov -- --test-threads 1 + + - name: Upload coverage + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: ./lcov.info diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c2bdb42..0000000 --- a/.travis.yml +++ /dev/null @@ -1,37 +0,0 @@ -language: rust - -rust: - - stable - -cache: cargo - -sudo: false - -# Dependencies of kcov, used for cargo-travis -addons: - apt: - packages: - - libcurl4-openssl-dev - - libelf-dev - - libdw-dev - - binutils-dev - - cmake - sources: - - kalakris-cmake - -before_script: - - cargo install --force cargo-travis - - export PATH=$HOME/.cargo/bin:$PATH - - rustup component add rustfmt-preview - -script: - - cargo fmt --all -- --check - - cargo build - - cargo test - -after_success: - - cargo coveralls - -env: - global: - - RUST_BACKTRACE=1 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1e856db --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +## [0.13.0](https://www.github.com/rust-bio/rust-bio-types/compare/v0.12.1...v0.13.0) (2022-07-23) + + +### Features + +* strand: Use serde ([#38](https://www.github.com/rust-bio/rust-bio-types/issues/38)) ([3fa1820](https://www.github.com/rust-bio/rust-bio-types/commit/3fa1820ec03165367b8c5025a6cf110fd968a81b)) + +### [0.12.1](https://www.github.com/rust-bio/rust-bio-types/compare/v0.12.0...v0.12.1) (2021-11-17) + + +### Bug Fixes + +* Fix `'\'` being rendered as ’' on docs.rs ([#31](https://www.github.com/rust-bio/rust-bio-types/issues/31)) ([ebbb67a](https://www.github.com/rust-bio/rust-bio-types/commit/ebbb67a3b4683a0584f869828223d622c1a502c6)) + +## [0.12.0](https://www.github.com/rust-bio/rust-bio-types/compare/v0.11.0...v0.12.0) (2021-07-09) + + +### Features + +* make AlignmentOperation hashable ([f1c4df0](https://www.github.com/rust-bio/rust-bio-types/commit/f1c4df09f0247ef76235f5ed6c17156535586b47)) diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..fc57007 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,369 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bio-types" +version = "0.13.0" +dependencies = [ + "clap", + "derive-new", + "lazy_static", + "petgraph", + "regex", + "serde", + "strum_macros", + "thiserror", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "derive-new" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fixedbitset" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "os_str_bytes" +version = "6.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" + +[[package]] +name = "petgraph" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rustversion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strum_macros" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + +[[package]] +name = "thiserror" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 8128f31..1e9f079 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,26 @@ [package] name = "bio-types" -version = "0.7.1-alpha.0" +version = "0.13.0" authors = ["Johannes Köster "] description = "A collection of common biomedical types for use in rust-bio and rust-htslib." homepage = "https://rust-bio.github.io" -repository = "https://github.com/rust-bio/rust-bio" -documentation = "https://docs.rs/bio" +repository = "https://github.com/rust-bio/rust-bio-types" +documentation = "https://docs.rs/bio-types" readme = "README.md" license = "MIT" license-file = "LICENSE.md" +edition = "2018" +exclude = [".gitignore", ".github"] +[features] +phylogeny = ["petgraph"] [dependencies] -serde = "1.0" -serde_derive = "1.0" -quick-error = "1.2" +serde = { version = "^1", optional = true, features=["derive"] } +clap = { version = ">=3.2.0", optional = true, features = ["derive"] } +thiserror = "1" regex = "1.0" lazy_static = "1.1" derive-new = "0.5" +petgraph = { version = ">=0.5,<0.7", optional = true } +strum_macros = ">=0.20, <0.25" diff --git a/README.md b/README.md index b3b672a..49e43df 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ [![Crates.io](https://img.shields.io/crates/d/bio-types.svg)](https://crates.io/crates/bio-types) [![Crates.io](https://img.shields.io/crates/v/bio-types.svg)](https://crates.io/crates/bio-types) [![Crates.io](https://img.shields.io/crates/l/bio-types.svg)](https://crates.io/crates/bio-types) +[![GitHub Workflow Status](https://github.com/rust-bio/rust-bio-types/actions/workflows/rust.yml/badge.svg?branch=master)](https://github.com/rust-bio/rust-bio-types/actions/workflows/rust.yml) +[![Coverage Status](https://coveralls.io/repos/github/rust-bio/rust-bio-types/badge.svg?branch=master)](https://coveralls.io/github/rust-bio/rust-bio-types?branch=master) # Rust-Bio-Types diff --git a/src/alignment.rs b/src/alignment.rs index 04d2ce3..9b0c4f4 100644 --- a/src/alignment.rs +++ b/src/alignment.rs @@ -5,6 +5,11 @@ //! Types for representing pairwise sequence alignments +#[cfg(feature = "clap")] +use clap::ValueEnum; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + pub type TextSlice<'a> = &'a [u8]; /// Alignment operations supported are match, substitution, insertion, deletion @@ -15,7 +20,8 @@ pub type TextSlice<'a> = &'a [u8]; /// value associated with the clipping operations are the lengths clipped. In case /// of standard modes like Global, Semi-Global and Local alignment, the clip operations /// are filtered out -#[derive(Eq, PartialEq, Debug, Copy, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Eq, PartialEq, Debug, Copy, Clone, Hash)] pub enum AlignmentOperation { Match, Subst, @@ -33,7 +39,9 @@ pub enum AlignmentOperation { /// appropriately set. /// /// The default alignment mode is Global. -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "clap", derive(ValueEnum))] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum AlignmentMode { Local, Semiglobal, @@ -53,7 +61,8 @@ impl Default for AlignmentMode { /// lengths of sequences x and y, and the alignment edit operations. The start position /// and end position of the alignment does not include the clipped regions. The length /// of clipped regions are already encapsulated in the Alignment Operation. -#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Default)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Eq, PartialEq, Clone, Default)] pub struct Alignment { /// Smith-Waterman alignment score pub score: i32, @@ -151,7 +160,7 @@ impl Alignment { /// sequence x, second line is for the alignment operation and the /// the third line is for the sequence y. A '-' in the sequence /// indicates a blank (insertion/deletion). The operations follow - /// the following convention: '|' for a match, '\' for a mismatch, + /// the following convention: '|' for a match, '\\' (a single backslash) for a mismatch, /// '+' for an insertion, 'x' for a deletion and ' ' for clipping /// /// # Example @@ -219,7 +228,7 @@ impl Alignment { x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[x_i]]))); x_i += 1; - inb_pretty.push_str("|"); + inb_pretty.push('|'); y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[y_i]]))); y_i += 1; @@ -303,13 +312,13 @@ impl Alignment { while idx < ml { let rng = idx..min(idx + ncol, ml); s.push_str(&x_pretty[rng.clone()]); - s.push_str("\n"); + s.push('\n'); s.push_str(&inb_pretty[rng.clone()]); - s.push_str("\n"); + s.push('\n'); s.push_str(&y_pretty[rng]); - s.push_str("\n"); + s.push('\n'); s.push_str("\n\n"); idx += ncol; @@ -319,6 +328,33 @@ impl Alignment { } /// Returns the optimal path in the alignment matrix + /// + /// # Example + /// + /// ``` + /// use bio_types::alignment::{Alignment,AlignmentMode}; + /// use bio_types::alignment::AlignmentOperation::*; + /// let alignment = Alignment { + /// score: 5, + /// xstart: 3, + /// ystart: 0, + /// xend: 9, + /// yend: 10, + /// ylen: 10, + /// xlen: 10, + /// operations: vec![Match, Match, Match, Subst, Ins, Ins, Del, Del], + /// mode: AlignmentMode::Semiglobal, + /// }; + /// assert_eq!(alignment.path(),[ + /// (4, 5, Match), + /// (5, 6, Match), + /// (6, 7, Match), + /// (7, 8, Subst), + /// (8, 8, Ins), + /// (9, 8, Ins), + /// (9, 9, Del), + /// (9, 10, Del)]) + /// ``` pub fn path(&self) -> Vec<(usize, usize, AlignmentOperation)> { let mut path = Vec::new(); @@ -473,4 +509,32 @@ mod tests { let pretty = concat!(" AAAA--A\n |\\\\+xx \nTTTTTTTT-TT \n\n\n"); assert_eq!(alignment.pretty(b"AAAAA", b"TTTTTTTTTT", 100), pretty); } + + #[test] + fn test_path() { + let alignment = Alignment { + score: 5, + xstart: 3, + ystart: 0, + xend: 9, + yend: 10, + ylen: 10, + xlen: 10, + operations: vec![Match, Match, Match, Subst, Ins, Ins, Del, Del], + mode: AlignmentMode::Semiglobal, + }; + assert_eq!( + alignment.path(), + [ + (4, 5, Match), + (5, 6, Match), + (6, 7, Match), + (7, 8, Subst), + (8, 8, Ins), + (9, 8, Ins), + (9, 9, Del), + (9, 10, Del) + ] + ) + } } diff --git a/src/annot/contig.rs b/src/annot/contig.rs index 136c439..bbfdc09 100644 --- a/src/annot/contig.rs +++ b/src/annot/contig.rs @@ -14,10 +14,10 @@ use std::str::FromStr; use regex::Regex; -use annot::loc::Loc; -use annot::pos::Pos; -use annot::*; -use strand::*; +use crate::annot::loc::Loc; +use crate::annot::pos::Pos; +use crate::annot::*; +use crate::strand::*; /// Contiguous sequence region on a particular, named sequence (e.g. a /// chromosome) @@ -63,10 +63,10 @@ impl Contig { /// ``` pub fn new(refid: R, start: isize, length: usize, strand: S) -> Self { Contig { - refid: refid, - start: start, - length: length, - strand: strand, + refid, + start, + length, + strand, } } @@ -106,7 +106,7 @@ impl Contig { Ok(Contig { refid: pos.refid().clone(), start: pos.start(), - length: length, + length, strand: pos.strand(), }) } else { @@ -118,8 +118,8 @@ impl Contig { Ok(Contig { refid: pos.refid().clone(), - start: start, - length: length, + start, + length, strand: pos.strand(), }) } @@ -131,7 +131,7 @@ impl Contig { refid: self.refid, start: self.start, length: self.length, - strand: strand, + strand, } } } @@ -287,17 +287,21 @@ impl Loc for Contig { impl Display for Contig where R: Display, - S: Display, + S: Display + Clone + Into, { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!( f, - "{}:{}-{}{}", + "{}:{}-{}", self.refid, self.start, - self.start + self.length as isize, - self.strand - ) + self.start + self.length as isize + )?; + let strand: Strand = self.strand.clone().into(); + if !strand.is_unknown() { + write!(f, "({})", strand)?; + } + Ok(()) } } @@ -313,21 +317,15 @@ where static ref CONTIG_RE: Regex = Regex::new(r"^(.*):(\d+)-(\d+)(\([+-]\))?$").unwrap(); } - let cap = CONTIG_RE - .captures(s) - .ok_or_else(|| ParseAnnotError::BadAnnot)?; + let cap = CONTIG_RE.captures(s).ok_or(ParseAnnotError::BadAnnot)?; - let start = cap[2] - .parse::() - .map_err(|e| ParseAnnotError::ParseInt(e))?; - let end = cap[3] - .parse::() - .map_err(|e| ParseAnnotError::ParseInt(e))?; + let start = cap[2].parse::().map_err(ParseAnnotError::ParseInt)?; + let end = cap[3].parse::().map_err(ParseAnnotError::ParseInt)?; let strand = cap .get(4) .map_or("", |m| m.as_str()) .parse::() - .map_err(|e| ParseAnnotError::ParseStrand(e))?; + .map_err(ParseAnnotError::ParseStrand)?; if start <= end { Ok(Contig::new( @@ -473,6 +471,17 @@ mod tests { }; } + #[test] + fn test_display_fmt() { + let tma19 = Contig::new( + "chrXI".to_owned(), + 334412, + 334916 - 334412, + ReqStrand::Reverse, + ); + assert_eq!(format!("{}", tma19), "chrXI:334412-334916(-)"); + } + #[test] fn intersection() { test_contig_ixn( diff --git a/src/annot/loc.rs b/src/annot/loc.rs index 5b87d78..f384705 100644 --- a/src/annot/loc.rs +++ b/src/annot/loc.rs @@ -8,10 +8,10 @@ use std::ops::Neg; -use annot::contig::Contig; -use annot::pos::Pos; +use crate::annot::contig::Contig; +use crate::annot::pos::Pos; -use strand::*; +use crate::strand::*; /// A trait for a sequence location -- a defined region on a named /// chromosome (or other reference sequence), which may also have @@ -80,7 +80,7 @@ pub trait Loc { Self::Strand: Into + Copy, T: Neg + Copy; - fn contig_intersection(&self, &Contig) -> Option + fn contig_intersection(&self, other: &Contig) -> Option where Self: ::std::marker::Sized, Self::RefID: PartialEq + Clone, diff --git a/src/annot/mod.rs b/src/annot/mod.rs index b96dd49..c8829e7 100644 --- a/src/annot/mod.rs +++ b/src/annot/mod.rs @@ -44,7 +44,8 @@ //! # fn main() { try_main().unwrap(); } //! ``` -use strand; +use crate::strand; +use thiserror::Error; pub mod contig; pub mod loc; @@ -53,36 +54,25 @@ pub mod refids; pub mod spliced; // Errors that arise in parsing annotations. -quick_error! { - #[derive(Debug, Clone)] - pub enum ParseAnnotError { - BadAnnot { - description("Annotation string does not match regex") - } - ParseInt(err: ::std::num::ParseIntError) { - description("Integer parsing error") - } - ParseStrand(err: strand::StrandError) { - description("Strand parsing error") - } - Splicing(err: spliced::SplicingError) { - description("Bad splicing structure") - } - EndBeforeStart { - description("Ending position < starting position") - } - } +#[derive(Error, Debug)] +pub enum ParseAnnotError { + #[error("Annotation string does not match regex")] + BadAnnot, + #[error("Integer parsing error")] + ParseInt(#[from] ::std::num::ParseIntError), + #[error("Strand parsing error")] + ParseStrand(#[from] strand::StrandError), + #[error("Bad splicing structure")] + Splicing(#[from] spliced::SplicingError), + #[error("Ending position < starting position")] + EndBeforeStart, } // Errors that arise in maniuplating annotations -quick_error! { - #[derive(Debug, Clone)] - pub enum AnnotError { - NoStrand { - description("No strand information") - } - BadSplicing { - description("Invalid splicing structure") - } - } +#[derive(Error, Debug)] +pub enum AnnotError { + #[error("No strand information")] + NoStrand, + #[error("Invalid splicing structure")] + BadSplicing, } diff --git a/src/annot/pos.rs b/src/annot/pos.rs index 92d5ca1..2639275 100644 --- a/src/annot/pos.rs +++ b/src/annot/pos.rs @@ -14,10 +14,10 @@ use std::str::FromStr; use regex::Regex; -use annot::contig::Contig; -use annot::loc::Loc; -use annot::*; -use strand::*; +use crate::annot::contig::Contig; +use crate::annot::loc::Loc; +use crate::annot::*; +use crate::strand::*; /// Position on a particular, named sequence (e.g. a chromosome). /// @@ -60,11 +60,7 @@ impl Pos { /// let start = Pos::new(chr, 683946, ReqStrand::Reverse); /// ``` pub fn new(refid: R, pos: isize, strand: S) -> Self { - Pos { - refid: refid, - pos: pos, - strand: strand, - } + Pos { refid, pos, strand } } /// Position on the reference sequence (0-based). @@ -77,7 +73,7 @@ impl Pos { Pos { refid: self.refid, pos: self.pos, - strand: strand, + strand, } } } @@ -107,8 +103,8 @@ where /// ``` fn add_assign(&mut self, dist: T) { match self.strand { - ReqStrand::Forward => self.pos += dist.into(), - ReqStrand::Reverse => self.pos -= dist.into(), + ReqStrand::Forward => self.pos += dist, + ReqStrand::Reverse => self.pos -= dist, } } } @@ -138,8 +134,8 @@ where /// ``` fn sub_assign(&mut self, dist: T) { match self.strand { - ReqStrand::Forward => self.pos -= dist.into(), - ReqStrand::Reverse => self.pos += dist.into(), + ReqStrand::Forward => self.pos -= dist, + ReqStrand::Reverse => self.pos += dist, } } } @@ -169,9 +165,7 @@ impl Loc for Pos { Self::Strand: Into + Copy, T: Neg + Copy, { - if self.refid != pos.refid { - None - } else if self.pos != pos.pos { + if (self.refid != pos.refid) || (self.pos != pos.pos) { None } else { Some(Pos::new( @@ -233,10 +227,15 @@ where impl Display for Pos where R: Display, - S: Display, + S: Display + Clone + Into, { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}:{}{}", self.refid, self.pos, self.strand) + let strand: Strand = self.strand.clone().into(); + if strand.is_unknown() { + write!(f, "{}:{}", self.refid, self.pos) + } else { + write!(f, "{}:{}({})", self.refid, self.pos, strand) + } } } @@ -252,21 +251,17 @@ where static ref POS_RE: Regex = Regex::new(r"^(.*):(\d+)(\([+-]\))?$").unwrap(); } - let cap = POS_RE - .captures(s) - .ok_or_else(|| ParseAnnotError::BadAnnot)?; + let cap = POS_RE.captures(s).ok_or(ParseAnnotError::BadAnnot)?; let strand = cap .get(3) .map_or("", |m| m.as_str()) .parse::() - .map_err(|e| ParseAnnotError::ParseStrand(e))?; + .map_err(ParseAnnotError::ParseStrand)?; Ok(Pos::new( R::from(cap[1].to_owned()), - cap[2] - .parse::() - .map_err(|e| ParseAnnotError::ParseInt(e))?, + cap[2].parse::().map_err(ParseAnnotError::ParseInt)?, strand, )) } diff --git a/src/annot/refids.rs b/src/annot/refids.rs index d0b4461..b0cb480 100644 --- a/src/annot/refids.rs +++ b/src/annot/refids.rs @@ -41,6 +41,12 @@ pub struct RefIDSet { refids: HashMap, } +impl Default for RefIDSet { + fn default() -> Self { + Self::new() + } +} + impl RefIDSet { /// Create a new, empty table of interned reference names pub fn new() -> Self { diff --git a/src/annot/spliced.rs b/src/annot/spliced.rs index edf93c6..639fef1 100644 --- a/src/annot/spliced.rs +++ b/src/annot/spliced.rs @@ -15,11 +15,11 @@ use std::str::FromStr; use regex::Regex; -use annot::contig::Contig; -use annot::loc::Loc; -use annot::pos::Pos; -use annot::*; -use strand::*; +use crate::annot::contig::Contig; +use crate::annot::loc::Loc; +use crate::annot::pos::Pos; +use crate::annot::*; +use crate::strand::*; // The spliced location representation inherently cannot represent // "bad" splicing structures. Locations comprise a first exon length, @@ -51,8 +51,8 @@ mod inex { Err(SplicingError::ExonLength) } else { Ok(InEx { - intron_length: intron_length, - exon_length: exon_length, + intron_length, + exon_length, }) } } @@ -100,7 +100,7 @@ mod inex { } impl<'a> Exes<'a> { - pub fn new(exon_0_length: usize, inexes: &'a Vec) -> Self { + pub fn new(exon_0_length: usize, inexes: &'a [InEx]) -> Self { Exes { state: ExesState::FirstExon(exon_0_length), curr_start: 0, @@ -167,7 +167,7 @@ mod inex { impl<'a> Ins<'a> { #[allow(dead_code)] - pub fn new(exon_0_length: usize, inexes: &'a Vec) -> Self { + pub fn new(exon_0_length: usize, inexes: &'a [InEx]) -> Self { Ins { curr_start: exon_0_length, rest: inexes.iter(), @@ -244,11 +244,11 @@ impl Spliced { /// ``` pub fn new(refid: R, start: isize, exon_0_length: usize, strand: S) -> Self { Spliced { - refid: refid, - start: start, - exon_0_length: exon_0_length, + refid, + start, + exon_0_length, inexes: Vec::new(), - strand: strand, + strand, } } @@ -261,7 +261,7 @@ impl Spliced { exon_starts: &[usize], strand: S, ) -> Result { - if exon_starts.len() < 1 { + if exon_starts.is_empty() { return Err(SplicingError::NoExons); } else if exon_starts[0] != 0 { return Err(SplicingError::BlockStart); @@ -287,11 +287,11 @@ impl Spliced { } Ok(Spliced { - refid: refid, - start: start, - exon_0_length: exon_0_length, - inexes: inexes, - strand: strand, + refid, + start, + exon_0_length, + inexes, + strand, }) } @@ -308,7 +308,7 @@ impl Spliced { self.exon_0_length == 0 && self.inexes.is_empty() } - fn exes<'a>(&'a self) -> inex::Exes<'a> { + fn exes(&self) -> inex::Exes { inex::Exes::new(self.exon_0_length, &self.inexes) } @@ -355,7 +355,7 @@ impl Spliced { start: self.start, exon_0_length: self.exon_0_length, inexes: self.inexes, - strand: strand, + strand, } } @@ -427,9 +427,7 @@ impl Loc for Spliced { Self::Strand: Into + Copy, T: Neg + Copy, { - if self.refid != *pos.refid() { - return None; - } else if pos.pos() < self.start { + if (self.refid != *pos.refid()) || pos.pos() < self.start { return None; } @@ -518,7 +516,7 @@ impl Loc for Spliced { } } - if exon_starts.len() > 0 { + if !exon_starts.is_empty() { let first_start = exon_starts[0]; for start in exon_starts.iter_mut() { *start -= first_start; @@ -547,7 +545,7 @@ impl Loc for Spliced { impl Display for Spliced where R: Display, - S: Display, + S: Display + Clone + Into, { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}:", self.refid)?; @@ -564,7 +562,11 @@ where )?; sep = true; } - write!(f, "{}", self.strand) + let strand: Strand = self.strand.clone().into(); + if !strand.is_unknown() { + write!(f, "({})", self.strand)?; + } + Ok(()) } } @@ -582,22 +584,14 @@ where static ref EXON_RE: Regex = Regex::new(r";(\d+)-(\d+)").unwrap(); } - let cap = SPLICED_RE - .captures(s) - .ok_or_else(|| ParseAnnotError::BadAnnot)?; + let cap = SPLICED_RE.captures(s).ok_or(ParseAnnotError::BadAnnot)?; let mut starts = Vec::new(); let mut lengths = Vec::new(); - let first_start = cap[2] - .parse::() - .map_err(|e| ParseAnnotError::ParseInt(e))?; - let first_end = cap[3] - .parse::() - .map_err(|e| ParseAnnotError::ParseInt(e))?; - let strand = cap[5] - .parse::() - .map_err(|e| ParseAnnotError::ParseStrand(e))?; + let first_start = cap[2].parse::().map_err(ParseAnnotError::ParseInt)?; + let first_end = cap[3].parse::().map_err(ParseAnnotError::ParseInt)?; + let strand = cap[5].parse::().map_err(ParseAnnotError::ParseStrand)?; starts.push(0); lengths.push((first_end - first_start) as usize); @@ -607,10 +601,10 @@ where for exon_cap in exon_caps { let next_start = exon_cap[1] .parse::() - .map_err(|e| ParseAnnotError::ParseInt(e))?; + .map_err(ParseAnnotError::ParseInt)?; let next_end = exon_cap[2] .parse::() - .map_err(|e| ParseAnnotError::ParseInt(e))?; + .map_err(ParseAnnotError::ParseInt)?; starts.push((next_start - first_start) as usize); lengths.push((next_end - next_start) as usize); } @@ -622,7 +616,7 @@ where starts.as_slice(), strand, ) - .map_err(|e| ParseAnnotError::Splicing(e))?; + .map_err(ParseAnnotError::Splicing)?; Ok(spliced) } } @@ -686,28 +680,20 @@ pub type SeqSplicedStranded = Spliced; /// by a `String` pub type SeqSplicedUnstranded = Spliced; -quick_error! { - #[derive(Debug, Clone)] - pub enum SplicingError { - IntronLength { - description("Invalid (non-positive) intron length") - } - ExonLength { - description("Invalid (non-positive) exon length") - } - NoExons { - description("No exons") - } - BlockStart { - description("Exons do not start at position 0") - } - BlockMismatch { - description("Number of exon starts != number of exon lengths") - } - BlockOverlap { - description("Exon blocks overlap") - } - } +#[derive(Error, Debug)] +pub enum SplicingError { + #[error("Invalid (non-positive) intron length")] + IntronLength, + #[error("Invalid (non-positive) exon length")] + ExonLength, + #[error("No exons")] + NoExons, + #[error("Exons do not start at position 0")] + BlockStart, + #[error("Number of exon starts != number of exon lengths")] + BlockMismatch, + #[error("Exon blocks overlap")] + BlockOverlap, } #[cfg(test)] @@ -802,8 +788,8 @@ mod tests { let p0_into_expected = Pos::new((), in_offset, in_strand); let p0_into_actual = loc.pos_into(&p0); let p0_back_out_actual = loc.pos_outof(&p0_into_expected); - print!( - "{}\t{}\t{:?}\t{:?}\t{:?}\n", + println!( + "{}\t{}\t{:?}\t{:?}\t{:?}", outstr, p0, p0_into_expected, p0_into_actual, p0_back_out_actual ); assert!(Some(p0_into_expected).same(&p0_into_actual)); diff --git a/src/genome.rs b/src/genome.rs index d28fd15..9d532bc 100644 --- a/src/genome.rs +++ b/src/genome.rs @@ -1,6 +1,9 @@ use std::cmp; use std::ops::Range; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + pub type Position = u64; pub type Length = u64; @@ -19,8 +22,8 @@ pub trait AbstractInterval { && locus.pos() < self.range().end } } - -#[derive(new, Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(new, Debug, PartialEq, Eq, Clone, Hash)] pub struct Interval { contig: String, range: Range, @@ -67,7 +70,8 @@ pub trait AbstractLocus { fn pos(&self) -> Position; } -#[derive(new, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Serialize, Deserialize)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(new, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)] pub struct Locus { contig: String, pos: Position, diff --git a/src/lib.rs b/src/lib.rs index 8396bec..592a248 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,5 @@ -#[macro_use] -extern crate serde_derive; -#[macro_use] -extern crate quick_error; +#[cfg(feature = "serde")] +extern crate serde; #[macro_use] extern crate lazy_static; #[macro_use] @@ -9,9 +7,14 @@ extern crate derive_new; extern crate regex; +#[cfg(feature = "phylogeny")] +extern crate petgraph; + pub mod alignment; pub mod annot; pub mod genome; +#[cfg(feature = "phylogeny")] +pub mod phylogeny; pub mod sequence; pub mod strand; pub mod variant; diff --git a/src/phylogeny.rs b/src/phylogeny.rs new file mode 100644 index 0000000..f593335 --- /dev/null +++ b/src/phylogeny.rs @@ -0,0 +1,18 @@ +// Copyright 2020 Franklin Delehelle +// Licensed under the MIT license (http://opensource.org/licenses/MIT) +// This file may not be copied, modified, or distributed +// except according to those terms. + +//! A phylogenetic tree is represented as a directed graph. +//! Each node is a taxon, identified as a string. +//! The edges are weighted by the phylogenetic distance if it was defined, or f32::NAN otherwise. + +use petgraph::{graph::Graph, Directed}; + +pub type Taxon = String; +pub type Proximity = f32; + +pub type TreeGraph = Graph; +pub struct Tree { + pub g: TreeGraph, +} diff --git a/src/sequence.rs b/src/sequence.rs index 86d362f..5098b62 100644 --- a/src/sequence.rs +++ b/src/sequence.rs @@ -1,3 +1,13 @@ +// Copyright 2021 Johannes Köster. +// Licensed under the MIT license (http://opensource.org/licenses/MIT) +// This file may not be copied, modified, or distributed +// except according to those terms. + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use strum_macros::{AsRefStr, Display}; +use SequenceReadPairOrientation::None; + /// A DNA base pub type Base = u8; /// An amino acid @@ -6,8 +16,50 @@ pub type AminoAcid = u8; pub type Sequence = Vec; pub trait SequenceRead { + /// Read name. fn name(&self) -> &[u8]; + /// Base at position `i` in the read. fn base(&self, i: usize) -> u8; + /// Base quality at position `i` in the read. fn base_qual(&self, i: usize) -> u8; + /// Read length. fn len(&self) -> usize; + /// Return `true` if read is empty. + fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +/// Representation of sequence read pair orientation +/// (e.g. F1R2 means that the forward read comes first on the reference contig, +/// followed by the reverse read, on the same contig). +/// +/// This enum can be pretty-printed into a readable string repesentation: +/// +/// ```rust +/// use bio_types::sequence::SequenceReadPairOrientation; +/// +/// // format into string +/// println!("{}", SequenceReadPairOrientation::F1R2); +/// // obtain string via `AsRef<&'static str>` +/// assert_eq!(SequenceReadPairOrientation::R1F2.as_ref(), "R1F2"); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, AsRefStr, Display)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum SequenceReadPairOrientation { + F1R2, + F2R1, + R1F2, + R2F1, + F1F2, + R1R2, + F2F1, + R2R1, + None, +} + +impl Default for SequenceReadPairOrientation { + fn default() -> Self { + None + } } diff --git a/src/strand.rs b/src/strand.rs index bd9b195..6653aef 100644 --- a/src/strand.rs +++ b/src/strand.rs @@ -5,12 +5,16 @@ //! Data types for strand information on annotations. +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; use std::ops::Neg; use std::str::FromStr; +use thiserror::Error; /// Strand information. #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Strand { Forward, Reverse, @@ -46,14 +50,11 @@ impl Strand { } pub fn is_unknown(&self) -> bool { - if let Strand::Unknown = *self { - true - } else { - false - } + matches!(*self, Strand::Unknown) } } +#[allow(clippy::match_like_matches_macro)] impl PartialEq for Strand { /// Returns true if both are `Forward` or both are `Reverse`, otherwise returns false. fn eq(&self, other: &Strand) -> bool { @@ -76,6 +77,7 @@ impl Neg for Strand { } } +#[allow(clippy::match_like_matches_macro)] impl Same for Strand { fn same(&self, s1: &Self) -> bool { match (*self, *s1) { @@ -89,10 +91,7 @@ impl Same for Strand { impl Display for Strand { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match *self { - Strand::Unknown => Ok(()), - _ => write!(f, "({})", self.strand_symbol()), - } + f.write_str(self.strand_symbol()) } } @@ -100,9 +99,9 @@ impl FromStr for Strand { type Err = StrandError; fn from_str(s: &str) -> Result { match s { - "" => Ok(Strand::Unknown), - "(+)" => Ok(Strand::Forward), - "(-)" => Ok(Strand::Reverse), + "+" | "(+)" => Ok(Strand::Forward), + "-" | "(-)" => Ok(Strand::Reverse), + "." | "" => Ok(Strand::Unknown), _ => Err(StrandError::ParseError), } } @@ -135,6 +134,7 @@ impl From for Strand { /// Strand information for annotations that require a strand. #[derive(Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd, Copy)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum ReqStrand { Forward, Reverse, @@ -197,10 +197,7 @@ impl Same for ReqStrand { impl Display for ReqStrand { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - ReqStrand::Forward => write!(f, "(+)"), - ReqStrand::Reverse => write!(f, "(-)"), - } + f.write_str(self.strand_symbol()) } } @@ -208,8 +205,8 @@ impl FromStr for ReqStrand { type Err = StrandError; fn from_str(s: &str) -> Result { match s { - "(+)" => Ok(ReqStrand::Forward), - "(-)" => Ok(ReqStrand::Reverse), + "+" | "(+)" => Ok(ReqStrand::Forward), + "-" | "(-)" => Ok(ReqStrand::Reverse), _ => Err(StrandError::ParseError), } } @@ -244,6 +241,7 @@ impl Neg for ReqStrand { /// Strand information for annotations that definitively have no /// strand information. #[derive(Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd, Copy)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum NoStrand { Unknown, } @@ -285,7 +283,7 @@ impl Display for NoStrand { pub trait Same { /// Indicate when two strands are the "same" -- two /// unknown/unspecified strands are the "same" but are not equal. - fn same(&self, &Self) -> bool; + fn same(&self, other: &Self) -> bool; } impl Same for Option @@ -301,17 +299,12 @@ where } } -quick_error! { - #[derive(Debug, Clone)] - pub enum StrandError { - InvalidChar(invalid_char: char) { - description("invalid character for strand conversion") - display("character {:?} can not be converted to a Strand", invalid_char) - } - ParseError { - description("error parsing strand") - } - } +#[derive(Error, Debug)] +pub enum StrandError { + #[error("invalid character for strand conversion: {0:?}: can not be converted to a Strand")] + InvalidChar(char), + #[error("error parsing strand")] + ParseError, } #[cfg(test)] diff --git a/src/variant.rs b/src/variant.rs index 4bc7ad9..f03932d 100644 --- a/src/variant.rs +++ b/src/variant.rs @@ -1,5 +1,8 @@ -use genome; -use sequence::{Base, Sequence}; +use crate::genome; +use crate::sequence::{Base, Sequence}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; /// A trait for providing variant information. This can e.g. be implemented by file readers. pub trait AbstractVariant: genome::AbstractLocus { @@ -7,7 +10,8 @@ pub trait AbstractVariant: genome::AbstractLocus { } /// Possible genomic variants. -#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, PartialEq, Eq, Clone, Hash)] pub enum Kind { SNV(Base), MNV(Sequence), @@ -21,14 +25,18 @@ pub enum Kind { impl Kind { /// Return variant length. pub fn len(&self) -> genome::Length { - match self { - &Kind::SNV(_) => 1, - &Kind::MNV(ref s) => s.len() as u64, - &Kind::Insertion(ref s) => s.len() as u64, - &Kind::Deletion(l) => l, - &Kind::Duplication(l) => l, - &Kind::Inversion(l) => l, - &Kind::None => 1, + match *self { + Kind::SNV(_) => 1, + Kind::MNV(ref s) => s.len() as u64, + Kind::Insertion(ref s) => s.len() as u64, + Kind::Deletion(l) => l, + Kind::Duplication(l) => l, + Kind::Inversion(l) => l, + Kind::None => 1, } } + /// Check if length is zero + pub fn is_empty(&self) -> bool { + self.len() == 0 + } }