@ -1,13 +1,20 @@
#![ feature(proc_macro_hygiene, proc_macro_quote, proc_macro_span, uniform_paths) ]
extern crate proc_macro ;
use std ::{ env , io ::{ BufRead , Write } , fs ::{ create_dir_all , read , File , OpenOptions } , iter ::FromIterator , path ::Path , process ::Command } ;
use proc_macro ::{ Delimiter , Literal , Spacing , Punct , TokenStream , TokenTree , quote } ;
use std ::{
env ,
fs ::{ create_dir_all , read , File , OpenOptions } ,
io ::{ BufRead , Read , Seek , SeekFrom , Write } ,
iter ::FromIterator ,
path ::Path ,
process ::Command ,
} ;
fn is ( t : & TokenTree , ch : char ) -> bool {
match t {
TokenTree ::Punct ( p ) = > p . as_char ( ) = = ch ,
_ = > false
_ = > false ,
}
}
@ -36,31 +43,56 @@ fn trim(t: TokenTree) -> TokenTree {
#[ proc_macro ]
pub fn i18n ( input : TokenStream ) -> TokenStream {
let span = input . clone ( ) . into_iter ( ) . next ( ) . expect ( "Expected catalog" ) . span ( ) ;
let span = input
. clone ( )
. into_iter ( )
. next ( )
. expect ( "Expected catalog" )
. span ( ) ;
let mut input = input . into_iter ( ) ;
let catalog = input . clone ( ) . take_while ( | t | ! is ( t , ',' ) ) . collect ::< Vec < _ > > ( ) ;
let catalog = input
. clone ( )
. take_while ( | t | ! is ( t , ',' ) )
. collect ::< Vec < _ > > ( ) ;
let file = span . source_file ( ) . path ( ) ;
let line = span . start ( ) . line ;
let out_dir = Path ::new ( & env ::var ( "CARGO_TARGET_DIR" ) . unwrap_or ( "target/debug" . into ( ) ) ) . join ( "gettext_macros" ) ;
let domain = read ( out_dir . join ( env ::var ( "CARGO_PKG_NAME" ) . expect ( "Please build with cargo" ) ) ) . expect ( "Coudln't read domain, make sure to call init_i18n! before" ) . lines ( ) . next ( ) . unwrap ( ) . unwrap ( ) ;
let mut pot = OpenOptions ::new ( ) . append ( true ) . create ( true ) . open ( format! ( "po/{0}/{0}.pot" , domain ) ) . expect ( "Couldn't open .pot file" ) ;
let out_dir = Path ::new ( & env ::var ( "CARGO_TARGET_DIR" ) . unwrap_or ( "target/debug" . into ( ) ) )
. join ( "gettext_macros" ) ;
let domain = read ( out_dir . join ( env ::var ( "CARGO_PKG_NAME" ) . expect ( "Please build with cargo" ) ) )
. expect ( "Coudln't read domain, make sure to call init_i18n! before" )
. lines ( )
. next ( )
. unwrap ( )
. unwrap ( ) ;
let mut pot = OpenOptions ::new ( )
. read ( true )
. write ( true )
. create ( true )
. open ( format! ( "po/{0}/{0}.pot" , domain ) )
. expect ( "Couldn't open .pot file" ) ;
for _ in 0 .. ( catalog . len ( ) + 1 ) { input . next ( ) ; }
let pot_reader = read ( format! ( "po/{0}/{0}.pot" , domain ) ) . expect ( "Couldn't read .pot file" ) ;
let mut pot_lines = pot_reader . lines ( ) ;
for _ in 0 .. ( catalog . len ( ) + 1 ) {
input . next ( ) ;
}
let message = trim ( input . next ( ) . unwrap ( ) ) ;
let msgid = format! ( "msgid {}" , message ) ;
let dont_write = is_empty ( & message ) | | pot_lines . any ( | l | l . map ( | l | l = = msgid ) . unwrap_or ( false ) ) ;
let mut contents = String ::new ( ) ;
pot . read_to_string ( & mut contents ) . unwrap ( ) ;
pot . seek ( SeekFrom ::End ( 0 ) ) . unwrap ( ) ;
let already_exists = is_empty ( & message ) | | contents . contains ( & format! ( "msgid {}" , message ) ) ;
let plural = match input . clone ( ) . next ( ) {
Some ( t ) = > if is ( & t , ',' ) {
input . next ( ) ;
input . next ( )
} else {
None
} ,
_ = > None
Some ( t ) = > {
if is ( & t , ',' ) {
input . next ( ) ;
input . next ( )
} else {
None
}
}
_ = > None ,
} ;
let mut format_args = vec! [ ] ;
@ -94,24 +126,47 @@ pub fn i18n(input: TokenStream) -> TokenStream {
String ::new ( )
} ;
if let Some ( pl ) = plural {
if ! dont_write {
pot . write_all ( & format! ( r #"
if ! already_exists {
pot . write_all (
& format! (
r #"
{ } msgid { }
msgid_plural { }
msgstr [ 0 ] ""
"#, code_path, message, pl).into_bytes()).expect(" Couldn ' t write message to . pot ( plural ) " ) ;
}
let count = format_args . clone ( ) . into_iter ( ) . next ( ) . expect ( "Item count should be specified" ) . clone ( ) ;
" #,
code_path ,
message ,
pl
)
. into_bytes ( ) ,
)
. expect ( "Couldn't write message to .pot (plural)" ) ;
}
let count = format_args
. clone ( )
. into_iter ( )
. next ( )
. expect ( "Item count should be specified" )
. clone ( ) ;
res . extend ( quote ! (
. ngettext ( $message , $pl , $count as u64 )
) )
} else {
if ! dont_write {
pot . write_all ( & format! ( r #"
if ! already_exists {
pot . write_all (
& format! (
r #"
{ } msgid { }
msgstr ""
"#, code_path, message).into_bytes()).expect(" Couldn ' t write message to . pot " ) ;
}
" #,
code_path ,
message
)
. into_bytes ( ) ,
)
. expect ( "Couldn't write message to .pot" ) ;
}
res . extend ( quote ! (
. gettext ( $message )
) )
@ -136,7 +191,7 @@ msgstr ""
#[ proc_macro ]
pub fn init_i18n ( input : TokenStream ) -> TokenStream {
let mut input = input . into_iter ( ) ;
let mut input = input . into_iter ( ) ;
let domain = match input . next ( ) {
Some ( TokenTree ::Literal ( lit ) ) = > lit . to_string ( ) . replace ( "\"" , "" ) ,
Some ( _ ) = > panic! ( "Domain should be a str" ) ,
@ -154,11 +209,13 @@ pub fn init_i18n(input: TokenStream) -> TokenStream {
break ;
}
match input . next ( ) {
Some ( TokenTree ::Ident ( i ) ) = > { langs . push ( i ) ; } ,
_ = > panic! ( "Expected a language identifier" )
Some ( TokenTree ::Ident ( i ) ) = > {
langs . push ( i ) ;
}
_ = > panic! ( "Expected a language identifier" ) ,
}
}
} ,
}
_ = > panic! ( "Expected a language identifier" ) ,
}
} else {
@ -167,19 +224,27 @@ pub fn init_i18n(input: TokenStream) -> TokenStream {
}
// emit file to include
let out_dir = Path ::new ( & env ::var ( "CARGO_TARGET_DIR" ) . unwrap_or ( "target/debug" . into ( ) ) ) . join ( "gettext_macros" ) ;
let out_dir = Path ::new ( & env ::var ( "CARGO_TARGET_DIR" ) . unwrap_or ( "target/debug" . into ( ) ) )
. join ( "gettext_macros" ) ;
let out = out_dir . join ( env ::var ( "CARGO_PKG_NAME" ) . expect ( "Please build with cargo" ) ) ;
create_dir_all ( out_dir ) . expect ( "Couldn't create output dir" ) ;
let mut out = File ::create ( out ) . expect ( "Metadata file couldn't be open" ) ;
writeln! ( out , "{}" , domain ) . expect ( "Couldn't write domain" ) ;
for l in langs {
writeln! ( out , "{}" , l ) . expect ( "Couldn't write lang" ) ;
writeln! ( out , "{}" , l ) . expect ( "Couldn't write lang" ) ;
}
// write base .pot
create_dir_all ( format! ( "po/{}" , domain ) ) . expect ( "Couldn't create po dir" ) ;
let mut pot = OpenOptions ::new ( ) . write ( true ) . create ( true ) . truncate ( true ) . open ( format! ( "po/{0}/{0}.pot" , domain ) ) . expect ( "Couldn't open .pot file" ) ;
pot . write_all ( & format! ( r#"msgid ""
let mut pot = OpenOptions ::new ( )
. write ( true )
. create ( true )
. truncate ( true )
. open ( format! ( "po/{0}/{0}.pot" , domain ) )
. expect ( "Couldn't open .pot file" ) ;
pot . write_all (
& format! (
r#"msgid ""
msgstr ""
"Project-Id-Version: {}\n"
"Report-Msgid-Bugs-To: \n"
@ -192,28 +257,43 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
"#, domain).into_bytes()).expect(" Couldn ' t init . pot file " ) ;
" #,
domain
)
. into_bytes ( ) ,
)
. expect ( "Couldn't init .pot file" ) ;
quote ! ( )
}
#[ proc_macro ]
pub fn i18n_domain ( _ : TokenStream ) -> TokenStream {
let out_dir = Path ::new ( & env ::var ( "CARGO_TARGET_DIR" ) . unwrap_or ( "target/debug" . into ( ) ) ) . join ( "gettext_macros" ) ;
let domain = read ( out_dir . join ( env ::var ( "CARGO_PKG_NAME" ) . expect ( "Please build with cargo" ) ) ) . expect ( "Coudln't read domain, make sure to call init_i18n! before" ) . lines ( ) . next ( ) . unwrap ( ) . unwrap ( ) ;
let out_dir = Path ::new ( & env ::var ( "CARGO_TARGET_DIR" ) . unwrap_or ( "target/debug" . into ( ) ) )
. join ( "gettext_macros" ) ;
let domain = read ( out_dir . join ( env ::var ( "CARGO_PKG_NAME" ) . expect ( "Please build with cargo" ) ) )
. expect ( "Coudln't read domain, make sure to call init_i18n! before" )
. lines ( )
. next ( )
. unwrap ( )
. unwrap ( ) ;
let tok = TokenTree ::Literal ( Literal ::string ( & domain ) ) ;
quote ! ( $tok )
}
#[ proc_macro ]
pub fn compile_i18n ( _ : TokenStream ) -> TokenStream {
let out_dir = Path ::new ( & env ::var ( "CARGO_TARGET_DIR" ) . unwrap_or ( "target/debug" . into ( ) ) ) . join ( "gettext_macros" ) ;
let file = read ( out_dir . join ( env ::var ( "CARGO_PKG_NAME" ) . expect ( "Please build with cargo" ) ) ) . expect ( "Coudln't read domain, make sure to call init_i18n! before" ) ;
let mut lines = file . lines ( ) ;
let domain = lines . next ( ) . unwrap ( ) . unwrap ( ) ;
let locales = lines . map ( | l | l . unwrap ( ) ) . collect ::< Vec < _ > > ( ) ;
let out_dir = Path ::new ( & env ::var ( "CARGO_TARGET_DIR" ) . unwrap_or ( "target/debug" . into ( ) ) )
. join ( "gettext_macros" ) ;
let file = read ( out_dir . join ( env ::var ( "CARGO_PKG_NAME" ) . expect ( "Please build with cargo" ) ) )
. expect ( "Coudln't read domain, make sure to call init_i18n! before" ) ;
let mut lines = file . lines ( ) ;
let domain = lines . next ( ) . unwrap ( ) . unwrap ( ) ;
let locales = lines . map ( | l | l . unwrap ( ) ) . collect ::< Vec < _ > > ( ) ;
let pot_path = Path ::new ( "po" ) . join ( domain . clone ( ) ) . join ( format! ( "{}.pot" , domain ) ) ;
let pot_path = Path ::new ( "po" )
. join ( domain . clone ( ) )
. join ( format! ( "{}.pot" , domain ) ) ;
for lang in locales {
let po_path = Path ::new ( "po" ) . join ( domain . clone ( ) ) . join ( format! ( "{}.po" , lang . clone ( ) ) ) ;
@ -281,26 +361,28 @@ pub fn compile_i18n(_: TokenStream) -> TokenStream {
/// ```
#[ proc_macro ]
pub fn include_i18n ( _ : TokenStream ) -> TokenStream {
let out_dir = Path ::new ( & env ::var ( "CARGO_TARGET_DIR" ) . unwrap_or ( "target/debug" . into ( ) ) ) . join ( "gettext_macros" ) ;
let file = read ( out_dir . join ( env ::var ( "CARGO_PKG_NAME" ) . expect ( "Please build with cargo" ) ) ) . expect ( "Coudln't read domain, make sure to call init_i18n! before" ) ;
let mut lines = file . lines ( ) ;
let domain = TokenTree ::Literal ( Literal ::string ( & lines . next ( ) . unwrap ( ) . unwrap ( ) ) ) ;
let locales = lines
let out_dir = Path ::new ( & env ::var ( "CARGO_TARGET_DIR" ) . unwrap_or ( "target/debug" . into ( ) ) )
. join ( "gettext_macros" ) ;
let file = read ( out_dir . join ( env ::var ( "CARGO_PKG_NAME" ) . expect ( "Please build with cargo" ) ) )
. expect ( "Coudln't read domain, make sure to call init_i18n! before" ) ;
let mut lines = file . lines ( ) ;
let domain = TokenTree ::Literal ( Literal ::string ( & lines . next ( ) . unwrap ( ) . unwrap ( ) ) ) ;
let locales = lines
. map ( Result ::unwrap )
. map ( | l | {
let lang = TokenTree ::Literal ( Literal ::string ( & l ) ) ;
quote ! ( {
::gettext ::Catalog ::parse (
& include_bytes! (
concat! ( env! ( "CARGO_MANIFEST_DIR" ) , "/translations/" , $lang , "/LC_MESSAGES/" , $domain , ".mo" )
) [ .. ]
) . expect ( "Error while loading catalog" )
} , )
let lang = TokenTree ::Literal ( Literal ::string ( & l ) ) ;
quote ! {
( $lang , ::gettext ::Catalog ::parse (
& include_bytes! (
concat! ( env! ( "CARGO_MANIFEST_DIR" ) , "/translations/" , $lang , "/LC_MESSAGES/" , $domain , ".mo" )
) [ .. ]
) . expect ( "Error while loading catalog" ) ) ,
}
} ) . collect ::< TokenStream > ( ) ;
quote ! ( {
vec! [
$locales
]
} )
quote ! ( {
vec! [
$locales
]
} )
}