feat: add licenses and refactor
Rust / test (push) Failing after 50s
Details
Rust / test (push) Failing after 50s
Details
This commit is contained in:
parent
f41157cab9
commit
e8ee999ece
41
.drone.yml
41
.drone.yml
|
@ -1,41 +0,0 @@
|
|||
name: default
|
||||
kind: pipeline
|
||||
type: docker
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: rust:1
|
||||
commands:
|
||||
- cargo run -- --help
|
||||
|
||||
- name: deploy:crate
|
||||
image: rust:1
|
||||
environment:
|
||||
CARGO_REGISTRY_TOKEN:
|
||||
from_secret: CARGO_REGISTRY_TOKEN
|
||||
commands:
|
||||
- cargo publish
|
||||
when:
|
||||
event:
|
||||
include:
|
||||
- tag
|
||||
depends_on:
|
||||
- build
|
||||
|
||||
- name: deploy:wheel
|
||||
image: rust:1
|
||||
environment:
|
||||
MATURIN_URL:
|
||||
from_secret: MATURIN_URL
|
||||
MATURIN_USERNAME:
|
||||
from_secret: MATURIN_USERNAME
|
||||
MATURIN_PASSWORD:
|
||||
from_secret: MATURIN_PASSWORD
|
||||
commands:
|
||||
- maturin publish
|
||||
when:
|
||||
event:
|
||||
include:
|
||||
- tag
|
||||
depends_on:
|
||||
- build
|
|
@ -0,0 +1,31 @@
|
|||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
jobs:
|
||||
deploy-crate:
|
||||
runs-on: debian-rust
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Publish crate
|
||||
run: cargo publish
|
||||
|
||||
deploy-wheel:
|
||||
runs-on: debian-rust
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Publish wheel
|
||||
env:
|
||||
MATURIN_REPOSITORY_URL: ${{ secrets.PIP_REPOSITORY }}
|
||||
MATURIN_USERNAME: datahearth
|
||||
MATURIN_PYPI_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
pip install maturin
|
||||
maturin publish
|
|
@ -0,0 +1,22 @@
|
|||
name: Rust
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: debian-rust
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
|
@ -1,2 +1,2 @@
|
|||
/target
|
||||
/my_project
|
||||
target
|
||||
demo_pynit
|
File diff suppressed because it is too large
Load Diff
|
@ -14,5 +14,6 @@ anyhow = "1.0"
|
|||
clap = { version = "4.0", features = ["derive"] }
|
||||
dialoguer = "0.10"
|
||||
regex = "1.7"
|
||||
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.5"
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# pynit
|
||||
|
||||
[![License](https://img.shields.io/crates/l/pynit)](https://gitea.antoine-langlois.net/DataHearth/pynit/src/branch/main/LICENSE)
|
||||
[![Build Status](https://drone.antoine-langlois.net/api/badges/DataHearth/pynit/status.svg?ref=refs/heads/main)](https://drone.antoine-langlois.net/DataHearth/pynit)
|
||||
[![Build Status](https://img.shields.io/crates/v/pynit)](https://crates.io/crates/pynit)
|
||||
[![Version](https://img.shields.io/crates/v/pynit)](https://crates.io/crates/pynit)
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
use anyhow::Result;
|
||||
use dialoguer::Select;
|
||||
use dialoguer::{theme::ColorfulTheme, Input};
|
||||
use std::fmt::Debug;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub fn input<T>(theme: &ColorfulTheme, prompt: &str, empty: bool, default: Option<T>) -> Result<T>
|
||||
where
|
||||
T: Clone + FromStr + ToString,
|
||||
<T as FromStr>::Err: Debug + ToString,
|
||||
{
|
||||
let mut input = Input::<T>::with_theme(theme);
|
||||
|
||||
input.with_prompt(prompt).allow_empty(empty);
|
||||
if let Some(default) = default {
|
||||
input.default(default);
|
||||
}
|
||||
|
||||
Ok(input.interact_text()?)
|
||||
}
|
||||
|
||||
pub fn input_list<F, T>(
|
||||
theme: &ColorfulTheme,
|
||||
prompt: &str,
|
||||
empty: bool,
|
||||
default: Option<String>,
|
||||
map_fn: F,
|
||||
) -> Result<Vec<T>>
|
||||
where
|
||||
F: FnMut(&str) -> T,
|
||||
{
|
||||
Ok(input(theme, prompt, empty, default)?
|
||||
.split(';')
|
||||
.filter(|v| !v.is_empty())
|
||||
.map(map_fn)
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn select(
|
||||
theme: &ColorfulTheme,
|
||||
prompt: &str,
|
||||
default: usize,
|
||||
items: &[String],
|
||||
) -> Result<usize> {
|
||||
Ok(Select::with_theme(theme)
|
||||
.with_prompt(prompt)
|
||||
.default(default)
|
||||
.items(items)
|
||||
.interact()?)
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
use anyhow::Result;
|
||||
use reqwest::{blocking::Client, header::USER_AGENT};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LicenseDetails {
|
||||
#[serde(flatten)]
|
||||
base_license: License,
|
||||
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct License {
|
||||
pub spdx_id: String,
|
||||
}
|
||||
|
||||
pub fn get_license_spdx() -> Result<Vec<String>> {
|
||||
let licenses = Client::new()
|
||||
.get(&format!("https://api.github.com/licenses",))
|
||||
.header(USER_AGENT, format!("pynit-{}", env!("CARGO_PKG_VERSION")))
|
||||
.send()?
|
||||
.json::<Vec<License>>()?;
|
||||
|
||||
Ok(licenses
|
||||
.iter()
|
||||
.map(|license| license.spdx_id.clone())
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn get_license(spdx: String) -> Result<LicenseDetails> {
|
||||
Ok(Client::new()
|
||||
.get(&format!("https://api.github.com/licenses/{spdx}"))
|
||||
.header(USER_AGENT, format!("pynit-{}", env!("CARGO_PKG_VERSION")))
|
||||
.send()?
|
||||
.json::<LicenseDetails>()?)
|
||||
}
|
10
src/main.rs
10
src/main.rs
|
@ -7,8 +7,11 @@ use std::{
|
|||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use license::get_license;
|
||||
use pyproject::Pyproject;
|
||||
|
||||
mod components;
|
||||
mod license;
|
||||
mod pyproject;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
|
||||
|
@ -81,8 +84,13 @@ fn initialize_folder(
|
|||
let mut pypro = Pyproject::new(folder.clone(), complete);
|
||||
|
||||
pypro.ask_inputs()?;
|
||||
let project_name = pypro.get_project_name();
|
||||
|
||||
let project_name = pypro.get_project_name();
|
||||
|
||||
fs::write(
|
||||
folder.join("LICENSE"),
|
||||
get_license(pypro.get_license_spdx())?.body,
|
||||
)?;
|
||||
pypro.create_file()?;
|
||||
|
||||
if let Some(layout) = layout {
|
||||
|
|
171
src/pyproject.rs
171
src/pyproject.rs
|
@ -1,10 +1,15 @@
|
|||
use std::{fs, path::PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use dialoguer::{theme::ColorfulTheme, Input};
|
||||
use dialoguer::theme::ColorfulTheme;
|
||||
use regex::Regex;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
components::{input, input_list, select},
|
||||
license::get_license_spdx,
|
||||
};
|
||||
|
||||
const PYPROJECT: &str = "pyproject.toml";
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
@ -34,102 +39,87 @@ impl Pyproject {
|
|||
pub fn ask_inputs(&mut self) -> Result<()> {
|
||||
let theme = ColorfulTheme::default();
|
||||
|
||||
self.build_system.requires = Input::<String>::with_theme(&theme)
|
||||
.with_prompt("build dependencies (comma separated)")
|
||||
.default("setuptools,wheel".to_string())
|
||||
.interact_text()?
|
||||
.split(',')
|
||||
.filter(|v| !v.is_empty())
|
||||
.map(|v| v.to_string())
|
||||
.collect();
|
||||
self.build_system.build_backend = Input::with_theme(&theme)
|
||||
.with_prompt("build back-end")
|
||||
.default("setuptools.build_meta".to_string())
|
||||
.interact_text()?;
|
||||
self.build_system.requires = input_list(
|
||||
&theme,
|
||||
"build dependencies (comma separated)",
|
||||
false,
|
||||
Some("setuptools,wheel".to_string()),
|
||||
|v| v.to_string(),
|
||||
)?;
|
||||
self.build_system.build_backend = input::<String>(
|
||||
&theme,
|
||||
"build back-end",
|
||||
false,
|
||||
Some("setuptools.build_meta".to_string()),
|
||||
)?;
|
||||
|
||||
// ? might want to switch to OsString instead, if the Serialize macro supports it
|
||||
let folder = match self
|
||||
.folder
|
||||
.file_name()
|
||||
.ok_or(anyhow!("project can't terminate by \"..\""))?
|
||||
.to_str()
|
||||
{
|
||||
Some(v) => Some(v.to_string()),
|
||||
None => None,
|
||||
};
|
||||
let mut input: Input<String> = Input::with_theme(&theme);
|
||||
if let Some(folder) = folder {
|
||||
self.project.name = input
|
||||
.with_prompt("project name")
|
||||
.default(folder)
|
||||
.interact_text()?;
|
||||
} else {
|
||||
self.project.name = input.with_prompt("project name").interact_text()?;
|
||||
}
|
||||
|
||||
self.project.version = Input::with_theme(&theme)
|
||||
.with_prompt("version")
|
||||
.default("0.1.0".to_string())
|
||||
.interact_text()?;
|
||||
self.project.name = input::<String>(
|
||||
&theme,
|
||||
"project name",
|
||||
false,
|
||||
match self
|
||||
.folder
|
||||
.file_name()
|
||||
.ok_or(anyhow!("project can't terminate by \"..\""))?
|
||||
.to_str()
|
||||
{
|
||||
Some(v) => Some(v.to_string()),
|
||||
None => None,
|
||||
},
|
||||
)?;
|
||||
self.project.version =
|
||||
input::<String>(&theme, "version", false, Some("0.1.0".to_string()))?;
|
||||
|
||||
if self.complete {
|
||||
self.project.description = Input::with_theme(&theme)
|
||||
.with_prompt("description")
|
||||
.allow_empty(true)
|
||||
.interact_text()?;
|
||||
self.project.readme = Input::with_theme(&theme)
|
||||
.with_prompt("readme")
|
||||
.allow_empty(true)
|
||||
.interact_text()?;
|
||||
self.project.requires_python = Input::with_theme(&theme)
|
||||
.with_prompt("minimum python version")
|
||||
.allow_empty(true)
|
||||
.interact_text()?;
|
||||
self.project.license = Input::with_theme(&theme)
|
||||
.with_prompt("license")
|
||||
.allow_empty(true)
|
||||
.interact_text()?;
|
||||
self.project.authors = Input::<String>::with_theme(&theme)
|
||||
.with_prompt(
|
||||
r#"authors (e.g: "Antoine Langlois";"name="Antoine L",email="email@domain.net"")"#,
|
||||
)
|
||||
.allow_empty(true)
|
||||
.interact_text()?
|
||||
.split(';')
|
||||
.filter(|v| !v.is_empty())
|
||||
.map(|v| self.parse_contributor(v))
|
||||
.collect();
|
||||
self.project.maintainers = Input::<String>::with_theme(&theme)
|
||||
.with_prompt(
|
||||
r#"maintainers (e.g: "Antoine Langlois";"name="Antoine L",email="email@domain.net"")"#,
|
||||
)
|
||||
.allow_empty(true)
|
||||
.interact_text()?
|
||||
.split(';')
|
||||
.filter(|v| !v.is_empty())
|
||||
.map(|v| self.parse_contributor(v))
|
||||
.collect();
|
||||
self.project.keywords = Input::<String>::with_theme(&theme)
|
||||
.with_prompt("keywords (e.g: KEYW1;KEYW2)")
|
||||
.allow_empty(true)
|
||||
.interact_text()?
|
||||
.split(';')
|
||||
.filter(|v| !v.is_empty())
|
||||
.map(|v| v.to_string())
|
||||
.collect();
|
||||
self.project.classifiers = Input::<String>::with_theme(&theme)
|
||||
.with_prompt("classifiers (e.g: CLASS1;CLASS2)")
|
||||
.allow_empty(true)
|
||||
.interact_text()?
|
||||
.split(';')
|
||||
.filter(|v| !v.is_empty())
|
||||
.map(|v| v.to_string())
|
||||
.collect();
|
||||
self.ask_complete(&theme)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ask_complete(&mut self, theme: &ColorfulTheme) -> Result<()> {
|
||||
self.project.description = input::<String>(theme, "description", true, None)?;
|
||||
self.project.readme = input::<String>(theme, "readme", true, None)?;
|
||||
self.project.requires_python =
|
||||
input::<String>(theme, "minimum python version", true, None)?;
|
||||
let license_spdx = get_license_spdx()?;
|
||||
let license_index = select(
|
||||
theme,
|
||||
"license",
|
||||
license_spdx
|
||||
.binary_search(&"MIT".into())
|
||||
.or(Err(anyhow!("MIT license not found")))?,
|
||||
&license_spdx[..],
|
||||
)?;
|
||||
|
||||
self.project.license = license_spdx[license_index].clone();
|
||||
self.project.authors = input_list(
|
||||
theme,
|
||||
r#"authors (e.g: "Antoine Langlois";"name="Antoine L",email="email@domain.net"")"#,
|
||||
true,
|
||||
None,
|
||||
|v| self.parse_contributor(v),
|
||||
)?;
|
||||
self.project.maintainers = input_list(
|
||||
theme,
|
||||
r#"maintainers (e.g: "Antoine Langlois";"name="Antoine L",email="email@domain.net"")"#,
|
||||
true,
|
||||
None,
|
||||
|v| self.parse_contributor(v),
|
||||
)?;
|
||||
self.project.keywords =
|
||||
input_list(theme, "keywords (e.g: KEYW1;KEYW2)", true, None, |v| {
|
||||
v.to_string()
|
||||
})?;
|
||||
self.project.classifiers =
|
||||
input_list(theme, "classifiers (e.g: CLASS1;CLASS2)", true, None, |v| {
|
||||
v.to_string()
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_contributor(&self, contributor: &str) -> Contributor {
|
||||
let name_regex =
|
||||
Regex::new(r#"name="([\w\s\-\.]*)""#).expect("invalid name regex expression");
|
||||
|
@ -157,6 +147,11 @@ impl Pyproject {
|
|||
// ? clone or maybe something else ?
|
||||
self.project.name.clone()
|
||||
}
|
||||
|
||||
pub fn get_license_spdx(&self) -> String {
|
||||
self.project.license.clone()
|
||||
}
|
||||
|
||||
/// Consume self and write everything to a `self.folder/pyproject.toml`
|
||||
pub fn create_file(self) -> Result<()> {
|
||||
fs::write(self.folder.join(PYPROJECT), toml::to_vec(&self)?)?;
|
||||
|
|
Loading…
Reference in New Issue