Commit 47703264 authored by Waffle's avatar Waffle

Simplify every thing, add file output, fix a lot of bugs

parent 4101b000
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Param {
pub name: String,
pub type_: String,
......@@ -19,6 +19,6 @@ impl Method {
}
pub fn is_simple(&self) -> bool {
self.params.iter().all(|param| param.type_ != "InputFile")
self.params.iter().all(|param| param.type_ != "InputFile or String")
}
}
use crate::common::Method;
use crate::common::{Method, Param};
use crate::type_converter::TypeConverter;
mod call;
mod class;
......@@ -8,7 +8,7 @@ mod interface;
pub struct Generator;
impl Generator {
pub fn generate(&self, method: Method, to: &ConvertTo) -> String {
pub fn generate(&self, method: &Method, to: &ConvertTo) -> String {
match to {
ConvertTo::RequestClass => self.convert_to_class(method),
ConvertTo::ApiCall => self.convert_to_api_call(method),
......@@ -16,13 +16,8 @@ impl Generator {
}
}
pub fn generate_all(&self, methods: Vec<Method>, to: &ConvertTo) -> Vec<String> {
let mut result: Vec<String> = Vec::new();
for method in methods {
result.push(self.generate(method, to))
}
result
pub fn generate_all<'a>(&self, methods: &'a Vec<Method>, to: &ConvertTo) -> Vec<(String, &'a Method)> {
methods.iter().map(|method| (self.generate(method, to), method)).collect()
}
}
......@@ -32,12 +27,14 @@ pub enum ConvertTo {
InterfaceFun
}
impl Method {
fn update_param_types_to_kotlin(self) -> Method {
fn update_param_types_to_kotlin(&self) -> Method {
let params = TypeConverter.convert_all(self.params.clone());
Method {
params: TypeConverter.convert_all(self.params),
description: self.description,
name: self.name
params,
description: self.description.clone(),
name: self.name.clone()
}
}
}
\ No newline at end of file
......@@ -5,13 +5,13 @@ use crate::{
};
impl Generator {
pub fn convert_to_api_call(&self, method: Method) -> String {
pub fn convert_to_api_call(&self, method: &Method) -> String {
simple(method)
}
}
fn simple(method: Method) -> String {
fn simple(method: &Method) -> String {
let method = method.update_param_types_to_kotlin();
let mut first = true;
let mut params = String::new();
......
......@@ -6,15 +6,24 @@ use crate::{
impl Generator {
pub fn convert_to_class(&self, method: Method) -> String {
pub fn convert_to_class(&self, method: &Method) -> String {
if method.is_simple() { simple(method) } else { multipart(method) }
}
}
fn simple(method: Method) -> String {
fn simple(method: &Method) -> String {
let method = method.update_param_types_to_kotlin();
let mut params = String::new();
let mut first = true;
let mut imports =
r"import kotlinx.serialization.KSerializer
import kotlinx.serialization.Optional
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import telekt.network.TelegramMethod
import telekt.network.requests.abstracts.SimpleRequest
".to_string();
for param in method.params.iter() {
if first { first = false } else { params += ",\n" }
......@@ -34,11 +43,19 @@ fn simple(method: Method) -> String {
let types = method.get_types();
let type_ = types.type_;
if type_ == "Unit" { imports += "import telekt.util.serializer\n" }
let serializer = types.serializer;
let name = method.name.as_ref().unwrap();
let name_up = uppercase_first_letter(name);
format!(r#"/**
format!(
r#"package telekt.network.requests.auto
{imports}
/**
* [{camel_name}] request.
* {description}
* More: {link}
......@@ -49,15 +66,30 @@ fn simple(method: Method) -> String {
@Transient override val method = TelegramMethod.{name}
@Transient override val resultDeserializer: KSerializer<out {type_}> = {serializer}
override fun stringify(json: Json): String = json.stringify({camel_name}.serializer(), this)
}}"#, camel_name = name_up, description = method.description.as_ref().unwrap(), link = method.link(), params = params, name = name, type_ = type_, serializer = serializer)
}}"#, imports = imports, camel_name = name_up, description = method.description.as_ref().unwrap(), link = method.link(), params = params, name = name, type_ = type_, serializer = serializer)
}
fn multipart(method: Method) -> String {
fn multipart(method: &Method) -> String {
let method = method.update_param_types_to_kotlin();
let mut params = String::new();
let mut media_params = String::new();
let mut first = true;
let mut first_media = true;
let mut imports =
r"import kotlinx.serialization.KSerializer
import kotlinx.serialization.Optional
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import telekt.network.InputFile
import telekt.network.TelegramMethod
import telekt.network.requests.abstracts.MultipartRequest
import telekt.types.*
import telekt.types.enums.*
import telekt.util.Recipient
import kotlinx.serialization.Transient
".to_string();
for param in method.params.iter() {
if first { first = false } else { params += ",\n" }
......@@ -76,22 +108,30 @@ fn multipart(method: Method) -> String {
} else {
if name != camel { params += &format!("@SerialName(\"{}\") ", name) };
if !param.required { params += "@Optional " }
// let (optional, optional_e) = if param.required { ("", "") } else { ("@Optional ", "? = null") };
}
params += &format!("val {camel}: {type_}", camel = camel, type_ = type_);
if type_ == "InputFile" { params += " = throw RuntimeException(\"InputFile has not default value\") /* dirty workaround */" }
if type_ == "InputFile" { params += "? = throw RuntimeException(\"InputFile has not default value\") /* dirty workaround */" }
else if !param.required { params += "? = null" }
}
let types = method.get_types();
let type_ = types.type_;
if type_ == "Unit" { imports += "\nimport telekt.util.serializer" }
let serializer = types.serializer;
let name = method.name.as_ref().unwrap();
let name_up = uppercase_first_letter(name);
format!(r#"/**
format!(
r#"package telekt.network.requests.auto
{imports}
/**
* [{camel_name}] request.
* {description}
* More: {link}
......@@ -100,11 +140,11 @@ fn multipart(method: Method) -> String {
{params}
) : MultipartRequest<{type_}>() {{
@Transient override val method = TelegramMethod.{name}
@Transient override val mediaMap: Map<String, InputFile> = mapOf("photo" to photo)
@Transient override val mediaMap: Map<String, InputFile> = mapOf({media})
@Transient override val resultDeserializer: KSerializer<out {type_}> = {serializer}
@Transient override val paramsJson: JsonElement = Json.plain.toJson({camel_name}.serializer(), this)
}}"#, camel_name = name_up, description = method.description.as_ref().unwrap(), link = method.link(), params = params, name = name, type_ = type_, serializer = serializer)
}}"#, imports = imports, camel_name = name_up, media = media_params, description = method.description.as_ref().unwrap(), link = method.link(), params = params, name = name, type_ = type_, serializer = serializer)
}
fn uppercase_first_letter(s: &str) -> String {
......
......@@ -17,8 +17,9 @@ impl TypeForClass {
}
fn list(type_: &str) -> TypeForClass {
let type_ = String::from(type_);
let serializer = format!("{}.serializer().list", type_);
let class = String::from(type_);
let type_ = format!("List<{type_}>", type_ = type_);
let serializer = format!("{}.serializer().list", class);
TypeForClass {
type_,
serializer
......@@ -27,41 +28,59 @@ impl TypeForClass {
}
fn f0(string: &str) -> Option<TypeForClass> {
let re = Regex::new("On success, a (.+) object is returned\\.").unwrap();
match re.captures(string) {
Some(c) => Some(TypeForClass::new(&c[1])),
None => None
}
}
fn f0_1(string: &str) -> Option<TypeForClass> {
let re = Regex::new("Returns a (.+) object on success.").unwrap();
match re.captures(string) {
Some(c) => Some(TypeForClass::new(&c[1])),
None => None
}
}
fn f1(string: &str) -> Option<TypeForClass> {
let re: Regex = Regex::new("On success, (.+) is returned\\.").unwrap();
let re = Regex::new("Returns a (.+) object\\.").unwrap();
match re.captures(string) {
Some(c) => Some(match &c[1] {
"the sent Message" => TypeForClass::new("Message"),
"an array of the sent Messages" => TypeForClass::list("Message"),
"a File" => TypeForClass::new("File"),
_ => panic!("The regex is passed, but first group is unknown, probably something is wrong")
}),
Some(c) => Some(TypeForClass::new(&c[1])),
None => None
}
}
fn f2(string: &str) -> Option<TypeForClass> {
let re = Regex::new("Returns (.+) on success.").unwrap();
let re = Regex::new("Returns (.+) on success\\.").unwrap();
match re.captures(string) {
Some(c) => Some(match &c[1] {
"True" => TypeForClass::new("Unit"),
"the new invite link as String" => TypeForClass::new("String"),
"Int" => TypeForClass::new("Int"),
"ChatMember object" => TypeForClass::new("ChatMember"),
_ => panic!("The regex is passed, but first group is unknown, probably something is wrong")
err => panic!(format!("(f2)The regex is passed, but first group is unknown, probably something is wrong. First group={err}", err = err))
}),
None => None
}
}
fn f3(string: &str) -> Option<TypeForClass> {
let re = Regex::new("Returns a (.+) object\\.").unwrap();
let re: Regex = Regex::new("On success, (.+) is returned\\.").unwrap();
match re.captures(string) {
Some(c) => Some(TypeForClass::new(&c[1])),
Some(c) => Some(match &c[1] {
"True" => TypeForClass::new("Unit"),
"the sent Message" => TypeForClass::new("Message"),
"an array of the sent Messages" => TypeForClass::list("Message"),
"a File" => TypeForClass::new("File"),
err => panic!(format!("(f3)The regex is passed, but first group is unknown, probably something is wrong. First group={err}", err = err))
}),
None => None
}
}
fn f4(string: &str) -> Option<TypeForClass> {
let re = Regex::new("On success, returns an Array of ChatMember objects").unwrap();
match re.captures(string) {
......@@ -74,7 +93,9 @@ fn f4(string: &str) -> Option<TypeForClass> {
impl Method {
pub fn get_types(&self) -> TypeForClass {
let string = &self.description.as_ref().unwrap();
f1(string)
f0(string)
.or_else(|| f0_1(string))
.or_else(|| f1(string))
.or_else(|| f2(string))
.or_else(|| f3(string))
.or_else(|| f4(string))
......
......@@ -5,12 +5,12 @@ use crate::{
};
impl Generator {
pub fn convert_to_interface_fun(&self, method: Method) -> String {
pub fn convert_to_interface_fun(&self, method: &Method) -> String {
simple(method)
}
}
fn simple(method: Method) -> String {
fn simple(method: &Method) -> String {
let method = method.update_param_types_to_kotlin();
let mut first = true;
let mut doc = format!("/**\n * {description}\n *\n", description = method.description.as_ref().unwrap());
......@@ -26,7 +26,7 @@ fn simple(method: Method) -> String {
doc += &format!(" * @param {name} {description}\n", name = name, description = description);
params += &format!("\n {name}: {type_}{optional}", name = name, type_ = type_, optional = optional)
}
doc += " */\n";
doc += &format!(" *\n * more: {more}\n */\n", more = method.link());
let returns = method.get_types().type_;
format!(" {doc} suspend fun {name}({params}\n ): {returns}", doc = doc, name = method.name.unwrap(), params = params, returns = returns)
......
......@@ -2,7 +2,7 @@ extern crate regex;
#[macro_use]
extern crate clap;
use clap::{Arg, App};
use clap::{Arg, App, SubCommand};
use crate::{
reader::Reader,
generator::{Generator, ConvertTo},
......@@ -11,6 +11,7 @@ use crate::{
use crate::writer::WriterTrait;
use crate::writer::stdout::StdoutWriter;
use crate::writer::files::FilesWriter;
use core::borrow::Borrow;
pub mod reader;
......@@ -24,6 +25,7 @@ fn greeting() {
println!("Input methods description from telegram bot api docs.");
println!("e.g. every thing from 'sendDocument' (inclusive) to 'sendVideo' (excluding)");
println!(" from https://core.telegram.org/bots/api#senddocument .");
println!("Note: edit methods are currently not supported! Because the need to generate to methods (one for inline messages, and one for common)");
println!("When done, press enter.");
println!();
}
......@@ -64,6 +66,7 @@ fn main() {
)
.get_matches();
let mut writer = match app.value_of("output").unwrap() {
"stdout" => { Writer::Stdout(StdoutWriter::default()) },
"files" => {
......@@ -88,6 +91,8 @@ fn main() {
greeting();
let methods = Reader::default().read();
let codes = Generator.generate_all(methods, &convert_to);
let codes = Generator.generate_all(&methods, &convert_to);
let (_, m) = codes[codes.len() - 1];
println!("Check last method: {}", m.name.as_ref().unwrap());
writer.write_all(codes);
}
......@@ -17,6 +17,8 @@ impl TypeConverter {
"InputFile or String" => "InputFile",
"Integer" => "Int",
"InlineKeyboardMarkup or ReplyKeyboardMarkup or ReplyKeyboardRemove or ForceReply" => "ReplyMarkup",
"Float number" => "Float",
"Array of InputMediaPhoto and InputMediaVideo" => "List<InputMedia>",
r => r
}
}.to_string()
......
use crate::writer::stdout::StdoutWriter;
use crate::writer::files::FilesWriter;
use crate::common::Method;
pub mod stdout;
pub mod files;
......@@ -7,9 +8,9 @@ pub mod files;
pub trait WriterTrait {
/// Writes parsed methods to some where
/// Return [true] if success else [false]
fn write(&mut self, string: String) -> bool;
fn write(&mut self, string: String, method: &Method) -> bool;
fn write_all(&mut self, strings: Vec<String>) -> bool;
fn write_all(&mut self, codes: Vec<(String, &Method)>) -> bool;
}
......@@ -20,14 +21,14 @@ pub enum Writer {
impl WriterTrait for Writer {
fn write(&mut self, string: String) -> bool {
fn write(&mut self, string: String, method: &Method) -> bool {
match self {
Writer::Stdout(writer) => writer.write(string),
Writer::Files(writer) => writer.write(string)
Writer::Stdout(writer) => writer.write(string, method),
Writer::Files(writer) => writer.write(string, method)
}
}
fn write_all(&mut self, strings: Vec<String>) -> bool {
fn write_all(&mut self, strings: Vec<(String, &Method)>) -> bool {
match self {
Writer::Stdout(writer) => writer.write_all(strings),
Writer::Files(writer) => writer.write_all(strings)
......
use std::path::PathBuf;
use std::env::current_dir;
use crate::writer::WriterTrait;
use crate::common::Method;
pub struct FilesWriter { pub to: PathBuf }
pub struct FilesWriter { pub path: PathBuf }
impl Default for FilesWriter {
fn default() -> FilesWriter {
let mut to = current_dir().unwrap();
to.push(PathBuf::from("output"));
FilesWriter { to }
FilesWriter { path: to }
}
}
impl From<&str> for FilesWriter {
fn from(path: &str) -> FilesWriter {
FilesWriter { to: PathBuf::from(path) }
FilesWriter { path: PathBuf::from(path) }
}
}
use std::fs::{File, create_dir_all, OpenOptions};
use std::io::prelude::*;
impl WriterTrait for FilesWriter {
fn write(&mut self, string: String) -> bool {
// not implemented yet
fn write(&mut self, string: String, method: &Method) -> bool {
if !self.path.exists() { create_dir_all(self.path.clone()).unwrap() }
if !self.path.is_dir() { panic!("`FilesWriter.path` must be directory!") }
let mut path = self.path.clone();
path.push(method.name.clone().unwrap() + ".kt");
let mut file = if !path.exists() {
println!("{:?}", path);
OpenOptions::new().write(true).create_new(true).open(path).unwrap()
} else {
OpenOptions::new().write(true).truncate(true).open(path).unwrap()
};
file.write(string.as_bytes()).unwrap();
file.write(b"\n").unwrap();
true
}
fn write_all(&mut self, strings: Vec<String>) -> bool {
// not implemented yet
fn write_all(&mut self, codes: Vec<(String, &Method)>) -> bool {
for (string, method) in codes {
self.write(string, method);
}
true
}
}
use std::io::{Write, Stdout, stdout};
use crate::writer::WriterTrait;
use crate::common::Method;
pub struct StdoutWriter { to: Stdout }
......@@ -10,14 +11,14 @@ impl Default for StdoutWriter {
}
impl WriterTrait for StdoutWriter {
fn write(&mut self, string: String) -> bool {
fn write(&mut self, string: String, _method: &Method) -> bool {
self.to.write(string.as_bytes()).unwrap();
self.to.write(b"\n").unwrap();
true
}
fn write_all(&mut self, strings: Vec<String>) -> bool {
for string in strings {
fn write_all(&mut self, codes: Vec<(String, &Method)>) -> bool {
for (string, _) in codes {
self.to.write(string.as_bytes()).unwrap();
self.to.write(b"\n\n").unwrap();
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment