feat: add licenses and refactor
Rust / test (push) Failing after 50s Details

This commit is contained in:
DataHearth 2023-06-28 12:05:10 +02:00
parent f41157cab9
commit e8ee999ece
No known key found for this signature in database
GPG Key ID: E88FD356ACC5F3C4
11 changed files with 1052 additions and 153 deletions

View File

@ -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

View File

@ -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

22
.gitea/workflows/rust.yml Normal file
View File

@ -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

4
.gitignore vendored
View File

@ -1,2 +1,2 @@
/target
/my_project
target
demo_pynit

835
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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"

View File

@ -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)
---

50
src/components.rs Normal file
View File

@ -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()?)
}

37
src/license.rs Normal file
View File

@ -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>()?)
}

View File

@ -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 {

View File

@ -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)?)?;