From 9af85ba3e44c374c7256a1689b547f42939f2763 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Mon, 20 Mar 2017 17:30:50 +0900 Subject: [PATCH] Pick highlighting theme from config --- README.md | 9 ++----- examples/generate_themes.rs | 45 +++++++++++++++++++++++++++++++++++ src/config.rs | 13 ++++++++++ src/markdown.rs | 32 ++++++++++++++----------- src/page.rs | 5 ++-- sublime_themes/all.themedump | Bin 0 -> 10174 bytes 6 files changed, 81 insertions(+), 23 deletions(-) create mode 100644 examples/generate_themes.rs create mode 100644 sublime_themes/all.themedump diff --git a/README.md b/README.md index 67f0481..cb45fd6 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,5 @@ markdown -> HTML for the content ### Themes Gallery at https://tmtheme-editor.herokuapp.com/#!/editor/theme/Agola%20Dark - -# TODO: - -- find a way to add tests -- syntax highlighting -- pass a --config arg to the CLI to change from `config.toml` -- have verbosity levels with a `verbosity` config variable with a default +Make .themedump file: +`cargo run --example generate_themes themepack sublime_themes sublime_themes/all.themedump` diff --git a/examples/generate_themes.rs b/examples/generate_themes.rs new file mode 100644 index 0000000..880bb96 --- /dev/null +++ b/examples/generate_themes.rs @@ -0,0 +1,45 @@ +//! This program is mainly intended for generating the dumps that are compiled in to +//! syntect, not as a helpful example for beginners. +//! Although it is a valid example for serializing syntaxes, you probably won't need +//! to do this yourself unless you want to cache your own compiled grammars. +extern crate syntect; +use syntect::parsing::SyntaxSet; +use syntect::highlighting::ThemeSet; +use syntect::dumps::*; +use std::env; + +fn usage_and_exit() -> ! { + println!("USAGE: gendata synpack source-dir newlines.packdump nonewlines.packdump\n + gendata themepack source-dir themepack.themedump"); + ::std::process::exit(2); +} + +fn main() { + + let mut a = env::args().skip(1); + match (a.next(), a.next(), a.next(), a.next()) { + (Some(ref cmd), + Some(ref package_dir), + Some(ref packpath_newlines), + Some(ref packpath_nonewlines)) if cmd == "synpack" => { + let mut ps = SyntaxSet::new(); + ps.load_plain_text_syntax(); + ps.load_syntaxes(package_dir, true).unwrap(); + dump_to_file(&ps, packpath_newlines).unwrap(); + + ps = SyntaxSet::new(); + ps.load_plain_text_syntax(); + ps.load_syntaxes(package_dir, false).unwrap(); + dump_to_file(&ps, packpath_nonewlines).unwrap(); + + } + (Some(ref s), Some(ref theme_dir), Some(ref packpath), None) if s == "themepack" => { + let ts = ThemeSet::load_from_folder(theme_dir).unwrap(); + for (path, _) in &ts.themes { + println!("{:?}", path); + } + dump_to_file(&ts, packpath).unwrap(); + } + _ => usage_and_exit(), + } +} diff --git a/src/config.rs b/src/config.rs index 045f4fd..c8a407b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use toml::{Value as Toml, self}; use errors::{Result, ResultExt}; +use markdown::SETUP; // TO ADD: @@ -22,6 +23,8 @@ pub struct Config { /// Whether to highlight all code blocks found in markdown files. Defaults to false pub highlight_code: Option, + /// Which themes to use for code highlighting. See Readme for supported themes + pub highlight_theme: Option, /// Description of the site pub description: Option, /// The language used in the site. Defaults to "en" @@ -50,6 +53,15 @@ impl Config { config.highlight_code = Some(false); } + match config.highlight_theme { + Some(ref t) => { + if !SETUP.theme_set.themes.contains_key(t) { + bail!("Theme {} not available", t) + } + }, + None => config.highlight_theme = Some("base16-ocean-dark".to_string()) + }; + if config.generate_rss.is_none() { config.generate_rss = Some(false); } @@ -84,6 +96,7 @@ impl Default for Config { title: "".to_string(), base_url: "http://a-website.com/".to_string(), highlight_code: Some(true), + highlight_theme: Some("base16-ocean-dark".to_string()), description: None, language_code: Some("en".to_string()), generate_rss: Some(false), diff --git a/src/markdown.rs b/src/markdown.rs index 8bc6d33..873047f 100644 --- a/src/markdown.rs +++ b/src/markdown.rs @@ -3,6 +3,7 @@ use std::borrow::Cow::Owned; use pulldown_cmark as cmark; use self::cmark::{Parser, Event, Tag}; +use syntect::dumps::from_binary; use syntect::easy::HighlightLines; use syntect::parsing::SyntaxSet; use syntect::highlighting::ThemeSet; @@ -10,18 +11,18 @@ use syntect::html::{start_coloured_html_snippet, styles_to_coloured_html, Includ // We need to put those in a struct to impl Send and sync -struct Setup { +pub struct Setup { syntax_set: SyntaxSet, - theme_set: ThemeSet, + pub theme_set: ThemeSet, } unsafe impl Send for Setup {} unsafe impl Sync for Setup {} lazy_static!{ - static ref SETUP: Setup = Setup { + pub static ref SETUP: Setup = Setup { syntax_set: SyntaxSet::load_defaults_newlines(), - theme_set: ThemeSet::load_defaults() + theme_set: from_binary(include_bytes!("../sublime_themes/all.themedump")) }; } @@ -30,13 +31,15 @@ struct CodeHighlightingParser<'a> { // The block we're currently highlighting highlighter: Option>, parser: Parser<'a>, + theme: &'a str, } impl<'a> CodeHighlightingParser<'a> { - pub fn new(parser: Parser<'a>) -> CodeHighlightingParser<'a> { + pub fn new(parser: Parser<'a>, theme: &'a str) -> CodeHighlightingParser<'a> { CodeHighlightingParser { highlighter: None, parser: parser, + theme: theme, } } } @@ -67,15 +70,16 @@ impl<'a> Iterator for CodeHighlightingParser<'a> { } }, Event::Start(Tag::CodeBlock(ref info)) => { + let theme = &SETUP.theme_set.themes[self.theme]; let syntax = info .split(' ') .next() .and_then(|lang| SETUP.syntax_set.find_syntax_by_token(lang)) .unwrap_or_else(|| SETUP.syntax_set.find_syntax_plain_text()); self.highlighter = Some( - HighlightLines::new(syntax, &SETUP.theme_set.themes["base16-ocean.dark"]) + HighlightLines::new(syntax, &theme) ); - let snippet = start_coloured_html_snippet(&SETUP.theme_set.themes["base16-ocean.dark"]); + let snippet = start_coloured_html_snippet(&theme); Some(Event::Html(Owned(snippet))) }, Event::End(Tag::CodeBlock(_)) => { @@ -89,10 +93,10 @@ impl<'a> Iterator for CodeHighlightingParser<'a> { } } -pub fn markdown_to_html(content: &str, highlight_code: bool) -> String { +pub fn markdown_to_html(content: &str, highlight_code: bool, highlight_theme: &str) -> String { let mut html = String::new(); if highlight_code { - let parser = CodeHighlightingParser::new(Parser::new(content)); + let parser = CodeHighlightingParser::new(Parser::new(content), highlight_theme); cmark::html::push_html(&mut html, parser); } else { let parser = Parser::new(content); @@ -108,13 +112,13 @@ mod tests { #[test] fn test_markdown_to_html_simple() { - let res = markdown_to_html("# hello", true); + let res = markdown_to_html("# hello", true, "base16-ocean-dark"); assert_eq!(res, "

hello

\n"); } #[test] fn test_markdown_to_html_code_block_highlighting_off() { - let res = markdown_to_html("```\n$ gutenberg server\n```", false); + let res = markdown_to_html("```\n$ gutenberg server\n```", false, "base16-ocean-dark"); assert_eq!( res, "
$ gutenberg server\n
\n" @@ -123,7 +127,7 @@ mod tests { #[test] fn test_markdown_to_html_code_block_no_lang() { - let res = markdown_to_html("```\n$ gutenberg server\n$ ping\n```", true); + let res = markdown_to_html("```\n$ gutenberg server\n$ ping\n```", true, "base16-ocean-dark"); assert_eq!( res, "
\n$ gutenberg server\n$ ping\n
" @@ -132,7 +136,7 @@ mod tests { #[test] fn test_markdown_to_html_code_block_with_lang() { - let res = markdown_to_html("```python\nlist.append(1)\n```", true); + let res = markdown_to_html("```python\nlist.append(1)\n```", true, "base16-ocean-dark"); assert_eq!( res, "
\nlist.append(1)\n
" @@ -140,7 +144,7 @@ mod tests { } #[test] fn test_markdown_to_html_code_block_with_unknown_lang() { - let res = markdown_to_html("```yolo\nlist.append(1)\n```", true); + let res = markdown_to_html("```yolo\nlist.append(1)\n```", true, "base16-ocean-dark"); // defaults to plain text assert_eq!( res, diff --git a/src/page.rs b/src/page.rs index be7d6ed..f788aba 100644 --- a/src/page.rs +++ b/src/page.rs @@ -132,12 +132,13 @@ impl Page { false }; - page.content = markdown_to_html(&page.raw_content, should_highlight); + let highlight_theme = config.highlight_theme.clone().unwrap(); + page.content = markdown_to_html(&page.raw_content, should_highlight, &highlight_theme); if page.raw_content.contains("") { page.summary = { let summary = page.raw_content.splitn(2, "").collect::>()[0]; - markdown_to_html(summary, should_highlight) + markdown_to_html(summary, should_highlight, &highlight_theme) } } diff --git a/sublime_themes/all.themedump b/sublime_themes/all.themedump new file mode 100644 index 0000000000000000000000000000000000000000..3af3d02d5d14916e1cebc1e4906b4d8569788423 GIT binary patch literal 10174 zcmYj$V{j!*({^lUqm6BEoNRRBjcxP98{4*R+cq{fPppmY4Zqya`{(;JHQjS{S65H< zRQEM*;1{~caRd_1TXQEX%QM+d)F)0I3uEF93{CV#u&Gn}eQ|!D2-Gk*D$h6Pmm6Eh zF0N|qjU9Y@j(C~Z%4mHCNGg(wOe1lQn%NL{AY0= zRd!mkGtuYC)HQ zW2|au8Cs|@`B$D^Lv*6ef*S2y(1L9iwZnJWE=00eH{V^y{L)CboUWqO(M? z0Qp-T3sk2&glj#eqUGoNb?4G=dlbwl$>+*XcWK!+!9`rxlvV0dQ9MBE3-1A%T#&P>N8?0xi$^bBBL8r)fr8FjrcJdr9L=x3$?x#d#ylT z2_vyt_kwKEIFqma?RpE7moONx#t!omL8C5kFO@3^)nIK>2qMe6Y?*&sfZu3a4}63& z4^$9J6Eqo}%+K9|NjEJdKSUpnt_1_ldkS_fYt6YCmlU1*kIQvn! z95%Ey2|UAsz`xOMd2lzfIi)Ivr6FhEOBd1K3i4rESK1g@RLc{^^E1dfE!j5%I#kNL zH`uh}MK8JdMl06JJ^qFm5pd3Ng}U2tLdr~Y9{zQeNW{GK>uIXFMdoenKvN6WWy4s7 z@=$p5yzSXZsWPBQwOY;W!3=q`;`2X|Rk_(}IU^<7cFC8wT-C>@UoF)8=S2zdESbl& z6Z7_NJcYEFAbBGyLiI>R`P#g%dr*IcPh<_+3Z@rA&b)Occ_S!R)!=_8=wuz)^>%zvMm z=9d2>Vf5tukG{gCh0>cC9QM&oD*s1;)`kqr+0-lBb*dV=9^&1Pm_sROSC^r zIq%!dn}vitjzxvfxyr6;g zE{`4Da}$Xal1WvH%1S0j1(a3SNGQ6Cz?p0WsZc+6jjQj7wq;;y>osKsrIN*o_!#Lj zva|;j^wBFUeF&v3;o~)A2;>R%vIM0DW4lI{m{SC@ccZFvkyF1$xOs*C-2M*d>#3+! zmKw=U(51%-&uA%ZH$q9XT9ydP(=5l3MoEHWABPSW5^9OtBOeSqsVE{?>~B&7bPJ!H ztK|JYhZdPA!LRH*ZcI#8SUPi9K1;Dd`-wH^P&TuNec@uk_9VGpdEeZtpfYuSn|bp( zel~e{P`m;;V(G5P?oHP3RnpiMmR4Gr@Kp5F6MWmSjx|$R*?fXRdyV0NTwaFbTA9Jl zj<7_4LTsX_GhHpOYZx(_*cPKQqy$Fmt*27YnUyXT0~uO^7BfH%>zM4Ydf?V!?ca}S zd3)BS1Z+vRSuHer%bNYYUVG(Rt^!o?xe8+O>)dzn6fOlxsV)5rEHAb6Cm!#K{*zTZ z4^N$mahWWT(V@qA%8#Ff$w_PD!E}*qZjqhn?4UBf>7!+&wgk_&sdyWB_?qWyfAg)@ zY90>V-5g{fR1SrdpG`NKr6mD#bt*v8#34PW85Lj*AmNcCi6f4kbyL_A}zfWaK)9C%26_IkUj5 zc6BuH=!(e6HwS>q*F7 zKg287=7`#!MBkCgY%?^E_0ev;eVrOANyuVQ9D4MeTA$DWMT(o}!b7P{JwH*&YA#6j z%=Lb>d=CioRbE|{MN_2ERuu+h31}XYkewxYq!f|ty_br2B9QGx8A+p|kB~5ijdIh# zGQgsP_&Tt@0e+<_mynRj*DG4u3tk!29#Y=w?>QSF{sd- zAp)3)x96daCP1SrtCr^q1CpkV0>jGh)_O_Ev}r2L*r3rp|AwKGvB^tN+L@p!9zS5f zpo7Hwm>aA70e}4T6*L3Gqre3>Cc(x2_^-?eqtZoSx2NWjjjy3tDa2=6t7gxjsQ^Ho z_jyJY^?k5digI{tWnp*f*TQ1)+nNY;1^1jhWykGGo6uN_-yxu|6xR@7u^OW_;OGjH z*eJ`w3PJ-T+Bou&=n7UTV6mWmZD8v^87s7hzlVo3P2`V!NH2J+cIwPBbsQJOERzau zm6qmsR1~USdWcFGx8(TH0f@Wh&3*noph&oGdp|j^ZX=vO1w20Q$G-X&=ML1gMa&IN z_qci;pa5k3ydGCQ$85q@Y__D@ap@u64i4~_{!+XK@4Gm49TJY;Da%mEm=>Ce%ylqK z5&W|IW1*H9n$nF_dF=wwospCz;_E`ZSEyc_cAFG2sBrOLW)QDiDNG|-(GD{>Nm)O_S8BoWo!_MoCF=>hf9)%i=<+io~U z+hY)KVros%XLvRH_S0jF)$ zICc?kxsZaxNM3)+(W*1~v5ecFU#mBPV{S-!;<1NhSE1OoE`uz78J+Yi%eKwV9853Z zi?iA7YG!leOAUFj3YZd(7s{5~(t1^pWe$VwDb*pt+9_r3yZl{O2C-CRs{ZZBuN99q?X8K0Mt@%|}Gly}e z`XPgj<~w_%<=@Fahf6$=_^{finQE~`Tu3=iTs79=HN;4?wi@mU_`II;TD@!(O}6gE zY(Jh8Ru4-(1d=O!@8*eWWJ=OZ`oBt3N`}91oyRaEMYY#N?JT)bk~Y z$FRJ(AI&pO2kY{&uLQ8@HfMz|*M} zxw4Kk%tz8gWY#4X{7p976xTHa)71^5gOxGP3}XqZi)5KGyp)!vQknB}-UcOgGATZY zvm8A$rlf1SEg72yhGMyas!<>2E9>is>a13s+PWX?YgN$9@YzFbez*&Xo944j3fzdu zt5C*eP-EE1OtG+ItSE4KLurF%o$13Fz;B?ct+N`d&0ZNv=_XYNYRQRSkj12bUi#jx z$`hSfr0RK;>;Lc>MCRJ4%I3K{>lBE}oW7{yO#Ntx`B<2f%l#?)QJ~AekWOpy`gB#= z`*!>|z;_~83-@}*YA{POIu*M0Le(5dX1uZSZc6Cy6@PFOg6e(ZzuwVfg}KBZML`}H z=$Al}fCNAjRYuCS@Hq!}4)hMpnfvbb+h8{nA^h-FT_+?C11<&eLDQP^?PY!Y(h1z) zx5w@t6H~W@BIV#rT{_YQ4qR_){sF5~3*cEmTC2${f^-LwaU)Wt?XEe*1J1IJb;joz z?63!jD-rr21b-d^Ie+73qXNQ|G8VH7%}{QZ1*4~hwwZ|$ynAG})5m_pQ~V@xbHU=Z z4>MNefb-N&tyS_Pf>j9e8&Rwij`h6(;P|bYPxFK3J{ zlfhX$?Z?r&erO+gl!rp|IZUtVhSxrs8dOw{UW3RIg%?z_sRNQ+3$n!Oo$bGaLO6~{ z+_>t1Op*S`EhM}?18O=R4#(*VEiCK=9g9V*Kh{y(8MurBWzc6CpYuoX*Z8OLxln1_ zc9&(^mX9|`VaE|4@Q@`sp(VeF^Rh%B4*Yq%Fd1>NKH_# zpGu*(-y6Wl1fx90NW65H*GZm&SZFB=6*((ZJ>20Z8ClB++hGpw|N29RpC)&t((!UIrBOG^!Ys#wkDeywre!ZBU}mSQ@Ndj+cl)VLEG1!r8{|VK{uRLVBb+?SVh$L z08$PHGt&qXdhW_#V%~{vnI+Kq?>vbX5I)>foD+L9lU@U8eb>GprVDtlssTU$(TUdf zV2JvC_3`zFoOq}e$=Amuh^hmkd!#QeNRgYNJI)0C^e0x7SqN7NdoIr8m3rssFA^s* z$iGS1#@AwV->Jwl-1!Hg;Ihv*S|kLz2Mfmd^D$m>ibz-kexVp9kc$O|I*3k9zn0(7ddSD8HnyP9z41wRFjXLgqofxjgFut!_YFmS`4X{8sBm!dmC2dVJdzk3;**xpK zVHj@d=-6RpW;QsW;pVug3A8KlOif32nSv9A&^M{Lq6wnn^eJTxuwwI%sNzu9F~JM- zzTTUxB!8D1#>YXL1O&k!%$X&j#fksJ-IqEGl6Pk=!!9*>U};jW57w>JHtl9ojJ!I` zV{}$RSx!SPDJjdzf<;@1lsnkUi+V0Xqh!UV6Q>tu+KULphEIARfIdXh#xqH&s+ZkT zn^6XSi_n=erZbyEUdAtQfje!R5Zxxmh(W>3nhA9!l2c&3 zOrduk9m6(^fE6h|3XdL%?GYMj>~Bd-)+oWmTcc;$X&fFVA_x1!khN7@5(+)0SF4p? z*dW~TcTAWt#_f8QD&thM@lykjPA#zLdhhl1K_5MLAV zX!At@@U^G`!n|HFCHOv*+D@&L1Xfud1Q#rOAVRkjybsJg9$3QiHpFirILa zBhuYL^CJ4k71PtdDEpBy;uAX@n-v4PCeT|mG%LpKFX4D+2U=B)(*{>#kC7-cJo~T`Kt=y>USMNPT(Rt4A!Hkcdf<`HUrRoz(R*0VGEd0tBZ;-iYQM4k(1%R2i9_C85ve4oA1oDx%Gi7(^cV z2O@loTdvEOhR+)%Pz4oRYrnNcVop_TyidjkDU`9{JY&0`3d+jhd9p>6DBE3tY8ji!Yk! z+v8GJ9 zTx*Wl?yuXkf31L_gJa=up+xTKJEb49!BrZ~DSL?B^_GVm;2X-Y=&2-NB%y zCjEKde}S;581ko1`J%*~oEGPkGa3SKe^d3h=A3kUTXsHWK4uGi)|>AarVL&6s?%WG zrHSSXof7eX`ut5+Clgt#r#C9jJ>V7OAp!hoGnz~oq->Qd-$}Yl^9ghaH3xUkbIhi? zUX974S@@4Fthvo?fsIYI0Zv;W=+5)5G$)DW8G{G9EEI95IN7!(aQ!)=kNs4YA1;a- zTM)eGrBnQ4{BHkArL9g%ojzI={O*h?F_7QHT*+k39jXZ?$p%CbrKO^$s8j`u}2{ntQ=jvJ+2;2LrFNRW&%$^0xa@HpW3(qr&wrgeO-ep1ls`|{daM~P$ z9hJ^0n*>f0R}n@l?Sv&8O!Z2EzHe|LKf6vei>OpgxpE!FOmKU=1mfvD$Re?zWU=V< zYraHbQ{;=3i}%@)HYCak1Q#%=TPjn5V#)S?;bmiwTy(b_ z@y2(QMuuNlgNdb!r7o!_vf_l*x=3B-(3w+V5z{_Et<*x?TU#r>o4JVaOyE_RLJt=1 z%ECGs4NsxNH4Kg*vhBa-rJx1Pl3M`8cL2M6kRfr&d8%!J3n7I=Bit1l3iz=~wVDs# zOidk8X}70{ql*O_7QbIH%7kYbRNg<(c-!sJaNCf?PmEVR7H6UH7YJhTqSo|f*$^h68VToS?k z}YW-m0+0n)8*5KGu!2fTq00riZP3##6+!t@&JvxD%`*$m?i0Ry@0S*9=D-_pn@$0!~-PP-6^lqV$uaY`&e6}nN5q_ zN8z0~L&#Q*1f_=v2%$^_J_<0&ndJ3IU1!OqDbATB*DC)4_8-%VsQ_ z_0f8jt6Bw5$Njgf=2untXUF+J@BWOoi{G_ZguuN&K;FP$j(%5~kqk6-?HG>w!+I*F z&ZiImFPU?m00?1=IFvDK_isb9FGFbR3qWct`$I3?SXKzzrmmhU4K~?Wwv4xBzMkr1 zS0jcaUL*$`xJT&fCZR6^x%Ce8?m!a(3 zUi>-yGsw|^0kJqHe=>v^N#TGZvtdyhm;?T$uvPGj{j?()l7ks&-z;|OsaB16v^6mI zV&aWuzd2Tg69A3iDZgT2aGo})xS6S^N+^oQFCzewjbt1z|5uy8|HFJq8rm?V&DSg= z8Kd=7Uv~Xkb>-++|2z3V#s9WoSYI`kWGs82foZP&RaLeB(<%^Y{Uh)r02b68)=?=u zAWZ~t#Q#?`%K$4MiQIZBpT|3D$0~R!s`4_S#>Uv-n$! zvkKmd5=wcFZyAd2UkzUsyo!&GSygz`xhTgnRE0cV9aY=>%7>xs^)GeaFU1}QV` z_)8n;iC4k*$6mk#A%@`tIs6^Tzi?&g>MQXDB}joBq)F9Iu^j#hs3<@Y+_jRBrKUqC z_Nvz2Kd;?+|IsQjF8%R$yax-3gcp>|J!OMj?!%RO^YI<5CC52PnEB1}JyHTRAb;O; zSf+tiKRU5y`EIQGqTK<-cp`pf^BTlWCJzlzMK6Usz-^wP&$|opW8RF4a?UVPOTOT z9fpmX<*k);m2A)nh|xe){bp@lNN&==4+U$62AQ-N3?vFU=_mpoNRkvo2wD)S`B>9$ z56idVYTY*2fB<3iJ66pgToeD_1y-iRiSK_DW-oo!!67l1?xrc=YWZRT$8+iZ&gyR z`4q0T!Wa)|*@k?bjqvc;rgBr|pT;cW`nB~*-jKcIR0t0AIQ~xP03?mnfpk>;=r|b* zGIL~ZGJnVX+ioSZHCPH=y>EMMV}i%R*bzDtrmQ@F}98 z8in|lWzrg-)?1L8K5{3yy`O>`VFjdV2)@=-S;T36ApCX5DdW;fU~~U9#PmkxW1l}k z(5tLGQWj`LL@D_9AQ}I1t{fBynhLXB_YmTSCf0Tz63G%45Jb<77F14EF(}8ZH+Cxz zi1Lpfk;3;FZYkhg7}P(z)|H%Lo#-nIEfft=W6NSvfX!hgZqDgCEFO_{)B*gOSthZp zG|V2yE#2DFj-;EFAMKQ# zcHP%EQD)XKt%eUueGUXA53T@)_#zvT_F13utR=k^rd2nl;3ge#COmwjG)cN99r063 zmpILenBcuagm@hC)jz|wwJ7v5Fa-2TqP(b(E|R>0CCk-X#{o!W9JV1;iR683%!|BR zS7OC<*n}AHw&_zwn+;l&=F19O>MTs{yAC3(+^9?%HK@x1(&_ea2dvTNKXI7&IL} zsGebY^PF_WFjsD`xTQP$t6T+AkOIe9^CLsO%{nWJ@@zBN*&B zlOU}ew+v0Etz^benTj9meiqkOazK}LfPsEOy>loQjVq>Myyu(T3V za;_t};h$06YUY0$=u%jJ+|Q8u^*u~aQ6t7uum20%4x`x~3WkO$kqoEuq5(ef5^wO1 zkK{H|MX0C!m&QGwC-u=wBLnBhlFs3l6u~e@_yP9)Ws>F6FwW=?6dEYVesiebF|aZ9I^;3g}o6K!#tO704x|f zGak}^nL`L_N0wGCFM8qK80Dx*HAU;NVi`QoPK(+fbI-%r)N#yl!(l9+OtdGwKkNBs zOZbSGr^qJ;$H?O1;<$?jhN;(Fnc68&`(tQ2HQKVAf@GM@^mUH4d(3_Bl$o2;DDfl+ z4SN55{(PQ2N)_nE{hNLCZ@3@F-G^2C(cQ-(N1s73{RoF%@@?cD)COLv1b%(f3peDHs*V3gVVds`Y!ZCqh<3I@r9{rzfzpRk> og(+mbU(~5(j$c+@yiA&3)O`P686oj*8T}BbgHPD8h5nlV2TatQC;$Ke literal 0 HcmV?d00001