From 0b0819bf268f08966f553954f61c18facbcb169a Mon Sep 17 00:00:00 2001 From: Kamil Adam Date: Sat, 28 Mar 2020 16:31:03 +0100 Subject: [PATCH 01/47] Add_favicon.ico --- favicon.ico | Bin 0 -> 15406 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 favicon.ico diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8aba9aa95f953f01f89a7a69801522122b1af6c6 GIT binary patch literal 15406 zcmeI2hmTf85WolSkPb)haP&ipNAGYHsR2b$5Tr{Dy@S$`BE2^$3P@A2*QiJo%Re>F zk7e`rEBD+c zC$yetY@byorP+mY^{E{4v(7KlMcs}%5Ov_ctpmd+q^_@~`2eN)30@bt zU%59o9#mPmOJ&Mjm3j?R`FPx1Ei`w2RjJ=7p?&EMm0f3(_mI9uEtPG@Rq!i1byulW z$>Fu_tg`)-%8)VH=|Udi^VIn&_kMRi(vyDUcjgl}r@Hmico}TRX_fPzsvx6~56{(V zsGKucw?3J$bX?wk?$H#xEOq}8TkdUtLFbBh@3H!t6X$#DMyIDFs z#4R46^3k6v?TS>22dX^Z)6M1Jb(@>{VC&vV<+Eak;s23+?Vc~b67z4~I+fPL!xr7a z-=hy%EH>Zm!$&GFPH}zcSEy6y#KkXi^X=Npjo)v0ZoGm9Dt8~N3=Z@KAN1BQD%EQ` z+MXtp3DXlkTaKulyqmm-Yw^$ciLAe@@n4yza`jsk{B}=!>_e5Ef!+f{r#*byLX|5< zcu_|=@%mTeQdaE#}w zs$Blc_ViMAxW`4;kWs642_Cw}TG{W3fy_t0tb(jOEk5~MC)_uJ@AK`_ttub#T% zDt(4J{T%a(O8YJe%;Us1$|UMQ)Pbl2W;Xx7KjI`7qLq2}k8u+70pfXxGKe}5bs*|M z)Pbl2Q3s+9L>>4ybb$Cz zRHUCA{oV^U*6hsrGZwMZ{w0~PY-14fn!Z@uAG@4H%n$+KKcp4|TW16G@`N9Be7 zskH>zc$K%0cpCA}tw%FuLmdHe&Q0%@J%40->b}bDM=Hc;i5ceQC3MIMc~H+l+%b$d zmv^q|c<@}e-^K1PeW}u-Fc}ZrHTxWlIWeDfdjC%B^Vn^d|5&_Ph4`(9rMJ{UOkH5{ z@rM?RG#yKaSLB`fA`4>u#Jj_M2K5;9vo7M{VJtWt1AV79<=$hJ3&yKOo3iIbOq~48 zaa%LBDj~m$Jd>Ze`!bNtA{E53GWAYuHgL)ayx91=?clfk2 zTjl%9YG1W8!kUTqSFKilI?uOt2bJqTIQ^(o;ONmO;_(AUxcEQ%;Om#>tg`%p%X(k1&dDBI5#mxeb?ladk#q2{_C;ogY|qo} z_q;X9JUniB+Obm|9X5-6PI+iN4*GH9XUkiCmuYjzVIWWJW_T9aBW!H5w0z)eiyZ#W zGZmWyXP$AMW&WHQ^cB1Ab9{bn!^S(l=#lTEt@51ZfHqofox#I?bd7oV{ACEs^~~7N zRaAeN@8KKHHj?j2ALFRmO8An$ONWuCVh_T8>>vC1-q)%9Ax`=;`i|TgXU(46_eDA_ zcQ@zu!snSj4>>XRg`EE0iT^AEzYkO}r@&ECv4hj={KG#>)p zK~CFG=f2m|X{lrI`$nBa2q*7&#so%ovM%(08y zPa$vrjK8wn>>K%Z?(~eh3R|DY4O{LX{A1*U&haPrOdb|n#J$8hqMw@8>NT9K_g_(Y zeW|M{fd^|@waeYv^X;xfkHVT%>OJuZsGY!eQHz4EH*Df^vDAF9M)K1Yv6;wvnd!^! z^R8|LUx)jMerieak+Fa9$A_Tafcgo>0fz6}v8&3^adz+9J9uxObp1`um+`VfzWA)< z{?U;Q2b`Y(Z{&k7g1(nm?w&8U57}||V1H=&)6e@882wLS_>bJ_Pw<|?pY)mjgX4KX z$G-S+e9Kt^hV0oR_MwjHTQ0xU)5bPao>3-ICQ%2X4n!S@IuLar>Oj Date: Tue, 7 Apr 2020 20:40:20 +0200 Subject: [PATCH 02/47] Add_atom.xml --- atom.xml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 atom.xml diff --git a/atom.xml b/atom.xml new file mode 100644 index 00000000..96c9681c --- /dev/null +++ b/atom.xml @@ -0,0 +1,28 @@ +--- +layout: null +--- + + + + + {{ site.title }} + + + {{ site.time | date_to_xmlschema }} + {{ site.url }} + + {{ site.author.name }} + {{ site.author.email }} + + + {% for post in site.posts %} + + {{ post.title }} + + {{ post.date | date_to_xmlschema }} + {{ site.url }}{{ post.id }} + {{ post.content | xml_escape }} + + {% endfor %} + + From 62424dc93044d24b281593e12756cc84c70e9937 Mon Sep 17 00:00:00 2001 From: Kamil Adam Date: Wed, 25 Mar 2020 22:06:47 +0100 Subject: [PATCH 03/47] Update_index.html --- index.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index c5944a59..400f75f5 100644 --- a/index.html +++ b/index.html @@ -3,5 +3,6 @@ # Edit theme's home layout instead if you wanna make some changes # See: https://jekyllrb.com/docs/themes/#overriding-theme-defaults layout: home -author_profile: true +sidebar: + nav: sidebar-main --- From fa2b5e5cb9f52df0b478678c699e1a7442c94cd2 Mon Sep 17 00:00:00 2001 From: Kamil Adam Date: Tue, 24 Mar 2020 21:37:09 +0100 Subject: [PATCH 04/47] Update_assets --- assets/android-chrome-192x192.png | Bin 0 -> 9371 bytes assets/android-chrome-512x512.png | Bin 0 -> 26312 bytes assets/apple-touch-icon.png | Bin 0 -> 8623 bytes assets/favicon-16x16.png | Bin 0 -> 469 bytes assets/favicon-32x32.png | Bin 0 -> 1008 bytes assets/favicon.ico | Bin 0 -> 15406 bytes assets/images/bio-photo.jpg | Bin 1661 -> 0 bytes assets/images/kamil.adam.jpg | Bin 0 -> 16769 bytes assets/site.webmanifest | 1 + 9 files changed, 1 insertion(+) create mode 100644 assets/android-chrome-192x192.png create mode 100644 assets/android-chrome-512x512.png create mode 100644 assets/apple-touch-icon.png create mode 100644 assets/favicon-16x16.png create mode 100644 assets/favicon-32x32.png create mode 100644 assets/favicon.ico delete mode 100644 assets/images/bio-photo.jpg create mode 100644 assets/images/kamil.adam.jpg create mode 100644 assets/site.webmanifest diff --git a/assets/android-chrome-192x192.png b/assets/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..b71458955d3a4b74623b49673d0f7ac1e32265d3 GIT binary patch literal 9371 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⋙y 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 GgZ}}62J0UH literal 0 HcmV?d00001 diff --git a/assets/apple-touch-icon.png b/assets/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6dbed03e5493d8c160a909688e1a6e6005e3c83b GIT binary patch literal 8623 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 LNkvXXu0mjfAu-jo literal 0 HcmV?d00001 diff --git a/assets/favicon-32x32.png b/assets/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..c86a57909909d078766b3934ff4095e558179544 GIT binary patch literal 1008 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 N#G?RMeuEU;1OTJ`a#jEU diff --git a/assets/images/kamil.adam.jpg b/assets/images/kamil.adam.jpg new file mode 100644 index 0000000000000000000000000000000000000000..db8a5ea1371f5f520eaf7afeb21da1159dc6cd93 GIT binary patch literal 16769 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 Date: Fri, 10 Apr 2020 22:32:44 +0200 Subject: [PATCH 05/47] Create CNAME --- CNAME | 1 + 1 file changed, 1 insertion(+) create mode 100644 CNAME diff --git a/CNAME b/CNAME new file mode 100644 index 00000000..a5a90e95 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +www.writeonly.pl From 39c18b6da06326bdbbc5f58f74c563d467f6ff03 Mon Sep 17 00:00:00 2001 From: Kamil Adam Date: Fri, 10 Apr 2020 22:41:01 +0200 Subject: [PATCH 06/47] remove --- LICENSE | 21 --------------------- README.md | 27 --------------------------- 2 files changed, 48 deletions(-) delete mode 100644 LICENSE delete mode 100644 README.md diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 89828dec..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 twocolumn - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index 0a8da578..00000000 --- a/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Minimal Mistakes remote theme starter - -Fork this repo for the quickest method of getting started with the [Minimal Mistakes Jekyll theme](https://github.com/mmistakes/minimal-mistakes). - -Contains basic configuration to get you a site with: - -- Sample posts. -- Sample top navigation. -- Sample author sidebar with social links. -- Sample footer links. -- Paginated home page. -- Archive pages for posts grouped by year, category, and tag. -- Sample about page. -- Sample 404 page. -- Site wide search. - -Replace sample content with your own and [configure as necessary](https://mmistakes.github.io/minimal-mistakes/docs/configuration/). - ---- - -## Troubleshooting - -If you have a question about using Jekyll, start a discussion on the [Jekyll Forum](https://talk.jekyllrb.com/) or [StackOverflow](https://stackoverflow.com/questions/tagged/jekyll). Other resources: - -- [Ruby 101](https://jekyllrb.com/docs/ruby-101/) -- [Setting up a Jekyll site with GitHub Pages](https://jekyllrb.com/docs/github-pages/) -- [Configuring GitHub Metadata](https://github.com/jekyll/github-metadata/blob/master/docs/configuration.md#configuration) to work properly when developing locally and avoid `No GitHub API authentication could be found. Some fields may be missing or have incorrect data.` warnings. From 4d537bc75a07323e52431b82c90da5598ca40637 Mon Sep 17 00:00:00 2001 From: Kamil Adam Date: Wed, 25 Mar 2020 22:05:41 +0100 Subject: [PATCH 07/47] Update _data --- _data/navigation.yml | 41 +- _data/ui-text.yml | 1618 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1652 insertions(+), 7 deletions(-) create mode 100644 _data/ui-text.yml diff --git a/_data/navigation.yml b/_data/navigation.yml index e3af8aec..2b38ccdd 100644 --- a/_data/navigation.yml +++ b/_data/navigation.yml @@ -1,9 +1,36 @@ main: - - title: "Posts" + +sidebar-main: + - title: "Artykuły" url: /posts/ - - title: "Categories" - url: /categories/ - - title: "Tags" - url: /tags/ - - title: "About" - url: /about/ \ No newline at end of file + children: + - title: "Po kategoriach" + url: /categories/ + - title: "Po językach" + url: /langs/ + - title: "Po narzędziach" + url: /tools/ + - title: "Po bibliotekach" + url: /libs/ + - title: "Po tagach" + url: /tags/ + - title: "Po projektach" + url: /projects/ + - title: "Eso" + url: /eso/ + - title: "Strony" + children: + - title: "Przeczytane ksiązki" + url: /books/ + - title: "O autorze" + url: /about/ + - title: "Polityka prywatności" + url: /privacy/ + - title: "Degustujaca Istota" + url: https://www.degustujacaistota.pl/ + - title: "Wyszukiwarki" + children: + - title: "Wyszukiwarka Jekyll" + url: /search-jekyll/ + - title: "Wyszukiwarka Jekyll Prosta" + url: /search-jekyll-simple/ diff --git a/_data/ui-text.yml b/_data/ui-text.yml new file mode 100644 index 00000000..e92f18f2 --- /dev/null +++ b/_data/ui-text.yml @@ -0,0 +1,1618 @@ +# User interface text and labels + +# English (default) +# ----------------- +en: &DEFAULT_EN + skip_links : "Skip links" + skip_primary_nav : "Skip to primary navigation" + skip_content : "Skip to content" + skip_footer : "Skip to footer" + page : "Page" + pagination_previous : "Previous" + pagination_next : "Next" + breadcrumb_home_label : "Home" + breadcrumb_separator : "/" + menu_label : "Toggle menu" + search_label : "Toggle search" + toc_label : "On this page" + ext_link_label : "Direct link" + less_than : "less than" + minute_read : "minute read" + share_on_label : "Share on" + meta_label : + tags_label : "Tags:" + categories_label : "Categories:" + date_label : "Updated:" + comments_label : "Leave a comment" + comments_title : "Comments" + more_label : "Learn more" + related_label : "You may also enjoy" + follow_label : "Follow:" + feed_label : "Feed" + powered_by : "Powered by" + website_label : "Website" + email_label : "Email" + recent_posts : "Recent posts" + undefined_wpm : "Undefined parameter words_per_minute at _config.yml" + comment_form_info : "Your email address will not be published. Required fields are marked" + comment_form_comment_label : "Comment" + comment_form_md_info : "Markdown is supported." + comment_form_name_label : "Name" + comment_form_email_label : "Email address" + comment_form_website_label : "Website (optional)" + comment_btn_submit : "Submit comment" + comment_btn_submitted : "Submitted" + comment_success_msg : "Thanks for your comment! It will show on the site once it has been approved." + comment_error_msg : "Sorry, there was an error with your submission. Please make sure all required fields have been completed and try again." + loading_label : "Loading..." + search_label_text : "Enter your search term..." + search_placeholder_text : "Enter your search term..." + results_found : "Result(s) found" + back_to_top : "Back to top" +en-US: + <<: *DEFAULT_EN +en-CA: + <<: *DEFAULT_EN +en-GB: + <<: *DEFAULT_EN +en-AU: + <<: *DEFAULT_EN + +# Spanish +# ------- +es: &DEFAULT_ES + skip_links : "Saltar enlaces" + skip_primary_nav : "Saltar a navegación principal" + skip_content : "Saltar a contenido" + skip_footer : "Saltar a pie" + page : "Página" + pagination_previous : "Anterior" + pagination_next : "Siguiente" + breadcrumb_home_label : "Inicio" + breadcrumb_separator : "/" + menu_label : "Alternar menú" + search_label : "Alternar búsqueda" + toc_label : "En esta página" + ext_link_label : "Enlace directo" + less_than : "menos de" + minute_read : "minuto(s) de lectura" + share_on_label : "Compartir en" + meta_label : + tags_label : "Etiquetas:" + categories_label : "Categorías:" + date_label : "Actualizado:" + comments_label : "Deja un comentario" + comments_title : "Comentarios" + more_label : "Ver más" + related_label : "Puede que también te interese" + follow_label : "Seguir:" + feed_label : "Feed" + powered_by : "Funciona con" + website_label : "Sitio web" + email_label : "Correo electrónico" + recent_posts : "Entradas recientes" + undefined_wpm : "El parámetro words_per_minute (palabras por minuto) no está definido en _config.yml" + comment_form_info : "Tu dirección de correo electrónico no se publicará. Los campos obligatorios están marcados" + comment_form_comment_label : "Comentario" + comment_form_md_info : "Puedes utilizar Markdown" + comment_form_name_label : "Nombre" + comment_form_email_label : "Dirección de correo electrónico" + comment_form_website_label : "Sitio web (opcional)" + comment_btn_submit : "Enviar comentario" + comment_btn_submitted : "Enviado" + comment_success_msg : "¡Gracias por tu comentario! Se publicará una vez sea aprobado." + comment_error_msg : "Ha ocurrido un error al enviar el comentario. Asegúrate de completar todos los campos obligatorios e inténtalo de nuevo." + loading_label : "Cargando..." + search_label_text : "Términos de búsqueda..." + search_placeholder_text : "Términos de búsqueda..." + results_found : "resultado(s) encontrado(s)" + back_to_top : "Volver arriba" +es-ES: + <<: *DEFAULT_ES +es-CO: + <<: *DEFAULT_ES + +# French +# ------ +fr: &DEFAULT_FR + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Page" + pagination_previous : "Précédent" + pagination_next : "Suivant" + breadcrumb_home_label : "Accueil" + breadcrumb_separator : "/" + menu_label : "Menu" + search_label : + toc_label : "Sur cette page" + ext_link_label : "Lien direct" + less_than : "moins de" + minute_read : "minute(s) de lecture" + share_on_label : "Partager sur" + meta_label : + tags_label : "Tags :" + categories_label : "Catégories :" + date_label : "Mis à jour :" + comments_label : "Laisser un commentaire" + comments_title : "Commentaires" + more_label : "Lire plus" + related_label : "Vous pourriez aimer aussi" + follow_label : "Contact" + feed_label : "Flux" + powered_by : "Propulsé par" + website_label : "Site" + email_label : "Email" + recent_posts : "Posts récents" + undefined_wpm : "Le paramètre words_per_minute n'est pas défini dans _config.yml" + comment_form_info : "Votre adresse email ne sera pas visible. Les champs obligatoires sont marqués" + comment_form_comment_label : "Commentaire" + comment_form_md_info : "Markdown est supporté." + comment_form_name_label : "Nom" + comment_form_email_label : "Adresse mail" + comment_form_website_label : "Site web (optionnel)" + comment_btn_submit : "Envoyer" + comment_btn_submitted : "Envoyé" + comment_success_msg : "Merci pour votre commentaire, il sera visible sur le site une fois approuvé." + comment_error_msg : "Désolé, une erreur est survenue lors de la soumission. Vérifiez que les champs obligatoires ont été remplis et réessayez." + loading_label : "Chargement..." + search_label_text : + search_placeholder_text : "Entrez votre recherche..." + results_found : "Résultat(s) trouvé(s)" + back_to_top : "Retour en haut" +fr-FR: + <<: *DEFAULT_FR +fr-BE: + <<: *DEFAULT_FR +fr-CH: + <<: *DEFAULT_FR + +# Turkish +# ------- +tr: &DEFAULT_TR + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Sayfa" + pagination_previous : "Önceki" + pagination_next : "Sonraki" + breadcrumb_home_label : "Ana Sayfa" + breadcrumb_separator : "/" + menu_label : + search_label : + toc_label : "İçindekiler" + ext_link_label : "Doğrudan Bağlantı" + less_than : "Şu süreden az: " + minute_read : "dakika tahmini okuma süresi" + share_on_label : "Paylaş" + meta_label : + tags_label : "Etiketler:" + categories_label : "Kategoriler:" + date_label : "Güncelleme tarihi:" + comments_label : "Yorum yapın" + comments_title : "Yorumlar" + more_label : "Daha fazlasını öğrenin" + related_label : "Bunlar ilginizi çekebilir:" + follow_label : "Takip et:" + feed_label : "RSS" + powered_by : "Emeği geçenler: " + website_label : "Web sayfası" + email_label : "E-posta" + recent_posts : "Son yazılar" + undefined_wpm : "_config.yml dosyasında tanımlanmamış words_per_minute parametresi" + comment_form_info : "Email adresiniz gösterilmeyecektir. Zorunlu alanlar işaretlenmiştir" + comment_form_comment_label : "Yorumunuz" + comment_form_md_info : "Markdown desteklenmektedir." + comment_form_name_label : "Adınız" + comment_form_email_label : "Email adresiniz" + comment_form_website_label : "Websiteniz (opsiyonel)" + comment_btn_submit : "Yorum Yap" + comment_btn_submitted : "Gönderildi" + comment_success_msg : "Yorumunuz için teşekkürler! Yorumunuz onaylandıktan sonra sitede gösterilecektir." + comment_error_msg : "Maalesef bir hata oluştu. Lütfen zorunlu olan tüm alanları doldurduğunuzdan emin olun ve sonrasında tekrar deneyin." + loading_label : "Yükleniyor..." + search_label_text : +tr-TR: + <<: *DEFAULT_TR + +# Portuguese +# ---------- +pt: &DEFAULT_PT + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Página" + pagination_previous : "Anterior" + pagination_next : "Seguinte" + breadcrumb_home_label : "Início" + breadcrumb_separator : "/" + menu_label : + search_label : + toc_label : "Nesta Página" + ext_link_label : "Link Direto" + less_than : "menos de" + minute_read : "minutos de leitura" + share_on_label : "Partilhar no" + meta_label : + tags_label : "Etiquetas:" + categories_label : "Categorias:" + date_label : "Atualizado:" + comments_label : "Deixe um Comentário" + comments_title : "Comentários" + more_label : "Saber mais" + related_label : "Também pode gostar de" + follow_label : "Siga:" + feed_label : "Feed" + powered_by : "Feito com" + website_label : "Site" + email_label : "Email" + recent_posts : "Artigos Recentes" + undefined_wpm : "Parâmetro words_per_minute não definido em _config.yml" + comment_form_info : "O seu endereço email não será publicado. Os campos obrigatórios estão assinalados" + comment_form_comment_label : "Comentário" + comment_form_md_info : "Markdown é suportado." + comment_form_name_label : "Nome" + comment_form_email_label : "Endereço Email" + comment_form_website_label : "Site (opcional)" + comment_btn_submit : "Sumbeter Comentário" + comment_btn_submitted : "Submetido" + comment_success_msg : "Obrigado pelo seu comentário! Será visível no site logo que aprovado." + comment_error_msg : "Lamento, ocorreu um erro na sua submissão. Por favor verifique se todos os campos obrigatórios estão corretamente preenchidos e tente novamente." + loading_label : "A carregar..." + search_label_text : +pt-PT: + <<: *DEFAULT_PT +# Brazilian Portuguese +pt-BR: + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Página" + pagination_previous : "Anterior" + pagination_next : "Próxima" + breadcrumb_home_label : "Home" + breadcrumb_separator : "/" + menu_label : + search_label : "Buscar" + toc_label : "Nesta página" + ext_link_label : "Link direto" + less_than : "menos que" + minute_read : "minuto(s) de leitura" + share_on_label : "Compartilhe" + meta_label : + tags_label : "Tags:" + categories_label : "Categorias:" + date_label : "Atualizado em:" + comments_label : "Deixe um comentário" + comments_title : "Comentários" + more_label : "Saiba mais" + related_label : "Talvez você também goste" + follow_label : "Acompanhe no" + feed_label : "Feed" + powered_by : "Desenvolvido com" + website_label : "Site" + email_label : "E-mail" + recent_posts : "Publicações recentes" + undefined_wpm : "Parâmetro words_per_minute indefinido no _config.yml" + comment_form_info : "Seu e-mail não será publicado. Os campos obrigatórios estão marcados" + comment_form_comment_label : "Comentário" + comment_form_md_info : "Você pode usar Markdown." + comment_form_name_label : "Nome" + comment_form_email_label : "E-mail" + comment_form_website_label : "Site (opcional)" + comment_btn_submit : "Enviar comentário" + comment_btn_submitted : "Enviado" + comment_success_msg : "Obrigado pelo seu comentário! Ele aparecerá no site assim que for aprovado." + comment_error_msg : "Desculpe, ocorreu um erro no envio. Verifique se todos os campos obrigatórios foram preenchidos e tente novamente." + loading_label : "Carregando..." + search_label_text : + search_placeholder_text : "Pesquisar..." + results_found : "Resultado(s) encontrado(s)" + back_to_top : "Voltar para o topo" + +# Italian +# ------- +it: &DEFAULT_IT + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Pagina" + pagination_previous : "Precedente" + pagination_next : "Prossima" + breadcrumb_home_label : "Home" + breadcrumb_separator : "/" + menu_label : + search_label : + toc_label : "Indice della pagina" + ext_link_label : "Link" + less_than : "meno di" + minute_read : "minuto/i di lettura" + share_on_label : "Condividi" + meta_label : + tags_label : "Tags:" + categories_label : "Categorie:" + date_label : "Aggiornato:" + comments_label : "Scrivi un commento" + comments_title : + more_label : "Scopri di più" + related_label : "Potrebbe Piacerti Anche" + follow_label : "Segui:" + feed_label : "Feed" + powered_by : "Powered by" + website_label : "Website" + email_label : "Email" + recent_posts : "Articoli Recenti" + undefined_wpm : "Parametro words_per_minute non definito in _config.yml" + comment_form_info : "Il tuo indirizzo email non sarà pubblicato. Sono segnati i campi obbligatori" + comment_form_comment_label : "Commenta" + comment_form_md_info : "Il linguaggio Markdown è supportato" + comment_form_name_label : "Nome" + comment_form_email_label : "Indirizzo email" + comment_form_website_label : "Sito Web (opzionale)" + comment_btn_submit : "Invia commento" + comment_btn_submitted : "Inviato" + comment_success_msg : "Grazie per il tuo commento! Verrà visualizzato nel sito una volta che sarà approvato." + comment_error_msg : "C'è stato un errore con il tuo invio. Assicurati che tutti i campi richiesti siano stati completati e riprova." + loading_label : "Caricamento..." + search_label_text : + search_placeholder_text : "Inserisci termini di ricerca..." + results_found : "Risultati" + back_to_top : "Vai su" +it-IT: + <<: *DEFAULT_IT + +# Chinese (zh-CN Chinese - China) +# -------------------------------- +zh: &DEFAULT_ZH_HANS + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "页面" + pagination_previous : "向前" + pagination_next : "向后" + breadcrumb_home_label : "首页" + breadcrumb_separator : "/" + menu_label : "切换菜单" + search_label : + toc_label : "在本页上" + ext_link_label : "直接链接" + less_than : "少于" + minute_read : "分钟阅读" + share_on_label : "分享" + meta_label : + tags_label : "标签:" + categories_label : "分类:" + date_label : "更新时间:" + comments_label : "留下评论" + comments_title : "评论" + more_label : "了解更多" + related_label : "猜您还喜欢" + follow_label : "关注:" + feed_label : "Feed" + powered_by : "技术来自于" + website_label : "网站" + email_label : "电子邮箱" + recent_posts : "最新文章" + undefined_wpm : "_config.yml 配置中 words_per_minute 字段未定义" + comment_form_info : "您的电子邮箱地址并不会被展示。请填写标记为必须的字段。" + comment_form_comment_label : "评论" + comment_form_md_info : "支持 Markdown 语法。" + comment_form_name_label : "姓名" + comment_form_email_label : "电子邮箱" + comment_form_website_label : "网站(可选)" + comment_btn_submit : "提交评论" + comment_btn_submitted : "已提交" + comment_success_msg : "感谢您的评论!被批准后它会立即在此站点展示。" + comment_error_msg : "很抱歉,您的提交存在错误。请确保所有必填字段都已填写正确,然后再试一次。" + loading_label : "正在加载..." + search_label_text : + search_placeholder_text : "输入您要搜索的关键词..." + results_found : "条记录匹配" + back_to_top : "返回顶部" +zh-CN: + <<: *DEFAULT_ZH_HANS +zh-SG: + <<: *DEFAULT_ZH_HANS +# Taiwan (Traditional Chinese) +zh-TW: &DEFAULT_ZH_HANT + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "頁面" + pagination_previous : "較舊" + pagination_next : "較新" + breadcrumb_home_label : "首頁" + breadcrumb_separator : "/" + menu_label : "切換選單" + search_label : + toc_label : "本頁" + ext_link_label : "外部連結" + less_than : "少於" + minute_read : "分鐘閱讀" + share_on_label : "分享到" + meta_label : + tags_label : "標籤:" + categories_label : "分類:" + date_label : "更新時間:" + comments_label : "留言" + comments_title : "留言內容" + more_label : "了解更多" + related_label : "猜您有與趣" + follow_label : "追蹤:" + feed_label : "RSS Feed" + powered_by : "Powered by" + website_label : "網站" + email_label : "電子信箱" + recent_posts : "最新文章" + undefined_wpm : "_config.yml 中未定義 words_per_minute" + comment_form_info : "您的電子信箱不會被公開. 必填部份已標記" + comment_form_comment_label : "留言內容" + comment_form_md_info : "支援Markdown語法。" + comment_form_name_label : "名字" + comment_form_email_label : "電子信箱帳號" + comment_form_website_label : "網頁 (可選填)" + comment_btn_submit : "送出留言" + comment_btn_submitted : "已送出" + comment_success_msg : "感謝您的留言! 審核後將會顯示在站上。" + comment_error_msg : "抱歉,部份資料輸入有問題。請確認資料填寫正確後再試一次。" + loading_label : "載入中..." + search_label_text : +zh-HK: + <<: *DEFAULT_ZH_HANT + +# German / Deutsch +# ---------------- +de: &DEFAULT_DE + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Seite" + pagination_previous : "Vorherige" + pagination_next : "Nächste" + breadcrumb_home_label : "Start" + breadcrumb_separator : "/" + menu_label : "Menü ein-/ausschalten" + search_label : + toc_label : "Auf dieser Seite" + ext_link_label : "Direkter Link" + less_than : "weniger als" + minute_read : "Minuten zum lesen" + share_on_label : "Teilen auf" + meta_label : + tags_label : "Tags:" + categories_label : "Kategorien:" + date_label : "Aktualisiert:" + comments_label : "Hinterlassen Sie einen Kommentar" + comments_title : "Kommentare" + more_label : "Mehr anzeigen" + related_label : "Ihnen gefällt vielleicht auch" + follow_label : "Folgen:" + feed_label : "Feed" + powered_by : "Möglich durch" + website_label : "Webseite" + email_label : "E-Mail" + recent_posts : "Aktuelle Beiträge" + undefined_wpm : "Undefinierter Parameter words_per_minute in _config.yml" + comment_form_info : "Ihre E-Mail Adresse wird nicht veröffentlicht. Benötigte Felder sind markiert" + comment_form_comment_label : "Kommentar" + comment_form_md_info : "Markdown wird unterstützt." + comment_form_name_label : "Name" + comment_form_email_label : "E-Mail-Addresse" + comment_form_website_label : "Webseite (optional)" + comment_btn_submit : "Kommentar absenden" + comment_btn_submitted : "Versendet" + comment_success_msg : "Danke für Ihren Kommentar! Er wird auf der Seite angezeigt, nachdem er geprüft wurde." + comment_error_msg : "Entschuldigung, es gab einen Fehler. Bitte füllen Sie alle benötigten Felder aus und versuchen Sie es erneut." + loading_label : "Lade..." + search_label_text : + search_placeholder_text : "Suchbegriff eingeben..." + results_found : "Ergebnis(se) gefunden" +de-DE: + <<: *DEFAULT_DE +de-AT: + <<: *DEFAULT_DE +de-CH: + <<: *DEFAULT_DE +de-BE: + <<: *DEFAULT_DE +de-LI: + <<: *DEFAULT_DE +de-LU: + <<: *DEFAULT_DE + +# Nepali (Nepal) +# -------------- +ne: &DEFAULT_NE + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "पृष्‍ठ" + pagination_previous : "अघिल्लो" + pagination_next : "अर्को" + breadcrumb_home_label : "गृह" + breadcrumb_separator : "/" + menu_label : "टगल मेनु" + search_label : + toc_label : "यो पृष्‍ठमा" + ext_link_label : "सिधा सम्पर्क" + less_than : "कम्तिमा" + minute_read : "मिनेट पढ्नुहोस्" + share_on_label : "शेयर गर्नुहोस्" + meta_label : + tags_label : "ट्यागहरू:" + categories_label : "वर्गहरु:" + date_label : "अद्यावधिक:" + comments_label : "टिप्पणी दिनुहोस्" + comments_title : "टिप्पणीहरू" + more_label : "अझै सिक्नुहोस्" + related_label : "तपाईं रुचाउन सक्नुहुन्छ " + follow_label : "पछ्याउनुहोस्:" + feed_label : "फिड" + powered_by : "Powered by" + website_label : "वेबसाइट" + email_label : "इमेल" + recent_posts : "ताजा लेखहरु" + undefined_wpm : "अपरिभाषित प्यारामिटर शब्दहरू_प्रति_मिनेट at _config.yml" + comment_form_info : "तपाइँको इमेल ठेगाना प्रकाशित गरिने छैन।आवश्यक जानकारीहरुमा चिन्ह लगाइको छ" + comment_form_comment_label : "टिप्पणी" + comment_form_md_info : "मार्कडाउन समर्थित छ।" + comment_form_name_label : "नाम" + comment_form_email_label : "इमेल ठेगाना" + comment_form_website_label : "वेबसाइट (वैकल्पिक)" + comment_btn_submit : "टिप्पणी दिनुहोस् " + comment_btn_submitted : "टिप्पणी भयो" + comment_success_msg : "तपाईंको टिप्पणीको लागि धन्यवाद! एक पटक यो अनुमोदन गरेपछी यो साइटमा देखाउनेछ।" + comment_error_msg : "माफ गर्नुहोस्, तपाईंको टिप्पणी त्रुटि थियो।सबै आवश्यक जानकारीहरु पूरा गरिएको छ भने निश्चित गर्नुहोस् र फेरि प्रयास गर्नुहोस्।" + loading_label : "लोड हुँदैछ ..." + search_label_text : +ne-NP: + <<: *DEFAULT_NE + +# Korean +# ------ +ko: &DEFAULT_KO + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "페이지" + pagination_previous : "이전" + pagination_next : "다음" + breadcrumb_home_label : "Home" + breadcrumb_separator : "/" + menu_label : "토글 메뉴" + search_label : + toc_label : "On This Page" + ext_link_label : "직접 링크" + less_than : "최대" + minute_read : "분 소요" + share_on_label : "공유하기" + meta_label : + tags_label : "태그:" + categories_label : "카테고리:" + date_label : "업데이트:" + comments_label : "댓글남기기" + comments_title : "댓글" + more_label : "더 보기" + related_label : "참고" + follow_label : "팔로우:" + feed_label : "피드" + powered_by : "Powered by" + website_label : "웹사이트" + email_label : "이메일" + recent_posts : "최근 포스트" + undefined_wpm : "Undefined parameter words_per_minute at _config.yml" + comment_form_info : "이메일은 공개되지 않습니다. 작성 필요 필드:" + comment_form_comment_label : "댓글" + comment_form_md_info : "마크다운을 지원합니다." + comment_form_name_label : "이름" + comment_form_email_label : "이메일" + comment_form_website_label : "웹사이트(선택사항)" + comment_btn_submit : "댓글 등록" + comment_btn_submitted : "등록됨" + comment_success_msg : "감사합니다! 댓글이 머지된 후 확인하실 수 있습니다." + comment_error_msg : "댓글 등록에 문제가 있습니다. 필요 필드를 작성했는지 확인하고 다시 시도하세요." + loading_label : "로딩중..." + search_label_text : + search_placeholder_text : "검색어를 입력하세요..." + results_found : "개 결과 발견" + back_to_top : "맨 위로 이동" +ko-KR: + <<: *DEFAULT_KO + +# Russian / Русский +# ----------------- +ru: &DEFAULT_RU + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Страница" + pagination_previous : "Предыдущая" + pagination_next : "Следующая" + breadcrumb_home_label : "Главная" + breadcrumb_separator : "/" + menu_label : "Выпадающее меню" + search_label : + toc_label : "Содержание" + ext_link_label : "Прямая ссылка" + less_than : "менее" + minute_read : "мин на чтение" + share_on_label : "Поделиться" + meta_label : + tags_label : "Метки:" + categories_label : "Разделы:" + date_label : "Дата изменения:" + comments_label : "Оставить комментарий" + comments_title : "Комментарии" + more_label : "Читать далее" + related_label : "Вам также может понравиться" + follow_label : "Связаться со мной:" + feed_label : "RSS-лента" + powered_by : "Сайт работает на" + website_label : "Сайт" + email_label : "Электронная почта" + recent_posts : "Свежие записи" + undefined_wpm : "Не определён параметр words_per_minute в _config.yml" + comment_form_info : "Ваш адрес электронной почты не будет опубликован. Обязательные поля помечены" + comment_form_comment_label : "Комментарий" + comment_form_md_info : "Поддерживается синтаксис Markdown." + comment_form_name_label : "Имя" + comment_form_email_label : "Электронная почта" + comment_form_website_label : "Ссылка на сайт (необязательно)" + comment_btn_submit : "Оставить комментарий" + comment_btn_submitted : "Отправлено" + comment_success_msg : "Спасибо за Ваш комментарий! Он будет опубликован на сайте после проверки." + comment_error_msg : "К сожалению, произошла ошибка с отправкой комментария. Пожалуйста, убедитесь, что все обязательные поля заполнены и попытайтесь снова." + loading_label : "Отправка..." + search_label_text : + search_placeholder_text : "Введите поисковый запрос..." + results_found : "Найдено" +ru-RU: + <<: *DEFAULT_RU + +# Lithuanian / Lietuviškai +# ------------------------ +lt: &DEFAULT_LT + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Puslapis" + pagination_previous : "Ankstesnis" + pagination_next : "Sekantis" + breadcrumb_home_label : "Pagrindinis" + breadcrumb_separator : "/" + menu_label : "Meniu rodymas" + search_label : + toc_label : "Turinys" + ext_link_label : "Tiesioginė nuoroda" + less_than : "mažiau nei" + minute_read : "min. skaitymo" + share_on_label : "Pasidalinti" + meta_label : + tags_label : "Žymės:" + categories_label : "Kategorijos:" + date_label : "Atnaujinta:" + comments_label : "Palikti komentarą" + comments_title : "Komentaras" + more_label : "Skaityti daugiau" + related_label : "Taip pat turėtų patikti" + follow_label : "Sekti:" + feed_label : "Šaltinis" + powered_by : "Sukurta su" + website_label : "Tinklapis" + email_label : "El. paštas" + recent_posts : "Naujausi įrašai" + undefined_wpm : "Nedeklaruotas parametras words_per_minute faile _config.yml" + comment_form_info : "El. pašto adresas nebus viešinamas. Būtini laukai pažymėti" + comment_form_comment_label : "Komentaras" + comment_form_md_info : "Markdown palaikomas." + comment_form_name_label : "Vardas" + comment_form_email_label : "El. paštas" + comment_form_website_label : "Tinklapis (nebūtina)" + comment_btn_submit : "Komentuoti" + comment_btn_submitted : "Įrašytas" + comment_success_msg : "Ačiū už komentarą! Jis bus parodytas kai bus patvirtintas." + comment_error_msg : "Atleiskite, įvyko netikėta klaida įrašant komentarą. Pasitikrinkite ar užpildėte visus būtinus laukus ir pamėginkite dar kartą." + loading_label : "Kraunama..." + search_label_text : +lt-LT: + <<: *DEFAULT_LT + +# Greek +# ----- +gr: &DEFAULT_GR + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Σελίδα" + pagination_previous : "Προηγούμενo" + pagination_next : "Επόμενo" + breadcrumb_home_label : "Αρχική" + breadcrumb_separator : "/" + menu_label : "Μενού" + search_label : + toc_label : "Περιεχόμενα" + ext_link_label : "Εξωτερικός Σύνδεσμος" + less_than : "Λιγότερο από" + minute_read : "λεπτά ανάγνωσης" + share_on_label : "Μοιραστείτε το" + meta_label : + tags_label : "Ετικέτες:" + categories_label : "Κατηγορίες:" + date_label : "Ενημερώθηκε:" + comments_label : "Αφήστε ένα σχόλιο" + comments_title : "Σχόλια" + more_label : "Διάβαστε περισσότερα" + related_label : "Σχετικές αναρτήσεις" + follow_label : "Ακολουθήστε:" + feed_label : "RSS Feed" + powered_by : "Δημιουργήθηκε με" + website_label : "Ιστοσελίδα" + email_label : "Email" + recent_posts : "Τελευταίες αναρτήσεις" + undefined_wpm : "Δεν έχει οριστεί η παράμετρος words_per_minute στο αρχείο _config.yml" + comment_form_info : "Η διεύθυνση email σας δεν θα δημοσιευθεί. Τα απαιτούμενα πεδία εμφανίζονται με αστερίσκο" + comment_form_comment_label : "Σχόλιο" + comment_form_md_info : "Το πεδίο υποστηρίζει Markdown." + comment_form_name_label : "Όνομα" + comment_form_email_label : "Διεύθυνση email" + comment_form_website_label : "Ιστοσελίδα (προαιρετικό)" + comment_btn_submit : "Υπόβαλε ένα σχόλιο" + comment_btn_submitted : "Έχει υποβληθεί" + comment_success_msg : "Ευχαριστούμε για το σχόλιό σας! Θα εμφανιστεί στην ιστοσελίδα αφού εγκριθεί." + comment_error_msg : "Λυπούμαστε, παρουσιάστηκε σφάλμα με την υποβολή σας. Παρακαλούμε βεβαιωθείτε ότι έχετε όλα τα απαιτούμενα πεδία συμπληρωμένα και δοκιμάστε ξανά." + loading_label : "Φόρτωση..." + search_label_text : + search_placeholder_text : "Εισάγετε όρο αναζήτησης..." + results_found : "Αποτελέσματα" +gr-GR: + <<: *DEFAULT_GR + +# Swedish +# ------- +sv: &DEFAULT_SV + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Sidan" + pagination_previous : "Föregående" + pagination_next : "Nästa" + breadcrumb_home_label : "Hem" + breadcrumb_separator : "/" + menu_label : "Växla menyläge" + search_label : "Växla sökläge" + toc_label : "På denna sida" + ext_link_label : "Direkt länk" + less_than : "mindre än" + minute_read : "minut läsning" + share_on_label : "Dela på" + meta_label : + tags_label : "Taggar:" + categories_label : "Kategorier:" + date_label : "Uppdaterades:" + comments_label : "Lämna en kommentar" + comments_title : "Kommentarer" + more_label : "Lär dig mer" + related_label : "Du kanske vill även läsa:" + follow_label : "Följ:" + feed_label : "Flöde" + powered_by : "Framställd med" + website_label : "Webbsida" + email_label : "E-post" + recent_posts : "Senaste inlägg" + undefined_wpm : "Odefinerade parametrar words_per_minute i _config.yml" + comment_form_info : "Din e-post adress kommer inte att publiceras. Obligatoriska fält är markerade" + comment_form_comment_label : "Kommentar" + comment_form_md_info : "Stöd för Markdown finns." + comment_form_name_label : "Namn" + comment_form_email_label : "E-post adress" + comment_form_website_label : "Webdsida (valfritt)" + comment_btn_submit : "Skicka en kommentar" + comment_btn_submitted : "Kommentaren har tagits emot" + comment_success_msg : "Tack för din kommentar! Den kommer att visas på sidan så fort den har godkännts." + comment_error_msg : "Tyvärr det har blivit något fel i ett av fälten, se till att du fyllt i alla obligatoriska fält och försök igen." + loading_label : "Laddar..." + search_label_text : + search_placeholder_text : "Fyll i sökterm..." + results_found : "Resultat funna" + back_to_top : "Tillbaka till toppen" +sv-SE: + <<: *DEFAULT_SV +sv-FI: + <<: *DEFAULT_SV + +# Dutch +# ----- +nl: &DEFAULT_NL + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Pagina" + pagination_previous : "Vorige" + pagination_next : "Volgende" + breadcrumb_home_label : "Home" + breadcrumb_separator : "/" + menu_label : "Wissel Menu" + search_label : + toc_label : "Op deze pagina" + ext_link_label : "Directe Link" + less_than : "minder dan" + minute_read : "minuut gelezen" + share_on_label : "Deel op" + meta_label : + tags_label : "Labels:" + categories_label : "Categorieën:" + date_label : "Bijgewerkt:" + comments_label : "Laat een reactie achter" + comments_title : "Commentaren" + more_label : "Meer informatie" + related_label : "Bekijk ook eens" + follow_label : "Volg:" + feed_label : "Feed" + powered_by : "Aangedreven door" + website_label : "Website" + email_label : "Email" + recent_posts : "Recente berichten" + undefined_wpm : "Niet gedefinieerde parameter words_per_minute bij _config.yml" + comment_form_info : "Uw e-mailadres wordt niet gepubliceerd. Verplichte velden zijn gemarkeerd" + comment_form_comment_label : "Commentaar" + comment_form_md_info : "Markdown wordt ondersteund." + comment_form_name_label : "Naam" + comment_form_email_label : "E-mailadres" + comment_form_website_label : "Website (optioneel)" + comment_btn_submit : "Commentaar toevoegen" + comment_btn_submitted : "Toegevoegd" + comment_success_msg : "Bedankt voor uw reactie! Het zal op de site worden weergegeven zodra het is goedgekeurd." + comment_error_msg : "Sorry, er is een fout opgetreden bij uw inzending. Zorg ervoor dat alle vereiste velden zijn voltooid en probeer het opnieuw." + loading_label : "Laden..." + search_label_text : "Geef uw zoekterm in..." + search_placeholder_text : "Geef uw zoekterm in..." + results_found : "Resultaat gevonden" + back_to_top : "Terug naar boven" +nl-BE: + <<: *DEFAULT_NL +nl-NL: + <<: *DEFAULT_NL + +# Indonesian +# ---------- +id: &DEFAULT_ID + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Halaman" + pagination_previous : "Kembali" + pagination_next : "Maju" + breadcrumb_home_label : "Home" + breadcrumb_separator : "/" + menu_label : "Menu Toggle" + search_label : + toc_label : "Pada Halaman Ini" + ext_link_label : "Link langsung" + less_than : "Kurang dari" + minute_read : "Waktu baca" + share_on_label : "Berbagi di" + meta_label : + tags_label : "Golongan:" + categories_label : "Kategori:" + date_label : "Diupdate:" + comments_label : "Tinggalkan komentar" + comments_title : "Komentar" + more_label : "Pelajari lagi" + related_label : "Anda juga akan suka" + follow_label : "Ikuti:" + feed_label : "Feed" + powered_by : "Didukung oleh" + website_label : "Website" + email_label : "Email" + recent_posts : "Posting terbaru" + undefined_wpm : "Parameter terdeskripsi words_per_minute di _config.yml" + comment_form_info : "Email Anda tidak akan dipublish. Kolom yang diperlukan ditandai" + comment_form_comment_label : "Komentar" + comment_form_md_info : "Markdown disupport." + comment_form_name_label : "Nama" + comment_form_email_label : "Alamat email" + comment_form_website_label : "Website (opsional)" + comment_btn_submit : "Submit Komentar" + comment_btn_submitted : "Telah disubmit" + comment_success_msg : "Terimakasih atas komentar Anda! Komentar ini akan tampil setelah disetujui." + comment_error_msg : "Maaf, ada kesalahan pada submisi Anda. Pastikan seluruh kolom sudah dilengkapi dan coba kembali." + loading_label : "Sedang meload..." + search_label_text : +id-ID: + <<: *DEFAULT_ID + +# Vietnamese +# ---------- +vi: &DEFAULT_VI + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Trang" + pagination_previous : "Trước" + pagination_next : "Sau" + breadcrumb_home_label : "Trang chủ" + breadcrumb_separator : "/" + menu_label : "Menu" + search_label : + toc_label : "Tại trang này" + ext_link_label : "Đường dẫn trực tiếp" + less_than : "nhỏ hơn" + minute_read : "phút đọc" + share_on_label : "Chia sẻ tại" + meta_label : + tags_label : "Nhãn:" + categories_label : "Chủ đề:" + date_label : "Cập nhật:" + comments_label : "Để lại bình luận" + comments_title : "Bình luận" + more_label : "Mở rộng" + related_label : "Có thể bạn cũng thích" + follow_label : "Theo dõi:" + feed_label : "Feed" + powered_by : "Được hỗ trợ bởi" + website_label : "Website" + email_label : "Email" + recent_posts : "Bài viết mới" + undefined_wpm : "Chưa định nghĩa thông số words_per_minute tại _config.yml" + comment_form_info : "Email của bạn sẽ được giữ bí mật. Các phần bắt buộc được đánh dấu" + comment_form_comment_label : "Bình luận" + comment_form_md_info : "Hỗ trợ Markdown." + comment_form_name_label : "Tên" + comment_form_email_label : "Địa chỉ email" + comment_form_website_label : "Website (không bắt buộc)" + comment_btn_submit : "Gửi bình luận" + comment_btn_submitted : "Đã được gửi" + comment_success_msg : "Cảm ơn bạn đã bình luận! Bình luận sẽ xuất hiện sau khi được duyệt." + comment_error_msg : "Rất tiếc, có lỗi trong việc gửi bình luận. Hãy đảm bảo toàn bộ các phần bắt buộc đã được điền đầy đủ và thử lại." + loading_label : "Đang tải..." + search_label_text : + search_placeholder_text : "Nhập từ khóa cần tìm..." + results_found : "Kết quả tìm được" + back_to_top : "Lên đầu trang" +vi-VN: + <<: *DEFAULT_VI + +# Danish +# ------ +da: &DEFAULT_DA + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Side" + pagination_previous : "Forrige" + pagination_next : "Næste" + breadcrumb_home_label : "Home" + breadcrumb_separator : "/" + menu_label : "Vis/skjul menu" + search_label : + toc_label : "På denne side" + ext_link_label : "Direkte link" + less_than : "mindre end" + minute_read : "minutters læsning" + share_on_label : "Del på" + meta_label : + tags_label : "Nøgleord:" + categories_label : "Kategorier:" + date_label : "Opdateret:" + comments_label : "Skriv en kommentar" + comments_title : "Kommentarer" + more_label : "Lær mere" + related_label : "Måske kan du også lide" + follow_label : "Følg:" + feed_label : "Feed" + powered_by : "Drives af" + website_label : "Website" + email_label : "E-mail" + recent_posts : "Seneste indlæg" + undefined_wpm : "Parameteren words_per_minute er ikke defineret i _config.yml" + comment_form_info : "Din e-mail bliver ikke offentliggjort. Obligatoriske felter er markeret" + comment_form_comment_label : "Kommentar" + comment_form_md_info : "Markdown er understøttet." + comment_form_name_label : "Navn" + comment_form_email_label : "E-mail" + comment_form_website_label : "Website (frivillig)" + comment_btn_submit : "Send kommentar" + comment_btn_submitted : "Sendt" + comment_success_msg : "Tak for din kommentar! Den bliver vist på siden, så snart den er godkendt." + comment_error_msg : "Desværre skete der en fejl. Prøv igen, mens du sørger for at alle obligatoriske felter er udfyldt." + loading_label : "Indlæser..." + search_label_text : + search_placeholder_text : "Hvad leder du efter..." + results_found : "Resultat(er) fundet" + back_to_top : "Tilbage til toppen" +da-DK: + <<: *DEFAULT_DA + +# Polish +# ------ +pl: &DEFAULT_PL + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Strona" + pagination_previous : "Poprzednia" + pagination_next : "Następna" + breadcrumb_home_label : "Strona główna" + breadcrumb_separator : "/" + menu_label : "Przełącz menu" + search_label : + toc_label : "Spis treści" + ext_link_label : "Link bezpośredni" + less_than : "mniej niż" + minute_read : "minut(y)" + share_on_label : "Udostępnij" + meta_label : + tags_label : "Tagi:" + categories_label : "Kategorie:" + date_label : "Ostatnia aktualizacja:" + comments_label : "Zostaw komentarz" + comments_title : "Komentarze" + more_label : "Dowiedz się więcej" + related_label : "Także może Ci się spodobać" + follow_label : "Śledź:" + feed_label : "Feed" + powered_by : "Powstało dzięki" + website_label : "Strona" + email_label : "Email" + recent_posts : "Najnowsze wpisy" + undefined_wpm : "Parametr words_per_minute nie został zdefiniowany w _config.yml." + comment_form_info : "Twój adres email nie będzie udostępiony. Wymagane pola są oznaczone" + comment_form_comment_label : "Skomentuj" + comment_form_md_info : "Markdown jest wspierany" + comment_form_name_label : "Imię" + comment_form_email_label : "Adres email" + comment_form_website_label : "Strona www (opcjonalna)" + comment_btn_submit : "Skomentuj" + comment_btn_submitted : "Komentarz dodany" + comment_success_msg : "Dziękuję za Twój komentarz! Zostanie dodany po akceptacji." + comment_error_msg : "Niestety wystąpił błąd. Proszę upewnij się, że wszystkie wymagane pola zostały wypełnione i spróbuj ponownie." + loading_label : "Trwa ładowanie strony..." + search_label_text : + posts_older : "Starsze artykuły" + posts_newer : "Nowsze artykuły" + back_to_top : "Wróc do góry" + go_to_comments : "Idź do komentarzy" + go_to_post : "Idź do artykułu" +pl-PL: + <<: *DEFAULT_PL + +# Japanese +# -------- +ja: &DEFAULT_JA + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "ページ" + pagination_previous : "前へ" + pagination_next : "次へ" + breadcrumb_home_label : "ホーム" + breadcrumb_separator : "/" + menu_label : "メニュー" + search_label : + toc_label : "目次" + ext_link_label : "リンク" + less_than : + minute_read : + share_on_label : "共有" + meta_label : + tags_label : "タグ:" + categories_label : "カテゴリー:" + date_label : "更新日時:" + comments_label : "コメントする" + comments_title : "コメント" + more_label : "さらに詳しく" + related_label : "関連記事" + follow_label : "フォロー" + feed_label : + powered_by : + website_label : + email_label : + recent_posts : "最近の投稿" + undefined_wpm : "パラメータ words_per_minute が _config.yml で定義されていません" + comment_form_info : "メールアドレスが公開されることはありません。次の印のある項目は必ず入力してください:" + comment_form_comment_label : "コメント" + comment_form_md_info : "Markdown を使用できます" + comment_form_name_label : "名前" + comment_form_email_label : "メールアドレス" + comment_form_website_label : "URL (任意)" + comment_btn_submit : "コメントを送信する" + comment_btn_submitted : "送信しました" + comment_success_msg : "コメントありがとうございます! コメントは承認されるとページに表示されます。" + comment_error_msg : "送信エラーです。必須項目がすべて入力されていることを確認して再送信してください。" + loading_label : "読み込み中..." + search_label_text : + search_placeholder_text : "検索キーワードを入力してください..." + results_found : "件" +ja-JP: + <<: *DEFAULT_JA + +# Slovak +# ----------------- +sk: &DEFAULT_SK + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Stránka" + pagination_previous : "Predošlá" + pagination_next : "Ďalšia" + breadcrumb_home_label : "Domov" + breadcrumb_separator : "/" + menu_label : "Menu" + search_label : + toc_label : "Obsah" + ext_link_label : "Priamy odkaz" + less_than : "menej ako" + minute_read : "minút" + share_on_label : "Zdieľaj na" + meta_label : + tags_label : "Tagy:" + categories_label : "Kategórie:" + date_label : "Aktualizované:" + comments_label : "Zanechaj odkaz" + comments_title : "Komentáre" + more_label : "Dozvedieť sa viac" + related_label : "Podobné články" + follow_label : "Sleduj:" + feed_label : "Zoznam" + powered_by : "Stránka vytvorená pomocou" + website_label : "Web stránka" + email_label : "Email" + recent_posts : "Najnovšie príspevky" + undefined_wpm : "Nedefinovaný parameter words_per_minute v _config.yml" + comment_form_info : "Tvoja emailová adresa nebude publikovaná. Požadované polia sú označené" + comment_form_comment_label : "Komentár" + comment_form_md_info : "Markdown je podporovaný." + comment_form_name_label : "Meno" + comment_form_email_label : "Emailová adresa" + comment_form_website_label : "Webstránka (voliteľné)" + comment_btn_submit : "Vlož komentár" + comment_btn_submitted : "Vložený" + comment_success_msg : "Ďakujem za tvoj komentár! Po schválení bude zobrazený na stránke." + comment_error_msg : "Prepáč, pri ukladaní nastala chyba. Ubezpeč sa prosím, že si vyplnil všetky požadované polia a skús znova." + loading_label : "Načítava sa..." + search_label_text : + search_placeholder_text : "Zadaj hľadaný výraz..." + results_found : "Nájdených výsledkov" + back_to_top : "Na začiatok stránky" +sk-SK: + <<: *DEFAULT_SK + +# Hungarian +# ----------------- +hu: &DEFAULT_HU + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Oldal" + pagination_previous : "Előző" + pagination_next : "Következő" + breadcrumb_home_label : "Kezdőlap" + breadcrumb_separator : "/" + menu_label : "Menü nyit/zár" + search_label : + toc_label : "Ezen az oldalon" + ext_link_label : "Közvetlen Link" + less_than : "kevesebb mint" + minute_read : "eltöltött percek" + share_on_label : "Megosztás" + meta_label : + tags_label : "Tagek:" + categories_label : "Kategóriák:" + date_label : "Frissítve:" + comments_label : "Szólj hozzá!" + comments_title : "Hozzászólások" + more_label : "Tovább" + related_label : "Ajánlások" + follow_label : "Követés:" + feed_label : "Folyam" + powered_by : "Powered by" + website_label : "Honlap" + email_label : "Email" + recent_posts : "Friss cikkek" + undefined_wpm : "Ismeretlen paraméter words_per_minute : _config.yml" + comment_form_info : "Az e-mail címed nem lesz publikus. A csillagozott mezők kitöltése kötelező" + comment_form_comment_label : "Hozzászólás" + comment_form_md_info : "Támogatott formázási mód: Markdown" + comment_form_name_label : "Név" + comment_form_email_label : "Email cím" + comment_form_website_label : "Honlap (nem kötelező):" + comment_btn_submit : "Hozzászólás elküldése" + comment_btn_submitted : "Hozzászólás elküldve" + comment_success_msg : "Köszönjük a Hozzászólást! A Hozzászólások csak előzetes moderáció után lesznek publikusak." + comment_error_msg : "Hoppá, hiba történt a beküldés közben. Kérlek ellenőrizd hogy minden kötelező mező ki van-e töltve." + loading_label : "Betöltés..." + search_label_text : + search_placeholder_text : "Keresendő szöveg..." + results_found : "Találatok:" + back_to_top : "Oldal tetejére" +hu-HU: + <<: *DEFAULT_HU + +# Romanian +# ----------------- +ro: &DEFAULT_RO + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "Pagina" + pagination_previous : "Anterior" + pagination_next : "Următor" + breadcrumb_home_label : "Acasă" + breadcrumb_separator : "/" + menu_label : "Comută meniul" + search_label : + toc_label : "Pe această pagină" + ext_link_label : "Link direct" + less_than : "mai puțin de" + minute_read : "minute de citit" + share_on_label : "Distribuie pe" + meta_label : + tags_label : "Etichete:" + categories_label : "Categorii:" + date_label : "Actualizat:" + comments_label : "Lasă un comentariu" + comments_title : "Comentarii" + more_label : "Citește mai departe" + related_label : "S-ar putea să-ți placă" + follow_label : "Urmărește:" + feed_label : "Feed RSS" + powered_by : "Cu sprijinul" + website_label : "Site" + email_label : "Email" + recent_posts : "Articole recente" + undefined_wpm : "Parametru words_per_minute nedefinit în _config.yml" + comment_form_info : "Adresa ta de email nu va fi făcută publică. Câmpurile marcate sunt obligatorii" + comment_form_comment_label : "Comentariu" + comment_form_md_info : "Markdown este suportat." + comment_form_name_label : "Nume" + comment_form_email_label : "Adresă de email" + comment_form_website_label : "Site (opțional)" + comment_btn_submit : "Trimite comentariul" + comment_btn_submitted : "Trimis" + comment_success_msg : "Mulțumesc pentru comentariu! Va apărea pe site în momentul în care va fi aprobat." + comment_error_msg : "Scuze, este o problemă cu comentariul tău. Asigură-te că toate câmpurile obligatorii au fost completate și încearcă din nou." + loading_label : "Se încarcă..." + search_label_text : + search_placeholder_text : "Caută ceva..." + results_found : "Rezultate găsite" + back_to_top : "Înapoi în susul paginii" +ro-RO: + <<: *DEFAULT_RO + +# Punjabi +# ----------------- +pa: &DEFAULT_PA + skip_links : "ਲਿੰਕ ਛੱਡੋ" + skip_primary_nav : "ਮੂਲ ਮਾਰਗ ਛੱਡੋ" + skip_content : "ਸਮੱਗਰੀ ਛੱਡੋ" + skip_footer : "ਅੰਤ ਵਿਚ ਲਿਖਿਆ ਛੱਡੋ" + page : "ਸਫ਼ਾ" + pagination_previous : "ਪਿਛਲਾ" + pagination_next : "ਅਗਲਾ " + breadcrumb_home_label : "ਘਰ" + breadcrumb_separator : "/" + menu_label : "ਟੌਗਲ ਮੀਨੂ" + search_label : "ਖੋਜ" + toc_label : "ਇਸ ਸਫ਼ੇ 'ਤੇ" + ext_link_label : "ਸਿੱਧਾ ਸੰਪਰਕ" + less_than : "ਤੋਂ ਘੱਟ" + minute_read : "ਮਿੰਟ ਵਿੱਚ ਪੜਿਆ ਜਾ ਸਕਦਾ ਹੈ" + share_on_label : "ਸਾਂਝਾ ਕਰੋ" + meta_label : "ਸਵੈ-ਸੰਦਰਭ ਜਾਣਕਾਰੀ" + tags_label : "ਟੈਗ" + categories_label : "ਵਰਗ" + date_label : "ਅਪਡੇਟ ਕੀਤਾ:" + comments_label : "ਇੱਕ ਟਿੱਪਣੀ ਛੱਡੋ" + comments_title : "ਟਿੱਪਣੀਆਂ" + more_label : "ਹੋਰ ਜਾਣੋ" + related_label : "ਤੁਸੀਂ ਇਸਦਾ ਆਨੰਦ ਵੀ ਲੈ ਸਕਦੇ ਹੋ" + follow_label : "ਫਾਲੋ ਅੱਪ ਕਰੋ:" + feed_label : "ਫੀਡ" + powered_by : "ਦੁਆਰਾ ਸੰਚਾਲਿਤ" + website_label : "ਵੈੱਬਸਾਇਟ" + email_label : "ਈਮੇਲ" + recent_posts : "ਹਾਲ ਹੀ ਦੇ ਪੋਸਟ" + undefined_wpm : "_config.yml ਤੇ ਅਣ-ਪ੍ਰਭਾਸ਼ਿਤ ਪੈਰਾਮੀਟਰ words_per_minute" + comment_form_info : "ਤੁਹਾਡਾ ਈਮੇਲ ਪਤਾ ਪ੍ਰਕਾਸ਼ਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ। ਅਨੁਮਾਨਿਤ ਸਥਾਨਾਂ ਨੂੰ ਅੰਡਰਲਾਈਨ ਕੀਤਾ ਗਿਆ ਹੈ" + comment_form_comment_label : "ਟਿੱਪਣੀ" + comment_form_md_info : "ਮਾਰਕਡਾਊਨ ਵਰਤ ਸਕਦੇ ਹੋ।" + comment_form_name_label : "ਨਾਮ" + comment_form_email_label : "ਈਮੇਲ ਪਤਾ" + comment_form_website_label : "ਵੈਬਸਾਈਟ (ਵਿਕਲਪਿਕ)" + comment_btn_submit : "ਕੋਈ ਟਿੱਪਣੀ ਭੇਜੋ" + comment_btn_submitted : "ਪੇਸ਼ ਕੀਤਾ" + comment_success_msg : "ਤੁਹਾਡੀਆਂ ਟਿੱਪਣੀਆਂ ਲਈ ਧੰਨਵਾਦ! ਇਹ ਮਨਜ਼ੂਰੀ ਮਿਲਣ ਦੇ ਬਾਅਦ ਸਾਈਟ 'ਤੇ ਦਿਖਾਇਆ ਜਾਵੇਗਾ।" + comment_error_msg : "ਮੁਆਫ ਕਰਨਾ, ਤੁਹਾਡੀ ਅਧੀਨਗੀ ਵਿੱਚ ਕੋਈ ਗਲਤੀ ਹੋਈ ਸੀ ਕਿਰਪਾ ਕਰਕੇ ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਸਾਰੇ ਲੋੜੀਂਦੇ ਖੇਤਰ ਪੂਰੇ ਹੋ ਗਏ ਹਨ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।" + loading_label : "ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ..." + search_label_text : "ਖੋਜ" + search_placeholder_text : "ਆਪਣੀ ਖੋਜ ਦੇ ਸ਼ਬਦ ਨੂੰ ਦਰਜ ਕਰੋ..." + results_found : "ਨਤੀਜਾ ਮਿਲਿਆ/ਮਿਲੇ" + back_to_top : "ਵਾਪਸ ਚੋਟੀ 'ਤੇ ਜਾਓ" +pa-IN: + <<: *DEFAULT_PA + +# Persian (Farsi) +# -------------- +fa: &DEFAULT_FA + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "صفحه" + pagination_previous : "قبلی" + pagination_next : "بعدی" + breadcrumb_home_label : "صفحه اصلی" + breadcrumb_separator : "/" + menu_label : "فهرست" + toc_label : "در این صفحه" + ext_link_label : "لینک مستقیم" + less_than : " " + minute_read : "(طول مطالعه (دقیقه" + share_on_label : "اشتراک گذاری در" + meta_label : + tags_label : "تگ ها: " + categories_label : "دسته بندی ها: " + date_label : "به روز شده در: " + comments_label : "ارسال نظر" + comments_title : "نظرات" + more_label : "ادامه مطلب" + related_label : "ممکن است از این مطالب نیز لذت ببرید" + follow_label : "دنبال کنید: " + feed_label : "خوراک" + powered_by : "طراحی شده توسط" + website_label : "سایت اینترنتی" + email_label : "پست الکترونیک" + recent_posts : "آخرین مطالب" + undefined_wpm : ".(words_per_minute) _config.yml متغیر اشتباه در" + comment_form_info : ".آدرس ایمیل شما منتشر نخواهد شد. فیلدهای اجباری مشخص شده اند" + comment_form_comment_label : "دیدگاه" + comment_form_md_info : ".پشتیبانی می شود Markdown" + comment_form_name_label : "نام" + comment_form_email_label : "پست الکترونیک" + comment_form_website_label : "سایت اینترنتی (اختیاری)" + comment_btn_submit : "ارسال نظر" + comment_btn_submitted : "ارسال شد" + comment_success_msg : ".باتشکر از ارسال دیدگاه! پس از تأیید، این دیدگاه در سایت نشان داده خواهد شد" + comment_error_msg : ".متاسفانه در ارسال شما خطایی بود. لطفا مطمئن شوید تمام فیلدهای مورد نیاز تکمیل شده و دوباره امتحان کنید" + loading_label : "...بارگذاری" + search_label_text : + search_placeholder_text : "...عبارت جستجوی خود را وارد کنید" + results_found : "نتایج" + back_to_top : "بازگشت به بالا" +fa-IR: + <<: *DEFAULT_FA + + +# Malayalam +# ----------------- +ml: &DEFAULT_ML + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "പേജ്" + pagination_previous : "തിരികെ" + pagination_next : "മുന്നോട്ട്" + breadcrumb_home_label : "ഹോം" + breadcrumb_separator : "/" + menu_label : "ടോഗിൾ മെനു" + search_label : "ടോഗിൾ സെർച്ച്" + toc_label : "ഈ പേജിൽ" + ext_link_label : "ലിങ്കിലേക് പോകാൻ" + less_than : "ഏതാണ്ട്" + minute_read : "മിനിറ്റ് ദൈർഖ്യം" + share_on_label : "ഷെയർ ചെയ്യുവാൻ " + meta_label : + tags_label : "ടാഗുകൾ:" + categories_label : "വിഭാഗങ്ങൾ:" + date_label : "അവസാന മാറ്റം:" + comments_label : "അഭിപ്രായം രേഖപ്പെടുത്തുക" + comments_title : "അഭിപ്രായങ്ങൾ" + more_label : "കൂടുതൽ അറിയുവാൻ" + related_label : "നിങ്ങൾക് ഇതും ഇഷ്ടപ്പെട്ടേക്കാം" + follow_label : "പിന്തുടരുക:" + feed_label : "ഫീഡ്" + powered_by : "പവേർഡ് ബൈ" + website_label : "വെബ്സൈറ്റ്" + email_label : "ഇ-മെയിൽ" + recent_posts : "സമീപകാല പോസ്റ്റുകൾ" + undefined_wpm : "Config.yml ലെ words_per_minute പരാമീറ്റർ നിർവചിച്ചിട്ടില്ല." + comment_form_info : "നിങ്ങളുടെ ഇമെയിൽ വിലാസം പ്രസിദ്ധീകരിക്കില്ല. ആവശ്യമായ ഫീൽഡുകൾ അടയാളപ്പെടുത്തി." + comment_form_comment_label : "കമന്റ്" + comment_form_md_info : "Markdown സപ്പോർട്ട് ചെയ്യുന്നതാണ്." + comment_form_name_label : "പേര്" + comment_form_email_label : "ഇ-മെയിൽ" + comment_form_website_label : "വെബ്സൈറ് (ഓപ്ഷണൽ)" + comment_btn_submit : "അഭിപ്രായം രേഖപ്പെടുത്തുക" + comment_btn_submitted : "രേഖപ്പെടുത്തി" + comment_success_msg : "നിങ്ങളുടെ അഭിപ്രായത്തിന് നന്ദി! ഇത് അംഗീകരിച്ചുകഴിഞ്ഞാൽ ഇത് സൈറ്റിൽ പ്രദർശിപ്പിക്കും." + comment_error_msg : "ക്ഷമിക്കണം, നിങ്ങളുടെ സമർപ്പണവുമായി ബന്ധപ്പെട്ട് ഒരു പിശകുണ്ടായിരുന്നു. ആവശ്യമായ എല്ലാ ഫീൽഡുകളും പൂർത്തിയായിട്ടുണ്ടെന്ന് ഉറപ്പുവരുത്തുക, വീണ്ടും ശ്രമിക്കുക." + loading_label : "ലോഡിംഗ്..." + search_label_text : + search_placeholder_text : "നിങ്ങളുടെ തിരയൽ പദം നൽകുക..." + results_found : "ഫലം (കൾ) കണ്ടെത്തി" + back_to_top : "മുകളിലേയ്ക്ക്" +ml-IN: + <<: *DEFAULT_ML + +# Thailand +# -------------- +th: &DEFAULT_TH + skip_links : + skip_primary_nav : + skip_content : + skip_footer : + page : "หน้า" + pagination_previous : "ก่อนหน้า" + pagination_next : "ถัดไป" + breadcrumb_home_label : "หน้าแรก" + breadcrumb_separator : "/" + menu_label : "พับเมนู" + search_label : "พับการค้นหา" + toc_label : "บนหน้านี้" + ext_link_label : "ลิงก์โดยตรง" + less_than : "น้อยกว่า" + minute_read : "นาที ในการอ่าน" + share_on_label : "แชร์ไปที่" + meta_label : + tags_label : "แท็ก:" + categories_label : "หมวดหมู่:" + date_label : "อัพเดตล่าสุด:" + comments_label : "แสดงความคิดเห็น" + comments_title : "ความคิดเห็น" + more_label : "อ่านต่อ" + related_label : "คุณอาจจะชอบสิ่งนี้" + follow_label : "ติดตาม:" + feed_label : "ฟืดข่าว" + powered_by : "ขับเคลื่อนโดย" + website_label : "เว็บไซต์" + email_label : "อีเมล" + recent_posts : "โพสล่าสุด" + undefined_wpm : "ไม่สามารถระบุพารามิเตอร์ words_per_minute ได้ใน _config.yml" + comment_form_info : "อีเมลของคุณไม่สามารถโพสสาธารณะได้ กรุณากรอกช่องที่ระบุด้วยเครื่องหมายดอกจันไว้" + comment_form_comment_label : "แสดงความคิดเห็น" + comment_form_md_info : "มาร์กดาวน์ได้รับการสนับสนุน" + comment_form_name_label : "ชื่อ" + comment_form_email_label : "ที่อยู่อีเมล" + comment_form_website_label : "เว็บไซต์ (ตัวเลือก)" + comment_btn_submit : "ส่งความคิดเห็น" + comment_btn_submitted : "ส่งเรียบร้อยแล้ว" + comment_success_msg : "ขอบคุณสำหรับการแสดงความคิดเห็น! ความคิดเห็นจะได้รับการแสดงหลังจากได้รับการยืนยัน" + comment_error_msg : "ขออภัย, มีบางอย่างผิดพลาดจากการส่งแบบฟอร์ม กรุณาตรวจทานทุกช่อง และลองส่งใหม่อีกครั้ง" + loading_label : "กำลังโหลด..." + search_label_text : + search_placeholder_text : "ใส่คำค้นหาของคุณ..." + results_found : "ผลการค้นหา พบ" + back_to_top : "กลับด้านบน" +th-TH: + <<: *DEFAULT_TH + +# Hindi +# ----------------- +hi: &DEFAULT_HI + skip_links : "लिंक छोड़ें" + skip_primary_nav : "प्राथमिक पथ-प्रदर्शन छोड़ें" + skip_content : "सामग्री छोड़ें" + skip_footer : "अंत-में लिखा छोड़ें" + page : "पृष्ठ" + pagination_previous : "पिछला" + pagination_next : "अगला" + breadcrumb_home_label : "घर" + breadcrumb_separator : "/" + menu_label : "टॉगल मेनू" + toc_label : "इस पृष्ठ पर" + ext_link_label : "सीधा संपर्क" + less_than : "से कम" + minute_read : "मिनट में पढ़ सकते हैं" + share_on_label : "साझा करें" + meta_label : "स्व-संदर्भात्मक जानकारी" + tags_label : "अंकितक:" + categories_label : "श्रेणियाँ:" + date_label : "अपडेट किया गया:" + comments_label : "एक टिप्पणी छोड़ें" + comments_title : "टिप्पणियाँ" + more_label : "और अधिक जानें" + related_label : "आप इसका भी आनंद ले सकते हैं" + follow_label : "अनुसरण करे:" + feed_label : "फ़ीड" + powered_by : "द्वारा संचालित" + website_label : "वेबसाइट" + email_label : "ईमेल" + recent_posts : "हाल के पोस्ट" + undefined_wpm : "_config.yml पर अपरिभाषित पैरामीटर words_per_minute" + comment_form_info : "आपका ईमेल पता प्रकाशित नहीं किया जाएगा। अपेक्षित स्थानों को रेखांकित कर दिया गया है" + comment_form_comment_label : "टिप्पणी" + comment_form_md_info : "मार्कडाउन की अनुमति है।" + comment_form_name_label : "नाम" + comment_form_email_label : "ईमेल पता" + comment_form_website_label : "वेबसाइट (ऐच्छिक)" + comment_btn_submit : "टिप्पणी भेजें" + comment_btn_submitted : "प्रस्तुत" + comment_success_msg : "आपके कमेंट के लिए धन्यवाद! इसे स्वीकृति मिलने के बाद साइट पर दिखाया जाएगा।" + comment_error_msg : "क्षमा करें, आपके सबमिशन के साथ एक त्रुटि हुई थी। कृपया सुनिश्चित करें कि सभी आवश्यक फ़ील्ड पूरा हो गए हैं और पुनः प्रयास करें।" + loading_label : "लोड हो रहा है..." + search_label_text : "खोज" + search_placeholder_text : "अपना खोज शब्द दर्ज करें..." + results_found : "परिणाम मिला/मिले" + back_to_top : "शीर्ष पर वापस" +hi-IN: + <<: *DEFAULT_HI + +# Catalan +# -------------- +ca: &DEFAULT_CA + skip_links : "Salta els enllaços" + skip_primary_nav : "Salta a la navegació primària" + skip_content : "Salta al contingut" + skip_footer : "Salta al peu" + page : "Pàgina" + pagination_previous : "Anterior" + pagination_next : "Següent" + breadcrumb_home_label : "Inici" + breadcrumb_separator : "/" + menu_label : "Mostra/amaga el menú" + search_label : "Mostra/amaga la cerca" + toc_label : "En aquesta pàgina" + ext_link_label : "Enllaç directe" + less_than : "es llegeix en menys de" + minute_read : "minut(s)" + share_on_label : "Comparteix a" + meta_label : + tags_label : "Etiquetes:" + categories_label : "Categories:" + date_label : "Actualitzat:" + comments_label : "Deixa un comentari" + comments_title : "Comentaris" + more_label : "Llegeix més" + related_label : "També et pot agradar" + follow_label : "Segueix-me:" + feed_label : "Feed" + powered_by : "Funciona amb" + website_label : "Pàgina web" + email_label : "Correu electrònic" + recent_posts : "Entrades recents" + undefined_wpm : "El paràmetre words_per_minute no està definit a _config.yml" + comment_form_info : "No es mostrarà el teu correu electrònic. Els camps obligatoris estan marcats" + comment_form_comment_label : "Comentari" + comment_form_md_info : "Admet Markdown." + comment_form_name_label : "Nom" + comment_form_email_label : "Correu electrònic" + comment_form_website_label : "Pàgina web (opcional)" + comment_btn_submit : "Envia" + comment_btn_submitted : "Enviat" + comment_success_msg : "Gràcies pel teu comentari! Apareixerà un cop sigui aprovat." + comment_error_msg : "Hi ha hagut un error enviat el comentari. Comprova que els camps obligatirs estiguin omplerts i torna-ho a provar." + loading_label : "Carregant..." + search_label_text : "Introdueix termes per cercar..." + search_placeholder_text : "Introdueix termes per cercar..." + results_found : "resultat(s)" + back_to_top : "Torna a dalt" +ca-ES: + <<: *DEFAULT_CA + +# Another locale +# -------------- +# From 1cac10a9ad4582b64c69ed4aabd494aa643f154f Mon Sep 17 00:00:00 2001 From: Kamil Adam Date: Thu, 16 Apr 2020 19:19:30 +0200 Subject: [PATCH 08/47] Update_gitignore --- .gitignore | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 20384474..85a6d1b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,10 @@ _site .sass-cache .jekyll-metadata -Gemfile.lock \ No newline at end of file +Gemfile.lock + +.jekyll-cache + +.idea +*.iml +/draft/ From 256671e8eb4a6cfd5d0dd6b1ae6dd2f0d10f8e80 Mon Sep 17 00:00:00 2001 From: Kamil Adam Date: Thu, 16 Apr 2020 19:26:03 +0200 Subject: [PATCH 09/47] Add fix.sh --- fix.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100755 fix.sh diff --git a/fix.sh b/fix.sh new file mode 100755 index 00000000..fcc57806 --- /dev/null +++ b/fix.sh @@ -0,0 +1,22 @@ +#!/bin/sh -x + +git filter-branch -f --env-filter ' + +NAME="Kamil.Zabinski" +CORRECT_NAME="Kamil Adam" +CORRECT_EMAIL="kamil.adam.zabinski@gmail.com" +if [ "$GIT_COMMITTER_NAME" = "$NAME" ] +then + export GIT_COMMITTER_NAME="$CORRECT_NAME" + export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL" + export GIT_AUTHOR_NAME="$CORRECT_NAME" + export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL" +fi +if [ "$GIT_AUTHOR_NAME" = "$NAME" ] +then + export GIT_COMMITTER_NAME="$CORRECT_NAME" + export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL" + export GIT_AUTHOR_NAME="$CORRECT_NAME" + export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL" +fi +' --tag-name-filter cat -- --branches --tags From 3a3ebc308fbe60c99a2261ea800e04414b41ee15 Mon Sep 17 00:00:00 2001 From: Kamil Adam Date: Wed, 25 Mar 2020 22:05:58 +0100 Subject: [PATCH 10/47] Update _pages --- _pages/about.html | 26 ++++++++++++++++++++++++++ _pages/about.md | 8 -------- _pages/archive-categories.html | 6 ++++++ _pages/archive-eso.html | 6 ++++++ _pages/archive-langs.html | 6 ++++++ _pages/archive-libs.html | 6 ++++++ _pages/archive-projects.html | 6 ++++++ _pages/archive-tags.html | 6 ++++++ _pages/archive-tools.html | 6 ++++++ _pages/archive-years.html | 5 +++++ _pages/category-archive.md | 6 ------ _pages/privacy.html | 21 +++++++++++++++++++++ _pages/read-books.html | 25 +++++++++++++++++++++++++ _pages/search-jekyll-simple.html | 10 ++++++++++ _pages/search-jekyll.html | 11 +++++++++++ _pages/tag-archive.md | 6 ------ _pages/textarea.html | 20 ++++++++++++++++++++ _pages/year-archive.md | 6 ------ 18 files changed, 160 insertions(+), 26 deletions(-) create mode 100644 _pages/about.html delete mode 100644 _pages/about.md create mode 100644 _pages/archive-categories.html create mode 100644 _pages/archive-eso.html create mode 100644 _pages/archive-langs.html create mode 100644 _pages/archive-libs.html create mode 100644 _pages/archive-projects.html create mode 100644 _pages/archive-tags.html create mode 100644 _pages/archive-tools.html create mode 100644 _pages/archive-years.html delete mode 100644 _pages/category-archive.md create mode 100644 _pages/privacy.html create mode 100644 _pages/read-books.html create mode 100644 _pages/search-jekyll-simple.html create mode 100644 _pages/search-jekyll.html delete mode 100644 _pages/tag-archive.md create mode 100644 _pages/textarea.html delete mode 100644 _pages/year-archive.md diff --git a/_pages/about.html b/_pages/about.html new file mode 100644 index 00000000..ff4d0da1 --- /dev/null +++ b/_pages/about.html @@ -0,0 +1,26 @@ +--- +layout: page +title: O autorze +permalink: /about/ +description: O autorze +--- + +

+ Backend Developer. + Jestem szczęśliwym Backend Developer ponieważ istnieją Frontendowcy, którzy piszą za mnie front. + Programuję komercyjnie od 2012 roku, głownie w Javie i Scali. + Epizodycznie w językach Kotlin, Perl, PL/Perl, PL/pgSQL, Lua i Bash + oraz hobbistyczne w językach Haskell, Scala + i Scheme/Racket, + a kiedyś w - Perl i TCL. + O językach Rust i Clojure na razie tylko czytam, bo doba jest za krótka. +

+ +

+ Pracuję głównie przy aplikacjach webowych we frameworkach takich jak zbyt magiczny Spring (Spring MVC, Spring Boot), DropWizard oraz Akka-Http, + a chciałbym jeszcze poznać ZIO-WEB i http4s. +

+ +

+ Moim hobby jest programowanie czysto funkcyjne i pisanie parserów. +

diff --git a/_pages/about.md b/_pages/about.md deleted file mode 100644 index 9ba0655c..00000000 --- a/_pages/about.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -permalink: /about/ -title: "About" ---- - -Tempor velit sint sunt ipsum tempor enim ad qui ullamco. Est dolore anim ad velit duis dolore minim sunt aliquip amet commodo labore. Ut eu pariatur aute ea aute excepteur laborum. Esse ea esse excepteur minim mollit qui cillum excepteur ex dolore magna. Labore deserunt fugiat incididunt incididunt sint ea. Consequat dolore aute laboris quis proident quis non et est consectetur ex eiusmod sit culpa. - -Cupidatat ea do et in excepteur in. Ad nostrud ut est esse eu duis ea sunt eiusmod. Aliquip tempor veniam sint elit fugiat. Velit incididunt laboris amet incididunt labore dolore irure velit excepteur commodo deserunt laborum. Consectetur eu fugiat veniam veniam Lorem labore magna eiusmod. Ea occaecat reprehenderit pariatur consectetur minim labore ut aliquip. \ No newline at end of file diff --git a/_pages/archive-categories.html b/_pages/archive-categories.html new file mode 100644 index 00000000..645e9516 --- /dev/null +++ b/_pages/archive-categories.html @@ -0,0 +1,6 @@ +--- +title: "Artykuły po kategoriach" +layout: archive-categories +permalink: /categories/ +collections: categories +--- diff --git a/_pages/archive-eso.html b/_pages/archive-eso.html new file mode 100644 index 00000000..4ba33e70 --- /dev/null +++ b/_pages/archive-eso.html @@ -0,0 +1,6 @@ +--- +title: "Artykuły po eso" +layout: archive-eso +permalink: /eso/ +collections: eso +--- diff --git a/_pages/archive-langs.html b/_pages/archive-langs.html new file mode 100644 index 00000000..b08a0314 --- /dev/null +++ b/_pages/archive-langs.html @@ -0,0 +1,6 @@ +--- +title: "Artykuły po językach programowania" +layout: archive-langs +permalink: /langs/ +collection: langs +--- diff --git a/_pages/archive-libs.html b/_pages/archive-libs.html new file mode 100644 index 00000000..e762c2d6 --- /dev/null +++ b/_pages/archive-libs.html @@ -0,0 +1,6 @@ +--- +title: "Artykuły po bibliotekach programistycznych" +layout: archive-libs +permalink: /libs/ +collection: libs +--- diff --git a/_pages/archive-projects.html b/_pages/archive-projects.html new file mode 100644 index 00000000..e6829e5f --- /dev/null +++ b/_pages/archive-projects.html @@ -0,0 +1,6 @@ +--- +title: "Artykuły po projektach" +layout: archive-projects +permalink: /projects/ +collection: projects +--- diff --git a/_pages/archive-tags.html b/_pages/archive-tags.html new file mode 100644 index 00000000..51ad1e0e --- /dev/null +++ b/_pages/archive-tags.html @@ -0,0 +1,6 @@ +--- +title: "Artykuły po tagach" +layout: archive-tags +permalink: /tags/ +collections: tags +--- diff --git a/_pages/archive-tools.html b/_pages/archive-tools.html new file mode 100644 index 00000000..77a6fd64 --- /dev/null +++ b/_pages/archive-tools.html @@ -0,0 +1,6 @@ +--- +title: "Artykuły po narzędziach programistycznych" +layout: archive-tools +permalink: /tools/ +collection: tools +--- diff --git a/_pages/archive-years.html b/_pages/archive-years.html new file mode 100644 index 00000000..2114ef69 --- /dev/null +++ b/_pages/archive-years.html @@ -0,0 +1,5 @@ +--- +title: "Artykuły po roku" +layout: posts +permalink: /posts/ +--- diff --git a/_pages/category-archive.md b/_pages/category-archive.md deleted file mode 100644 index 4cb3860e..00000000 --- a/_pages/category-archive.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: "Posts by Category" -layout: categories -permalink: /categories/ -author_profile: true ---- diff --git a/_pages/privacy.html b/_pages/privacy.html new file mode 100644 index 00000000..56883d31 --- /dev/null +++ b/_pages/privacy.html @@ -0,0 +1,21 @@ +--- +layout : page +title : Polityka prywatności +description: Polityka prywatności plików cookies +permalink : /privacy/ +--- +
+

+ Blog, o języku programowania Scala, WriteOnly.pl przechowuje pliki cookies (tzw. ciasteczka) są używane przez: +

+
Google Analytics
+
do poprawego analizowania ruchu strony
+
Disqus
+
do obsługi komentarzy
+
WriteOnly.pl
+
po to żeby za każdym razem nie wyświetlać informacji o plikach cookies.
+
+

+ + Pokaż ostrzeżenie +
diff --git a/_pages/read-books.html b/_pages/read-books.html new file mode 100644 index 00000000..059318fd --- /dev/null +++ b/_pages/read-books.html @@ -0,0 +1,25 @@ +--- +layout: page +title: Przeczytane książki +permalink: /books/ +description: Przeczytane książki programistyczne +--- +
+
+ {% assign books = site.books | reverse | sort: "year-of-publishment"| reverse %} + {% for book in books %} +
+ + {{ book.title }} + +
+
+
    +
  • Autorzy: {{ book.authors | join: ", "}}
  • +
  • Tytuł oryginalny: {{ book.title-of-original }}
  • +
  • Rok pierwszego wydania: {{ book.year-of-publishment }}
  • +
+
+ {% endfor %} +
+
diff --git a/_pages/search-jekyll-simple.html b/_pages/search-jekyll-simple.html new file mode 100644 index 00000000..3ae791f2 --- /dev/null +++ b/_pages/search-jekyll-simple.html @@ -0,0 +1,10 @@ +--- +layout: search +title: Wyszukiwarka Jekyll prosta +permalink: /search-jekyll-simple/ +description: Wyszukiwarka artykułów +power_supply_url: "https://github.com/christian-fei/Simple-Jekyll-Search" +power_supply_label: "Simple-Jekyll-Search" +--- + + diff --git a/_pages/search-jekyll.html b/_pages/search-jekyll.html new file mode 100644 index 00000000..65bcb44e --- /dev/null +++ b/_pages/search-jekyll.html @@ -0,0 +1,11 @@ +--- +layout: search +title: Wyszukiwarka Jekyll +permalink: /search-jekyll/ +description: Wyszukiwarka artykułów +power_supply_url: "https://github.com/daviddarnes/jekyll-search-js" +power_supply_label: "jekyll-search-js" +--- + + + diff --git a/_pages/tag-archive.md b/_pages/tag-archive.md deleted file mode 100644 index 3f4e3f0d..00000000 --- a/_pages/tag-archive.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: "Posts by Tag" -permalink: /tags/ -layout: tags -author_profile: true ---- diff --git a/_pages/textarea.html b/_pages/textarea.html new file mode 100644 index 00000000..e195d2f1 --- /dev/null +++ b/_pages/textarea.html @@ -0,0 +1,20 @@ +--- +layout: page +title: The TextArea +permalink: /textarea/ +description: The Text Area +--- +
+ +
+ + diff --git a/_pages/year-archive.md b/_pages/year-archive.md deleted file mode 100644 index ce3413fc..00000000 --- a/_pages/year-archive.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: "Posts by Year" -permalink: /posts/ -layout: posts -author_profile: true ---- From c92bd654f4231a6301de959b735f400970e1d70d Mon Sep 17 00:00:00 2001 From: Kamil Adam Date: Sun, 10 May 2020 20:01:25 +0200 Subject: [PATCH 11/47] Add categories --- _collections/_categories/cli.html | 4 ++++ _collections/_categories/haskell-eta.html | 4 ++++ _collections/_categories/java.html | 3 +++ _collections/_categories/jekyll.html | 4 ++++ _collections/_categories/programming.html | 4 ++++ _collections/_categories/relationships.html | 4 ++++ _collections/_categories/rust.html | 4 ++++ _collections/_categories/scala-jvm.html | 6 ++++++ _collections/_categories/scala-native.html | 6 ++++++ _collections/_categories/scheme-racket.html | 4 ++++ _collections/_categories/thoughts.html | 4 ++++ 11 files changed, 47 insertions(+) create mode 100644 _collections/_categories/cli.html create mode 100644 _collections/_categories/haskell-eta.html create mode 100644 _collections/_categories/java.html create mode 100644 _collections/_categories/jekyll.html create mode 100644 _collections/_categories/programming.html create mode 100644 _collections/_categories/relationships.html create mode 100644 _collections/_categories/rust.html create mode 100644 _collections/_categories/scala-jvm.html create mode 100644 _collections/_categories/scala-native.html create mode 100644 _collections/_categories/scheme-racket.html create mode 100644 _collections/_categories/thoughts.html diff --git a/_collections/_categories/cli.html b/_collections/_categories/cli.html new file mode 100644 index 00000000..b32f8a9a --- /dev/null +++ b/_collections/_categories/cli.html @@ -0,0 +1,4 @@ +--- +category: cli +--- +Ang. command line interpreter/interface, czyli wiersz poleceń. diff --git a/_collections/_categories/haskell-eta.html b/_collections/_categories/haskell-eta.html new file mode 100644 index 00000000..6b786acc --- /dev/null +++ b/_collections/_categories/haskell-eta.html @@ -0,0 +1,4 @@ +--- +category: haskell-eta +redirect_from: +--- diff --git a/_collections/_categories/java.html b/_collections/_categories/java.html new file mode 100644 index 00000000..762460b8 --- /dev/null +++ b/_collections/_categories/java.html @@ -0,0 +1,3 @@ +--- +category: java +--- diff --git a/_collections/_categories/jekyll.html b/_collections/_categories/jekyll.html new file mode 100644 index 00000000..2bcceae3 --- /dev/null +++ b/_collections/_categories/jekyll.html @@ -0,0 +1,4 @@ +--- +category: jekyll +--- +Opis walki z generatorem statycznych stron Jekyll i powstawania tematu WriteOnlyDoc. diff --git a/_collections/_categories/programming.html b/_collections/_categories/programming.html new file mode 100644 index 00000000..2be6a29b --- /dev/null +++ b/_collections/_categories/programming.html @@ -0,0 +1,4 @@ +--- +category: programming +--- +Techniczne opowieści o programowaniu i językach programowania. diff --git a/_collections/_categories/relationships.html b/_collections/_categories/relationships.html new file mode 100644 index 00000000..027c07d5 --- /dev/null +++ b/_collections/_categories/relationships.html @@ -0,0 +1,4 @@ +--- +category: relationships +--- +Relacje z wydarzeń diff --git a/_collections/_categories/rust.html b/_collections/_categories/rust.html new file mode 100644 index 00000000..d57c1949 --- /dev/null +++ b/_collections/_categories/rust.html @@ -0,0 +1,4 @@ +--- +category: rust +redirect_from: +--- diff --git a/_collections/_categories/scala-jvm.html b/_collections/_categories/scala-jvm.html new file mode 100644 index 00000000..7cb6b966 --- /dev/null +++ b/_collections/_categories/scala-jvm.html @@ -0,0 +1,6 @@ +--- +category: scala-jvm +redirect_from: +--- +Opis powstawania projektu hyde +w języku programowania Scala, diff --git a/_collections/_categories/scala-native.html b/_collections/_categories/scala-native.html new file mode 100644 index 00000000..a3bdcbb7 --- /dev/null +++ b/_collections/_categories/scala-native.html @@ -0,0 +1,6 @@ +--- +category: scala-native +--- +Opis powstawania projektu resentiment +w języku programowania Scala, +Scala.js i Scala native. diff --git a/_collections/_categories/scheme-racket.html b/_collections/_categories/scheme-racket.html new file mode 100644 index 00000000..fc7c4f50 --- /dev/null +++ b/_collections/_categories/scheme-racket.html @@ -0,0 +1,4 @@ +--- +category: scheme-racket +redirect_from: +--- diff --git a/_collections/_categories/thoughts.html b/_collections/_categories/thoughts.html new file mode 100644 index 00000000..901c832b --- /dev/null +++ b/_collections/_categories/thoughts.html @@ -0,0 +1,4 @@ +--- +category: thoughts +--- +Przemyślenia niedokońca technicznie. From 2bd5c2c9c3d631968da84c73f8aab5c89fc36432 Mon Sep 17 00:00:00 2001 From: Kamil Adam Date: Sun, 10 May 2020 20:01:54 +0200 Subject: [PATCH 12/47] Add tools --- _collections/_tools/bash.html | 4 ++++ _collections/_tools/bucklescript.html | 14 ++++++++++++++ _collections/_tools/cabal.html | 6 ++++++ _collections/_tools/clang.html | 5 +++++ _collections/_tools/clojurescript.html | 11 +++++++++++ _collections/_tools/docker.html | 4 ++++ _collections/_tools/etlas.html | 6 ++++++ _collections/_tools/git.html | 4 ++++ _collections/_tools/github.html | 4 ++++ _collections/_tools/graalvm.html | 5 +++++ _collections/_tools/jekyll.html | 4 ++++ _collections/_tools/jvm.html | 7 +++++++ _collections/_tools/kotlin-js.html | 6 ++++++ _collections/_tools/kotlin-native.html | 6 ++++++ _collections/_tools/llvm.html | 16 ++++++++++++++++ _collections/_tools/lunr.html | 5 +++++ _collections/_tools/node-js.html | 5 +++++ _collections/_tools/postgresql.html | 5 +++++ _collections/_tools/sass.html | 4 ++++ _collections/_tools/sbt.html | 6 ++++++ _collections/_tools/scala-js.html | 6 ++++++ _collections/_tools/scala-jvm.html | 5 +++++ _collections/_tools/scala-native.html | 9 +++++++++ _collections/_tools/scalafix.html | 6 ++++++ _collections/_tools/sed.html | 4 ++++ _collections/_tools/seed.html | 7 +++++++ _collections/_tools/solr.html | 5 +++++ _collections/_tools/swagger.html | 4 ++++ _collections/_tools/teavm.html | 5 +++++ _collections/_tools/ubuntu.html | 4 ++++ 30 files changed, 182 insertions(+) create mode 100644 _collections/_tools/bash.html create mode 100644 _collections/_tools/bucklescript.html create mode 100644 _collections/_tools/cabal.html create mode 100644 _collections/_tools/clang.html create mode 100644 _collections/_tools/clojurescript.html create mode 100644 _collections/_tools/docker.html create mode 100644 _collections/_tools/etlas.html create mode 100644 _collections/_tools/git.html create mode 100644 _collections/_tools/github.html create mode 100644 _collections/_tools/graalvm.html create mode 100644 _collections/_tools/jekyll.html create mode 100644 _collections/_tools/jvm.html create mode 100644 _collections/_tools/kotlin-js.html create mode 100644 _collections/_tools/kotlin-native.html create mode 100644 _collections/_tools/llvm.html create mode 100644 _collections/_tools/lunr.html create mode 100644 _collections/_tools/node-js.html create mode 100644 _collections/_tools/postgresql.html create mode 100644 _collections/_tools/sass.html create mode 100644 _collections/_tools/sbt.html create mode 100644 _collections/_tools/scala-js.html create mode 100644 _collections/_tools/scala-jvm.html create mode 100644 _collections/_tools/scala-native.html create mode 100644 _collections/_tools/scalafix.html create mode 100644 _collections/_tools/sed.html create mode 100644 _collections/_tools/seed.html create mode 100644 _collections/_tools/solr.html create mode 100644 _collections/_tools/swagger.html create mode 100644 _collections/_tools/teavm.html create mode 100644 _collections/_tools/ubuntu.html diff --git a/_collections/_tools/bash.html b/_collections/_tools/bash.html new file mode 100644 index 00000000..57f87458 --- /dev/null +++ b/_collections/_tools/bash.html @@ -0,0 +1,4 @@ +--- +tool: bash +title: Bash +--- \ No newline at end of file diff --git a/_collections/_tools/bucklescript.html b/_collections/_tools/bucklescript.html new file mode 100644 index 00000000..29d4bcd4 --- /dev/null +++ b/_collections/_tools/bucklescript.html @@ -0,0 +1,14 @@ +--- +tool: bucklescript +title: BuckleScript +langs: ocaml meta-language +--- +BuckleScript - transpilator obiektowo-funkcyjnego języka programowania OCaml do języka JavaScript. + diff --git a/_collections/_tools/cabal.html b/_collections/_tools/cabal.html new file mode 100644 index 00000000..53b4c0eb --- /dev/null +++ b/_collections/_tools/cabal.html @@ -0,0 +1,6 @@ +--- +tool: cabal +title: Cabal +langs: haskell +--- +Cabal \ No newline at end of file diff --git a/_collections/_tools/clang.html b/_collections/_tools/clang.html new file mode 100644 index 00000000..67c39705 --- /dev/null +++ b/_collections/_tools/clang.html @@ -0,0 +1,5 @@ +--- +tool: clang +title: Clang +--- +Clang \ No newline at end of file diff --git a/_collections/_tools/clojurescript.html b/_collections/_tools/clojurescript.html new file mode 100644 index 00000000..b36f1d18 --- /dev/null +++ b/_collections/_tools/clojurescript.html @@ -0,0 +1,11 @@ +--- +tool: clojurescript +title: ClojureScript +langs: clojure +--- +ClojureScript - transpilator obiektowo-funkcyjnego języka programowania Clojure do języka JavaScript. + diff --git a/_collections/_tools/docker.html b/_collections/_tools/docker.html new file mode 100644 index 00000000..218b61b2 --- /dev/null +++ b/_collections/_tools/docker.html @@ -0,0 +1,4 @@ +--- +tool: docker +title: Docker +--- \ No newline at end of file diff --git a/_collections/_tools/etlas.html b/_collections/_tools/etlas.html new file mode 100644 index 00000000..9a8bd4ef --- /dev/null +++ b/_collections/_tools/etlas.html @@ -0,0 +1,6 @@ +--- +tool: etlas +title: Etlas +langs: eta haskell +--- +Etlas \ No newline at end of file diff --git a/_collections/_tools/git.html b/_collections/_tools/git.html new file mode 100644 index 00000000..ef0af9dc --- /dev/null +++ b/_collections/_tools/git.html @@ -0,0 +1,4 @@ +--- +tool: git +title: Git +--- \ No newline at end of file diff --git a/_collections/_tools/github.html b/_collections/_tools/github.html new file mode 100644 index 00000000..9ba59013 --- /dev/null +++ b/_collections/_tools/github.html @@ -0,0 +1,4 @@ +--- +tool: github +title: Github +--- diff --git a/_collections/_tools/graalvm.html b/_collections/_tools/graalvm.html new file mode 100644 index 00000000..2c554023 --- /dev/null +++ b/_collections/_tools/graalvm.html @@ -0,0 +1,5 @@ +--- +tool: graalvm +title: GraalVM +langs: java +--- diff --git a/_collections/_tools/jekyll.html b/_collections/_tools/jekyll.html new file mode 100644 index 00000000..4b657837 --- /dev/null +++ b/_collections/_tools/jekyll.html @@ -0,0 +1,4 @@ +--- +tool: jekyll +title: Jekyll +--- \ No newline at end of file diff --git a/_collections/_tools/jvm.html b/_collections/_tools/jvm.html new file mode 100644 index 00000000..2395d230 --- /dev/null +++ b/_collections/_tools/jvm.html @@ -0,0 +1,7 @@ +--- +tool: jvm +title: JVM +langs: java +--- +https://pl.wikipedia.org/wiki/Wirtualna_maszyna_Javy +(ang. Java virtual machine, JVM) \ No newline at end of file diff --git a/_collections/_tools/kotlin-js.html b/_collections/_tools/kotlin-js.html new file mode 100644 index 00000000..9e32eaa5 --- /dev/null +++ b/_collections/_tools/kotlin-js.html @@ -0,0 +1,6 @@ +--- +tool: kotlin-js +title: kotlin.js +langs: kotlin +--- +Transpilator Kotlin JS \ No newline at end of file diff --git a/_collections/_tools/kotlin-native.html b/_collections/_tools/kotlin-native.html new file mode 100644 index 00000000..e74786fd --- /dev/null +++ b/_collections/_tools/kotlin-native.html @@ -0,0 +1,6 @@ +--- +tool: kotlin-native +title: Kotlin/Native +langs: kotlin +--- +Kompilator Kotlin Native \ No newline at end of file diff --git a/_collections/_tools/llvm.html b/_collections/_tools/llvm.html new file mode 100644 index 00000000..55145a96 --- /dev/null +++ b/_collections/_tools/llvm.html @@ -0,0 +1,16 @@ +--- +tool: llvm +title: LLVM +--- + +LLVM Haskell Examples +llvm-hs +llvm-hs-pure +llvm-hs-typed + +llvm-general +llvm-general-pure +llvm-general-quote + +llvm-data-interop + diff --git a/_collections/_tools/lunr.html b/_collections/_tools/lunr.html new file mode 100644 index 00000000..f7452a70 --- /dev/null +++ b/_collections/_tools/lunr.html @@ -0,0 +1,5 @@ +--- +tool: lunr +title: Lunr +tags: search +--- \ No newline at end of file diff --git a/_collections/_tools/node-js.html b/_collections/_tools/node-js.html new file mode 100644 index 00000000..0c72deba --- /dev/null +++ b/_collections/_tools/node-js.html @@ -0,0 +1,5 @@ +--- +tool: node-js +title: Node.js +langs: javascript +--- \ No newline at end of file diff --git a/_collections/_tools/postgresql.html b/_collections/_tools/postgresql.html new file mode 100644 index 00000000..a53ac3f2 --- /dev/null +++ b/_collections/_tools/postgresql.html @@ -0,0 +1,5 @@ +--- +tool: postgresql +title: PostgreSQL +tags: database +--- \ No newline at end of file diff --git a/_collections/_tools/sass.html b/_collections/_tools/sass.html new file mode 100644 index 00000000..46839c35 --- /dev/null +++ b/_collections/_tools/sass.html @@ -0,0 +1,4 @@ +--- +tool: sass +title: Sass +--- \ No newline at end of file diff --git a/_collections/_tools/sbt.html b/_collections/_tools/sbt.html new file mode 100644 index 00000000..62ebc27e --- /dev/null +++ b/_collections/_tools/sbt.html @@ -0,0 +1,6 @@ +--- +tool: sbt +title: sbt +langs: scala +--- +Scala Build Tool \ No newline at end of file diff --git a/_collections/_tools/scala-js.html b/_collections/_tools/scala-js.html new file mode 100644 index 00000000..95102a15 --- /dev/null +++ b/_collections/_tools/scala-js.html @@ -0,0 +1,6 @@ +--- +tool: scala-js +title: Scala.js +langs: scala +--- +Transpilator Scala.js \ No newline at end of file diff --git a/_collections/_tools/scala-jvm.html b/_collections/_tools/scala-jvm.html new file mode 100644 index 00000000..b933b33a --- /dev/null +++ b/_collections/_tools/scala-jvm.html @@ -0,0 +1,5 @@ +--- +tool: scala-jvm +title: Scala JVM +langs: scala +--- \ No newline at end of file diff --git a/_collections/_tools/scala-native.html b/_collections/_tools/scala-native.html new file mode 100644 index 00000000..c53b341c --- /dev/null +++ b/_collections/_tools/scala-native.html @@ -0,0 +1,9 @@ +--- +tool: scala-native +title: Scala Native +langs: scala +--- +Kompilator Scala Native. +@scala_native + +Zobacz Awesome Scala Native diff --git a/_collections/_tools/scalafix.html b/_collections/_tools/scalafix.html new file mode 100644 index 00000000..1aabf803 --- /dev/null +++ b/_collections/_tools/scalafix.html @@ -0,0 +1,6 @@ +--- +tool: scalafix +title: Scalafix +langs: scala +--- +Scalafix \ No newline at end of file diff --git a/_collections/_tools/sed.html b/_collections/_tools/sed.html new file mode 100644 index 00000000..3d7c647a --- /dev/null +++ b/_collections/_tools/sed.html @@ -0,0 +1,4 @@ +--- +tool: sed +title: Sed +--- diff --git a/_collections/_tools/seed.html b/_collections/_tools/seed.html new file mode 100644 index 00000000..4e0cae6c --- /dev/null +++ b/_collections/_tools/seed.html @@ -0,0 +1,7 @@ +--- +tool: seed +title: Seed +langs: scala +tags: toml +--- +Seed diff --git a/_collections/_tools/solr.html b/_collections/_tools/solr.html new file mode 100644 index 00000000..b3cf346d --- /dev/null +++ b/_collections/_tools/solr.html @@ -0,0 +1,5 @@ +--- +tool: solr +title: Solr +tags: search +--- \ No newline at end of file diff --git a/_collections/_tools/swagger.html b/_collections/_tools/swagger.html new file mode 100644 index 00000000..c9e45455 --- /dev/null +++ b/_collections/_tools/swagger.html @@ -0,0 +1,4 @@ +--- +tool: swagger +title: Swagger +--- diff --git a/_collections/_tools/teavm.html b/_collections/_tools/teavm.html new file mode 100644 index 00000000..9ac8451c --- /dev/null +++ b/_collections/_tools/teavm.html @@ -0,0 +1,5 @@ +--- +tool: teavm +title: TeaVM +langs: java +--- diff --git a/_collections/_tools/ubuntu.html b/_collections/_tools/ubuntu.html new file mode 100644 index 00000000..960b30ef --- /dev/null +++ b/_collections/_tools/ubuntu.html @@ -0,0 +1,4 @@ +--- +tool: ubuntu +title: Ubuntu +--- \ No newline at end of file From 80fe2963838f1f27d842415e708b952e90d5070c Mon Sep 17 00:00:00 2001 From: Kamil Adam Date: Sat, 16 May 2020 17:20:27 +0200 Subject: [PATCH 13/47] Add projects --- _collections/_projects/helcam.html | 5 +++++ _collections/_projects/hell.html | 5 +++++ _collections/_projects/helms.html | 5 +++++ _collections/_projects/helpa.html | 5 +++++ _collections/_projects/helvm.html | 5 +++++ _collections/_projects/linkchecker.html | 4 ++++ _collections/_projects/resentiment.html | 4 ++++ 7 files changed, 33 insertions(+) create mode 100644 _collections/_projects/helcam.html create mode 100644 _collections/_projects/hell.html create mode 100644 _collections/_projects/helms.html create mode 100644 _collections/_projects/helpa.html create mode 100644 _collections/_projects/helvm.html create mode 100644 _collections/_projects/linkchecker.html create mode 100644 _collections/_projects/resentiment.html diff --git a/_collections/_projects/helcam.html b/_collections/_projects/helcam.html new file mode 100644 index 00000000..6985a1f5 --- /dev/null +++ b/_collections/_projects/helcam.html @@ -0,0 +1,5 @@ +--- +project: helcam +title: HelCam +--- +HelCam diff --git a/_collections/_projects/hell.html b/_collections/_projects/hell.html new file mode 100644 index 00000000..5e6e454d --- /dev/null +++ b/_collections/_projects/hell.html @@ -0,0 +1,5 @@ +--- +project: hell +title: HelL +--- +HelL diff --git a/_collections/_projects/helms.html b/_collections/_projects/helms.html new file mode 100644 index 00000000..0cc5a967 --- /dev/null +++ b/_collections/_projects/helms.html @@ -0,0 +1,5 @@ +--- +project: helms +title: HelMS +--- +HelMS diff --git a/_collections/_projects/helpa.html b/_collections/_projects/helpa.html new file mode 100644 index 00000000..8e38568b --- /dev/null +++ b/_collections/_projects/helpa.html @@ -0,0 +1,5 @@ +--- +project: helpa +title: HelPA +--- +HelPA diff --git a/_collections/_projects/helvm.html b/_collections/_projects/helvm.html new file mode 100644 index 00000000..cd7f331a --- /dev/null +++ b/_collections/_projects/helvm.html @@ -0,0 +1,5 @@ +--- +project: helvm +title: HelVM +--- +HelVM diff --git a/_collections/_projects/linkchecker.html b/_collections/_projects/linkchecker.html new file mode 100644 index 00000000..ff116e6e --- /dev/null +++ b/_collections/_projects/linkchecker.html @@ -0,0 +1,4 @@ +--- +project: linkchecker +title: LinkChecker +--- \ No newline at end of file diff --git a/_collections/_projects/resentiment.html b/_collections/_projects/resentiment.html new file mode 100644 index 00000000..b791d78f --- /dev/null +++ b/_collections/_projects/resentiment.html @@ -0,0 +1,4 @@ +--- +project: resentiment +title: Resentiment +--- From e67acb681fd727094b9ad0237ca21363f0329fd3 Mon Sep 17 00:00:00 2001 From: Kamil Adam Date: Tue, 24 Mar 2020 21:36:28 +0100 Subject: [PATCH 14/47] Update _config.yml --- _config.yml | 393 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 295 insertions(+), 98 deletions(-) diff --git a/_config.yml b/_config.yml index 3da6a50c..a3d6eac8 100644 --- a/_config.yml +++ b/_config.yml @@ -1,115 +1,312 @@ -# Welcome to Jekyll! -# -# This config file is meant for settings that affect your whole blog, values -# which you are expected to set up once and rarely edit after that. If you find -# yourself editing this file very often, consider using Jekyll's data files -# feature for the data you need to update frequently. -# -# For technical reasons, this file is *NOT* reloaded automatically when you use -# 'bundle exec jekyll serve'. If you change this file, please restart the server process. - -# Site settings -# These are used to personalize your new site. If you look in the HTML files, -# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. -# You can create any custom variable you would like, and they will be accessible -# in the templates via {{ site.myvariable }}. -title: MM -email: -description: >- # this means to ignore newlines until "baseurl:" - Write an awesome description for your new site here. You can edit this - line in _config.yml. It will appear in your document head meta (for - Google search results) and in your feed.xml site description. -twitter_username: username -github_username: username -minimal_mistakes_skin: default -search: true - -# Build settings -markdown: kramdown -remote_theme: mmistakes/minimal-mistakes -# Outputting -permalink: /:categories/:title/ -paginate: 5 # amount of posts to show -paginate_path: /page:num/ -timezone: # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +# Jekyll: https://jekyllrb.com/docs/configuration/ +## Default Configuration: https://jekyllrb.com/docs/configuration/default/ + +future : true + +### Conversion +markdown : kramdown +highlighter : rouge + +### Outputting +permalink : /:categories/:title/ +paginate_path : /:num +timezone : "Europe/Warsaw" # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + +### Where things are include: + - _collections - _pages -# Exclude from processing. -# The following items will not be processed, by default. Create a custom list -# to override the default setting. -# exclude: -# - Gemfile -# - Gemfile.lock -# - node_modules -# - vendor/bundle/ -# - vendor/cache/ -# - vendor/gems/ -# - vendor/ruby/ - -# Plugins (previously gems:) -plugins: - - jekyll-paginate - - jekyll-sitemap - - jekyll-gist - - jekyll-feed - - jemoji - - jekyll-include-cache +sass: + # sass_dir: ui/_scss + style: compressed -author: - name : "First Lastname" - avatar : "/assets/images/bio-photo.jpg" - bio : "My awesome biography constrained to a sentence or two goes here." - links: - - label: "Website" - icon: "fas fa-fw fa-link" - url: "https://" - - label: "Twitter" - icon: "fab fa-fw fa-twitter-square" - url: "https://twitter.com/" - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: "https://github.com/" - - label: "Instagram" - icon: "fab fa-fw fa-instagram" - url: "https://instagram.com/" +## Collections: https://jekyllrb.com/docs/collections/ +collections_dir : _collections +collections: + categories: + output : true + permalink : /:collection/:path/ + tags: + output : true + permalink : /:collection/:path/ + langs: + output : true + permalink : /:collection/:path/ + tools: + output : true + permalink : /:collection/:path/ + libs: + output : true + permalink : /:collection/:path/ + projects: + output : true + permalink : /:collection/:path/ + eso: + output : true + permalink : /:collection/:path/ + books: + output : true + permalink : /:collection/:path/ -footer: - links: - - label: "Twitter" - icon: "fab fa-fw fa-twitter-square" - url: "https://twitter.com/" - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: "https://github.com/" - - label: "Instagram" - icon: "fab fa-fw fa-instagram" - url: "https://instagram.com/" +## Front Matter Defaults: https://jekyllrb.com/docs/configuration/front-matter-defaults/ defaults: # _posts - scope: - path: "" - type: posts + path : "" + type : posts values: - layout: single - author_profile: true - read_time: true - comments: true - share: true - related: true + layout : post + read_time : true + comments : true + share : true + related : true + pagination : true + toc: true + toc_sticky : true + sidebar: + nav : sidebar-main # _pages - scope: - path: "_pages" - type: pages + path : "_pages" + type : pages + values: + layout : page + classes : wide + sidebar : + main : true + nav : sidebar-main + # _categories + - scope: + path : "" + type : categories + values: + layout : page-category + share : true + classes : wide + sidebar: + main : true + nav : sidebar-main + # _tags + - scope: + path : "" + type : tags + values: + layout : page-tag + share : true + classes : wide + sidebar: + main : true + nav : sidebar-main + # _langs + - scope: + path : "" + type : langs + values : + layout : page-lang + share : true + classes : wide + sidebar: + main : true + nav : sidebar-main + # _tools + - scope: + path : "" + type : tools + values: + layout : page-tool + share : true + classes : wide + sidebar: + main : true + nav : sidebar-main + # _libs + - scope: + path : "" + type : libs + values: + layout : page-lib + share : true + classes : wide + sidebar: + main : true + nav : sidebar-main + # _projects + - scope: + path : "" + type : projects + values: + layout : page-project + share : true + classes : wide + sidebar: + main : true + nav : sidebar-main + # _eso + - scope: + path : "" + type : eso + values: + layout : page-eso + share : true + classes : wide + sidebar: + main : true + nav : sidebar-main + # _books + - scope: + path : "" + type : books values: - layout: single - author_profile: true + layout : page-book + share : true + classes : wide + sidebar: + main : true + nav : sidebar-main + +# Plugins +plugins: + - jemoji + - jekyll-avatar + - jekyll-coffeescript + - jekyll-commonmark-ghpages + - jekyll-default-layout + - jekyll-feed + - jekyll-gist + - jekyll-github-metadata + - jekyll-include-cache + - jekyll-mentions + - jekyll-optional-front-matter + - jekyll-paginate + - jekyll-readme-index + - jekyll-redirect-from + - jekyll-relative-links + - jekyll-remote-theme + - jekyll-sass-converter + - jekyll-seo-tag + - jekyll-sitemap + - jekyll-titles-from-headings + +## jekyll-commonmark-ghpages +commonmark: + options : ["SMART", "FOOTNOTES"] + extensions : ["strikethrough", "autolink", "table", "tagfilter"] + +## jekyll-mentions +jekyll-mentions: + base_url : "https://twitter.com" + +## jekyll-paginate +paginate : 1 + +## jekyll-remote-theme +remote_theme : twocolumn/minimal-mistakes@gh-pages + +## jekyll-seo-tag +title : "WriteOnly.pl" +tagline : "WriteOnly Haskell, Scala, Jekyll, Git & Bash Blog" +description: >- + Blog o programowaniu w językach Haskell, Eta, Scala Native i Scala.js + oraz generatorze stron Jekyll, systemie kontroli wersji Git i użyciu wiersza poleceń CLI +url : +author: + twitter : "TheKamilAdam" + name : "Kamil Adam" + avatar : "/assets/images/kamil.adam.jpg" + bio : "My awesome biography constrained to a sentence or two goes here." +twitter: + username : "TheKamilAdam" +facebook: +logo : "/assets/favicon.ico" +social: + name : "Kamil Adam" + links: + - https://github.com/writeonly + - https://www.linkedin.com/company/writeonly-pl + - https://www.facebook.com/WriteOnlyPL + - https://www.instagram.com/degustujacaistota/ + - https://twitter.com/TheKamilAdam + +# External systems + +## Compress HTML +compress_html: + clippings : all + comments : all + endings : all + profile : false + startings : [head, body] + +## Analytics +analytics: + provider : "google-gtag" + google: + tracking_id : "UA-136426906-1" + anonymize_ip : false + +## Comments +comments: + provider : "disqus" + disqus: + shortname : "writeonly-pl" + +## Search +search : true +search_full_content : true +search_provider : "lunr" + +## Social Sharing +og_image : "/favocon.ico" +footer: + links: + - label : "GitHub" + icon : "fab fa-fw fa-github" + url : "https://github.com/writeonly" + - label : "Linkedin" + icon : "fab fa-fw fa-linkedin" + url : "https://www.linkedin.com/company/writeonly-pl/" + - label : "Facebook" + icon : "fab fa-fw fa-facebook" + url : "https://www.facebook.com/WriteOnlyPL" + - label : "Instagram" + icon : "fab fa-fw fa-instagram" + url : "https://instagram.com/degustujacaistota" + - label : "Twitter" + icon : "fab fa-fw fa-twitter" + url : "https://twitter.com/TheKamilAdam" + - label : "Dev.to" + icon : "fab fa-fw fa-dev" + url : "https://dev.to/kamiladam" + +# Theme settings +locale : "pl-PL" + +masthead_title : "Blog o programowaniu w językach Haskell, Eta, Scala Native i Scala.js" +subtitle : "oraz generatorze stron Jekyll, systemie kontroli wersji Git i użyciu wiersza poleceń CLI" +email : "kamil.adam.zabinski@gmail.com" + +repository : "writeonly/writeonly.github.io" +twitter_username : "TheKamilAdam" +github_username : "kamil-adam" + +minimal_mistakes_skin : "gold" +breadcrumbs : true +words_per_minute : 100 + +## Scripts +scripts: +# - https://code.jquery.com/jquery-3.4.1.min.js + - https://kit.fontawesome.com/4eee35f757.js + - /assets/js/main.min.js + - /assets/js/app.js +## Archives category_archive: - type: liquid - path: /categories/ + type : collection + path : /categories/ tag_archive: - type: liquid - path: /tags/ + type : collection + path : /tags/ From 0ac1fe4c44f263b9122c122fd1531a514fa47185 Mon Sep 17 00:00:00 2001 From: Kamil Adam Date: Sat, 15 Aug 2020 17:01:11 +0200 Subject: [PATCH 15/47] Create microblog --- _collections/microblog/jezyk-fullstackowy.md | 115 +++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 _collections/microblog/jezyk-fullstackowy.md diff --git a/_collections/microblog/jezyk-fullstackowy.md b/_collections/microblog/jezyk-fullstackowy.md new file mode 100644 index 00000000..afca1b19 --- /dev/null +++ b/_collections/microblog/jezyk-fullstackowy.md @@ -0,0 +1,115 @@ +W ciągu ostatnich 48h zarzucono mi to wiele rzeczy, ale nikt mi nie będzie zarzucać że nie wiem, że `nawet w WEB można się już obyć bez JS`, dlatego przeklejam tu swój stary artykuł z bloga. Mam nadzieję że przez rok się mocno nie zdezaktualizował. + +Nie jest to artykuł profesjonalisty, ale bardziej zapiski hobbysty i próba usystematyzowania nowych rzeczy. Proszę o konstruktywną krytykę. `Jesteś upośledzony` to nie jest konstruktywna krytyka, bo to już wiem (mam krótkowzroczność -8D, niezdiagnozowanego Aspergera i totalny brak gustu). BTW Podobno jest to najlepszy artykuł jaki mam na blogu, więc kolejne będą jeszcze gorsze :P + +Z pozdrowieniami dla frontendowców za to że piszą front i dzięki temu ja tego nie muszę robić. + +# Który język programowania wybrać na początek - język fullstackowy + +Wiele osób pyta się, **który język programowania wybrać na początek** jako pierwszy język do nauki. Wiele jednak zależy od tego do czego chcemy użyć tego języka programowania. Dlatego wybrałem zwycięzców w czterech kategoriach: +1. [dynamicznie typowany język skryptowy ogólnego przeznaczenia](https://www.writeonly.pl/programming/jezyk-skryptowy/) +2. [statycznie typowany język korporacyjny używany do pisania długowiecznych aplikacji klasy *enterprise*](https://www.writeonly.pl/programming/jezyk-korporacyjny/) +3. język *fullstackowy*, który można używać do pisania frontendu i backendu +4. [szybki język natywny działający bez maszyny wirtualnej i interpretera](https://www.writeonly.pl/programming/jezyk-natywny/) + +W tym artykule skupię się na zwycięzcy trzeciej kategorii, świętym Graalu programowania webowego, czyli języku *fullstackowy*, w którym można pisać zarówno frontend jak i backend. + +**Ważna uwaga!** +Artykuł nie jest sponsorowany przez Google, mimo że każdy ze zwycięskich języków jest używany przez Google. Nie jest też sponsorowany przez JetBrains. Po prostu uważam, że JetBrains tworzy najlepsze IDE. Jest to tylko mój mały subiektywny ranking języków programowania na rok 2019. + +## JavaScript oraz jego problemy i dziwactwa +**JavaScript** jest obecnie jedynym językiem programowania wspieranym przez przeglądarki bezpośrednio. Dlatego jesteśmy skazani na niego przy programowaniu frontendu działającego po stronie przeglądarki. Jednocześnie JS posiada sporo swoich problemów i dziwactw. Zaczynając od najważniejszych są to: +1. Różne implementacje w różnych przeglądarkach +2. Słabe typowanie, które pozwala robić takie rzeczy jak JSFuck +3. Obiektowość oparta na [prototypach](https://pl.wikipedia.org/wiki/Prototyp_(oprogramowanie)). + +Ostatnie nie byłoby nawet problemem gdyby nie to, że w początkowej fazie istnienia rozdziału na frontend i backend programiści backendowi byli przymuszani do pisania frontendu. Bolało to zwłaszcza programistów języków korporacyjnych, gdzie obiektowość oparta jest na [klasach](https://pl.wikipedia.org/wiki/Klasa_(programowanie_obiektowe)). + +### Podejście zerowe +Początkowo głównym problemem z JavaScryptem było to, że był różnie implementowany w różnych przeglądarkach. W niektórych przeglądarkach brakowało niektórych funkcji, w innych te same funkcje zachowywały się różnie. Spowodowało to powstawanie wielu różnych nakładek i bibliotek na JS, z czego jedna stała się *de facto* standardem - **JQuery**. Było to jednak w dawnych czasach, gdy programowanie JavaScripcie było proste, ponieważ służył on głownie do robienia bezsensownych animacji na strona oraz customowych menu kontekstowych. + +Obecnie głównym problemem pisania aplikacji webowych jest to, że potrzebujemy osobnych programistów do tworzenia frontendu (*frontendowców*) i osobnych do tworzenia backendu (*backendowców*). Teoretycznie istnieje święty Graal wszelakiej rekrutacji, człowiek orkiestra, Leonardo da Vinci swoich czasów, czyli fullstackowiec potrafiący pisać zarówno frontend jak i backend. W praktyce jest to jednak osoba znająca się na wszystkim, ale po trochu. Trochę jak np. internista w szpitalu. Rozwiązaniem jest dopiero język *fullstackowy*. + +### Podejście pierwsze - język backendowy transpilowany do JS + +Ponieważ programistów JavaScriptu było mało, a frontend stawał się coraz bardziej skomplikowany pojawił się pomysł, żeby już istniejące języki backendu transpilować do JS. Dzięki temu programiści backendu mogą z marszu stać się programistami frontendu. Tutaj są dwie grupy rozwiązań: +* Biblioteki graficzne przeniesione do **JS**: + * RAP (ang. *Remote Aplication Platform*) - to implementacja SWT (ang. *Standard Widget Toolkit*). SWT to niestandardowy (sic!) zestaw bibliotek graficznych dla Javy, posiadający implementacje dla najpopularniejszych systemów operacyjnych (Mac, Linux, Windows). +* GWT (ang. *Google Widget Toolkit*) - biblioteka graficzna zaprojektowana dla Javy specjalnie pod transpilację do JS + * Pyjs - Port GWT dla języka **Python** +* Język backendu przeniesiony do frontendu jednocześnie nie narzucający frameworka widoku i biblioteki graficznej: + * **Scala.js** - posiada już dedykowane frameworki ja *Udash* + * Kotlin.js - dalej w procesie projektowania + * Ceylon.js - transpilowany do CommonJS + * ClojureScript* - używa Closure Compiler do optymalizacji kodu. Tutaj pojawia się gwiazdka ponieważ istnieją [różnice](https://clojurescript.org/about/differences) pomiędzy +**Clojure** i ClojureScript + * Dla Javy: + * J2CL - transpilator od Google używający Closure Compiler i będący następcą GWT + * JSweet - transpilator do języka **TypeScript**, który następnie jest transpilowany do JS + * Programiści języka **Haskell** także podejmowali kilka prób stworzenia transpilatora z Haskella do JS. Są to Fay, GHCJS, Haste i uhc + * Dla języka **OCaml** jest kilka transpilatorów, ale aktualnie najbardziej obiecująco wygląda **BuckleScript.** + * Język **Racket** był jednym z pierwszych transpilowanych do JS, aktualnie jest rozwijany RacketScript + +### Podejście drugie - nowy język transpilowany do JS + +Według wielu programistów pierwsze podejście było niezadowalające. Istniało wiele problemów z językami backedowymi transpilowanymi do JS. Prawdopodobnie największym jest przetwarzanie współbieżne. Statycznie typowane języki korporacyjne posiadają wielowątkowość do przetwarzania współbieżnego. Interpretery JS posiadają jeden wątek i do przetwarzania współbieżnego używa konstrukcji `Promise` (*obietnicy*), której najbliżej do monady) `IO` z Haskella lub FlatMappable `Future` ze Scali. Ponieważ nie da się łatwo zamienić modelu `Promise` na wielowątkowość to jest to problemem. + +Ponieważ pierwsze podejście okazało się niezadowalające postanowiono stworzyć nowe języki programowania będące jak najbardziej podobne do JS, ale jednocześnie rozwiązujące znane w nim problemy. Te języki to m.in.: +* **CoffeeScript** - pierwszy popularny, nowy język transpilowany do JS. Posiada klasy i *normalne* dziedziczenie, ale typowany dynamicznie. Promowany przez programistów **Ruby** we frameworku RoR +* **LiveScript** - funkcyjny następca CoffeeScriptu +* **TypeScript** - rozszeżenie **JS** o statyczne typowanie i klasy +* **PureScript** - **Haskell**, ale wykonywany zachłannie, czyli jak *normalny* język programowania, a nie Haskell +* **ReasonML** - **OCaml** ze zmienioną składnią w celu upodobnienia go do **JS**. Także transpilowany za pomocą **BuckleScript**. +* **Elm** * - język będący połączeniem języków **Meta Language** i **Haskell** oraz będący już zintegrowany z frameworkami React i Redux (implementacja architektury Flux). Jest z gwiazdką, bo jest dedykowany tylko do pisania frontendu. Jednocześnie posiada ciekawą właściwość jaką jest usuwanie martwego kodu podczas transpilacji. Dlatego aplikacja napisana w Elmie może być mniejsza niż kod Reacta +* **Dart** * - kolejny język z gwiazdką. Mimo że jest względnie młody przeszedł już historię dzielącą się na etapy: + 1. Natywny język przeglądarki - niestety wykonywany natywnie tylko przez Chroma. Inne przeglądarki miały posiadać maszynę wirtualną Darta napisaną w JS. + 2. Język do pisania frontendu, transpilowany do JS + 3. Język do pisania frontendu i aplikacji mobilnych za pomocą frameworku Flutter + +Ale jakim cudem w ogóle można mówić o JS jako o języku *fullstackowym*? Spójrzy na małe kalendarium: +* 2007 - Qt dodaje Qt Script, własną implementacją EmcaScriptu, język jest dedykowany do tworzenia **GUI**. Teoretycznie nic to nie wnosi do tej historii, ale zainspirowało niektórych +* 2008 - Gnome dodaje dwa [bindingi Javascriptu](https://wiki.gnome.org/JavaScript), Gjs i Seed. Są one dedykowane do tworzenia **GUI** w miejsce wcześniej stosowanego w tym celu języka **Python**, ale można ich używać jako główny język do pisania aplikacji oraz jako język skryptowy +* 2009 - powstaje Node.js, wyciągnięty z przeglądarki interpreter] V8, który można używać z linii poleceń CLI +* 2011 - powstaje Vert.x, framework do przetwarzania współbieżnego na JVM, który posiada API m.in. w JavaScripcie + +A więc wszystkie języki transpilowane do JS (i sam JS oczywiście) można wykonywać po stronie backendu! +### Podejście wygrywające +zwycięzcą kategorii *język fullstackowy* jest język transpilowany do JS i jest to ... JavaScript! + +Co?! Dlaczego JavaScript jest transpilowany do JavaScriptu? Wynika to z tego, że w międzyczasie tworzenia gigantycznej ilości nakładek do JavaScriptu sam Javascript też był rozwijany. Niestety dalej trzeba wspierać stare przeglądarki używające starszych wersji JavaScriptu. Dlatego jest potrzebny transpilator Babel zamieniający ECMAScript 6 do ECMAScript 5. + +A dlaczego JavaScript jest zwycięzcą? +* Ewoluował i nie jest już tak brzydki jak kiedyś +* Jest popularny - język roku 2014 według Tiobe +* Nawet jeśli będziesz pisać frontend w innym języku programowania niż JS to prawdopodobnie i tak będziesz musiał znać podstawy JavaScriptu, +* Niestety [WebStorm] od JetBains jest płatny, ale na szczęście istnieją inne dobre IDE dla JavaScriptu jak Atom lub VSC. +* Chyba wreszcie zrozumieli JS w Google. Google długo miało problemy z JavaScriptem i dlatego m.in. stworzyło GWT. Jednak od pewnego czasu Google wspiera użycie JavaScriptu np w Google Cloud Functions, gdzie można używać tylko i wyłącznie JavaScriptu. + +Ale nie to jest największą zaletą JavaScriptu! Największą zaletą jest Node.js. A największą zaletą Node.js jest to, że jest jednowątkowy i zmusza do pisania aplikacji funkcyjnych, asynchronicznych i reaktywnych + +Node.js nie jest jedynym asynchronicznym serwerem. Istnieją także inne jak: +* Vert.x - wielki framework dla języków JavaScript, Java, Scala, Kotlin, **Ceylon**, Groovy i **Ruby** +* Lagom - kolejny wielki framework, dedykowany dla języka Java +* Akka HTTP - biblioteka dedykowana dla języków **Scala** i Java +* Spark - biblioteka dedykowana dla języków **Kotlin** i Java +* Ratpack - biblioteka dedykowana dla języków Groovy i Java +* WebFlux - reaktywa wersja Springa + +Wiele innych frameworków także zaczyna wspierać programowanie asynchroniczne, jak np. DropWizard. +## Podsumowanie + +Trudno tutaj o dobre podsumowanie. Chyba nie mam odwagi polecić komuś JavaScript/TypeScript do nauki jako pierwszy język do programowania. +Głównie obawiając się o zdrowie psychiczne tej osoby, która miałaby programować w JS. + +Osobiście jestem zwolennikiem statycznie typowanych języków backendu transpilowanych do JS, jak np. Scala.js lub Kotlin.js. Pozwala to współdzielić kod między frontendem i backendem, który teoretycznie może być nawet aplikacją natywną. Jednocześnie w tych językach programowania do rozwiązywania problemów asynchroniczności są preferowane konstrukcje `Future` oraz **monady** `IO`, które łatwo przetłumaczyć na `Promise` z JS. + +Równie ciekawy wydaje się także, pojawiącący w niektórych zagranicznych ogłoszeniach o pracę, stos technologiczny PHP, czyli: +* **P**ureScript na frontendzie +* **H**askell na backandzie +* **P**ostgreSQL jako baza danych + +Są to co prawda dwa języki programowania do nauki, ale bardzo podobne do siebie. O wiele bardziej niż Java i JavaScript. Więc opanowanie jednego z nich, gdy umie się już drugi nie powinno być problemem. + +[Link do oryginalnego artykułu](https://www.writeonly.pl/programming/jezyk-fullstackowy/) + +#writeonly From 0d93e4137466e093320f2173183971d6b9fd85f6 Mon Sep 17 00:00:00 2001 From: Kamil Adam Date: Sun, 10 May 2020 20:02:36 +0200 Subject: [PATCH 16/47] Add posts --- ...18-08-13-dlaczego-warto-prowadzic-bloga.md | 66 +++ ...08-22-jekyll-i-ciasteczka-pliki-cookies.md | 108 +++++ .../2018-08-30-alias-komenda-powloki-bash.md | 105 +++++ .../2018-09-05-docker-usuwanie-obrazow.md | 56 +++ .../2018-09-12-git-submoduly-i-aliasy.md | 124 +++++ .../2018-09-26-jekyllcodex-org-czesc-druga.md | 55 +++ .../_posts/2018-10-03-przenosna-scala.md | 149 ++++++ .../_posts/2018-10-10-git-flow-i-aliasy.md | 79 ++++ .../2018-10-17-dynamiczna-analiza-kodu.md | 183 ++++++++ .../2018-10-24-statyczna-analiza-kodu-1.md | 167 +++++++ .../2018-10-31-statyczna-analiza-kodu-2.md | 176 +++++++ .../2018-11-07-flagi-kompilatora-scalac.md | 156 +++++++ ...-11-14-bardziej-dynamiczna-analiza-kodu.md | 242 ++++++++++ .../_posts/2018-12-12-ciagla-intergracja.md | 275 +++++++++++ .../2019-01-09-biblioteki-do-logowania.md | 370 +++++++++++++++ .../_posts/2019-01-16-kategorie-i-tagi.md | 247 ++++++++++ .../2019-01-30-no-universal-equality.md | 179 +++++++ _collections/_posts/2019-02-13-setxkbmap.md | 74 +++ _collections/_posts/2019-02-27-scalapipe.md | 243 ++++++++++ .../2019-03-13-biblioteki-do-parserowania.md | 216 +++++++++ .../_posts/2019-03-27-arrow-asserts.md | 108 +++++ .../2019-04-10-jekyllcodex-org-head-seo.md | 105 +++++ .../_posts/2019-04-17-git-rebase-i.md | 183 ++++++++ .../_posts/2019-04-24-git-rebase-i-edit.md | 92 ++++ .../_posts/2019-05-01-github-pages.md | 160 +++++++ .../_posts/2019-05-08-gcc-llvm-clang.md | 334 +++++++++++++ _collections/_posts/2019-05-15-slogging.md | 251 ++++++++++ ...019-05-22-czy-nadaje-sie-na-programiste.md | 210 +++++++++ _collections/_posts/2019-05-29-logback.md | 148 ++++++ ...ers-programownnie-funkcyjne-dla-kazdego.md | 103 ++++ .../_posts/2019-06-12-jezyk-skryptowy.md | 165 +++++++ .../_posts/2019-06-19-jezyk-korporacyjny.md | 164 +++++++ .../_posts/2019-07-03-jezyk-fullstackowy.md | 312 +++++++++++++ .../_posts/2019-07-10-jezyk-natywny.md | 248 ++++++++++ .../2019-07-24-paradygmaty-programowania.md | 250 ++++++++++ .../_posts/2019-08-07-jezyk-wszystkomajacy.md | 183 ++++++++ .../_posts/2019-09-25-no-exceptions.md | 441 ++++++++++++++++++ .../2019-10-30-no-exceptions-std-lib.md | 261 +++++++++++ ...2019-11-27-no-exceptions-thridparty-lib.md | 255 ++++++++++ .../2019-12-25-no-exceptions-io-monad.md | 317 +++++++++++++ .../_posts/2020-01-29-git-filter-branch.md | 116 +++++ _collections/_posts/2020-02-26-preload-css.md | 98 ++++ .../_posts/2020-03-11-interface-collection.md | 155 ++++++ .../_posts/2020-04-01-sojowe-latte.md | 81 ++++ .../_posts/2020-04-15-serialization.md | 255 ++++++++++ _collections/_posts/2020-04-29-monads.md | 363 ++++++++++++++ .../_posts/2020-05-13-polymorphism.md | 391 ++++++++++++++++ _collections/_posts/2020-05-27-type-class.md | 372 +++++++++++++++ _collections/_posts/2020-06-17-state-monad.md | 253 ++++++++++ .../_posts/2020-07-01-optparse-applicative.md | 221 +++++++++ _collections/_posts/2020-08-05-attoparsec.md | 401 ++++++++++++++++ 51 files changed, 10266 insertions(+) create mode 100644 _collections/_posts/2018-08-13-dlaczego-warto-prowadzic-bloga.md create mode 100644 _collections/_posts/2018-08-22-jekyll-i-ciasteczka-pliki-cookies.md create mode 100644 _collections/_posts/2018-08-30-alias-komenda-powloki-bash.md create mode 100644 _collections/_posts/2018-09-05-docker-usuwanie-obrazow.md create mode 100644 _collections/_posts/2018-09-12-git-submoduly-i-aliasy.md create mode 100644 _collections/_posts/2018-09-26-jekyllcodex-org-czesc-druga.md create mode 100644 _collections/_posts/2018-10-03-przenosna-scala.md create mode 100644 _collections/_posts/2018-10-10-git-flow-i-aliasy.md create mode 100644 _collections/_posts/2018-10-17-dynamiczna-analiza-kodu.md create mode 100644 _collections/_posts/2018-10-24-statyczna-analiza-kodu-1.md create mode 100644 _collections/_posts/2018-10-31-statyczna-analiza-kodu-2.md create mode 100644 _collections/_posts/2018-11-07-flagi-kompilatora-scalac.md create mode 100644 _collections/_posts/2018-11-14-bardziej-dynamiczna-analiza-kodu.md create mode 100644 _collections/_posts/2018-12-12-ciagla-intergracja.md create mode 100644 _collections/_posts/2019-01-09-biblioteki-do-logowania.md create mode 100644 _collections/_posts/2019-01-16-kategorie-i-tagi.md create mode 100644 _collections/_posts/2019-01-30-no-universal-equality.md create mode 100644 _collections/_posts/2019-02-13-setxkbmap.md create mode 100644 _collections/_posts/2019-02-27-scalapipe.md create mode 100644 _collections/_posts/2019-03-13-biblioteki-do-parserowania.md create mode 100644 _collections/_posts/2019-03-27-arrow-asserts.md create mode 100644 _collections/_posts/2019-04-10-jekyllcodex-org-head-seo.md create mode 100644 _collections/_posts/2019-04-17-git-rebase-i.md create mode 100644 _collections/_posts/2019-04-24-git-rebase-i-edit.md create mode 100644 _collections/_posts/2019-05-01-github-pages.md create mode 100644 _collections/_posts/2019-05-08-gcc-llvm-clang.md create mode 100644 _collections/_posts/2019-05-15-slogging.md create mode 100644 _collections/_posts/2019-05-22-czy-nadaje-sie-na-programiste.md create mode 100644 _collections/_posts/2019-05-29-logback.md create mode 100644 _collections/_posts/2019-06-05-fun-miners-programownnie-funkcyjne-dla-kazdego.md create mode 100644 _collections/_posts/2019-06-12-jezyk-skryptowy.md create mode 100644 _collections/_posts/2019-06-19-jezyk-korporacyjny.md create mode 100644 _collections/_posts/2019-07-03-jezyk-fullstackowy.md create mode 100644 _collections/_posts/2019-07-10-jezyk-natywny.md create mode 100644 _collections/_posts/2019-07-24-paradygmaty-programowania.md create mode 100644 _collections/_posts/2019-08-07-jezyk-wszystkomajacy.md create mode 100644 _collections/_posts/2019-09-25-no-exceptions.md create mode 100644 _collections/_posts/2019-10-30-no-exceptions-std-lib.md create mode 100644 _collections/_posts/2019-11-27-no-exceptions-thridparty-lib.md create mode 100644 _collections/_posts/2019-12-25-no-exceptions-io-monad.md create mode 100644 _collections/_posts/2020-01-29-git-filter-branch.md create mode 100644 _collections/_posts/2020-02-26-preload-css.md create mode 100644 _collections/_posts/2020-03-11-interface-collection.md create mode 100644 _collections/_posts/2020-04-01-sojowe-latte.md create mode 100644 _collections/_posts/2020-04-15-serialization.md create mode 100644 _collections/_posts/2020-04-29-monads.md create mode 100644 _collections/_posts/2020-05-13-polymorphism.md create mode 100644 _collections/_posts/2020-05-27-type-class.md create mode 100644 _collections/_posts/2020-06-17-state-monad.md create mode 100644 _collections/_posts/2020-07-01-optparse-applicative.md create mode 100644 _collections/_posts/2020-08-05-attoparsec.md diff --git a/_collections/_posts/2018-08-13-dlaczego-warto-prowadzic-bloga.md b/_collections/_posts/2018-08-13-dlaczego-warto-prowadzic-bloga.md new file mode 100644 index 00000000..635837ba --- /dev/null +++ b/_collections/_posts/2018-08-13-dlaczego-warto-prowadzic-bloga.md @@ -0,0 +1,66 @@ +--- +title: "Dlaczego warto prowadzić bloga o programowaniu" +author: TheKamilAdam +category: thoughts +tags: blog +langs: scala java +tools: github +libs: +redirect_from: + - dlaczego-warto-prowadzic-bloga + - thoughts/dlaczego-warto-prowadzic-bloga + - thoughts/2018/08/13/dlaczego-warto-prowadzic-bloga.html +--- + +Ja znalazłem cztery powody, dlaczego warto prowadzić bloga o programowaniu. +Natrafiałem na nie właśnie w tej kolejności. + +## Notatki dla siebie +Nie wiem jak inni programiści, +ale ja mam dobrą pamięć do rzeczy złożonych, +jak np. składnia języka **[Scala]**, +i słabą pamięć do rzeczy prostych, jak np. “z jakimi parametrami należy budować projekt w języku Scala, żeby otrzymać żądany efekt”. +Rezultat tego był taki, że wielokrotnie przeszukiwałem internet w poszukiwaniu tych samych rzeczy. +Pierwszą próbą rozwiązania mojego problemu był pomysł zapisywania wiedzy w pliku na dysku. +Niestety po formacie dysku ta wiedza się traciła. +Kolejnym krokiem było trzymanie wiedzy w chmurze np. w Dokumentach Googlowych. + +## Pomoc dla innych +Nie tylko ja mam słabą pamięć. +Poza tym do pracy przychodzą też nowi lub ludzie z innych zespołów, którzy chcą się czegoś dowiedzieć. +Głupio mi było odpowiedzieć, że czegoś nie pamiętam, gdy wiedzą że robię to od roku.q +Wycinanie fragmentów Dokumentów Googlowych i wysyłanie ich przez komunikator nie było najbardziej eleganckim rozwiązaniem. +Więc założyłem repozytorium na **[GitHubie]** i odsyłałem linki jako odpowiedzi na pytania. +Było to rozwiązanie lepsze, ale nie zadowalało mnie w pełni, +ponieważ w ten sposób mogłem pomóc tylko osobom, które mnie bezpośrednio poprosiły o pomoc. + +## Dokumentowanie swojego postępu prac + +Repozytoria na GitHubie, bez dodatkowej dokumentacji szybko mogą przerodzić się w chaos. +Tak było zarówno w przypadku mojego repozytorium zawierającego wiedzę, jak i moich projektów Scalowych. +Blog będący Dziennikiem Postępów Prac pozwala temu przeciwdziałać i zaprezentować innym zastosowane rozwiązania. + +## Dostanie wymarzonej pracy +Jest to ostatni powód, który odkryłem. Jest to także powód, który ostatecznie zmotywował mnie do założenia tego bloga. + +Jestem programistą **[Javy]**, a chcę być programistą **[Scali]**. +Fascynuję się tym językiem programowania odkąd o nim usłyszałem, czyli dłużej niż pracuję w Javie. +Niestety na Górnym Śląsku, gdzie mieszkam, jest mało pracy dla programistów Scali. +Można pracować zdalnie, ale jest ciężko przekonać firmy programistyczne, +że umie się Scalę, gdy pisało się w niej komercyjnie tylko pół roku. + +Mimo że hobbistycznie piszę w niej od pięciu lat. + +## Podsumowanie +Mój plan jest taki, +żeby umieszczać tu posty głównie związane z postępem prac nad moimi projektami opensoursowymi pisanymi dla wprawy. +Dokumentować tutaj każdą większą zmianę wraz z uzasadnieniem. + +Mam nadzieję, że będzie to jasne i łatwe do zrozumienia zarówno dla mnie, gdy po roku wrócę do swojego kodu, +jak i dla wszystkich innych, którzy chcieliby się nauczyć języka **[Scala]**. + +[Javy]: /langs/java +[Scala]: /langs/scala +[Scali]: /langs/scala + +[GitHubie]: /tools/github diff --git a/_collections/_posts/2018-08-22-jekyll-i-ciasteczka-pliki-cookies.md b/_collections/_posts/2018-08-22-jekyll-i-ciasteczka-pliki-cookies.md new file mode 100644 index 00000000..2cfc0217 --- /dev/null +++ b/_collections/_posts/2018-08-22-jekyll-i-ciasteczka-pliki-cookies.md @@ -0,0 +1,108 @@ +--- +title: "Jekyll i ciasteczka (pliki cookies)" +author: TheKamilAdam +category: jekyll +tags: blog html jekyllcodex github-pages +labels: cookies +tools: jekyll +libs: jquery +redirect_from: + - jekyll-i-ciasteczka-pliki-cookies + - jekyll/jekyll-i-ciasteczka-pliki-cookies + - writeonlydoc/jekyll-i-ciasteczka-pliki-cookies + - writeonlydoc/2018/08/22/jekyll-i-ciasteczka-pliki-cookies.html +--- + +Przed założeniem bloga opartego na technologiach **[Jekyll]** i **[GitHub Pages]** +przejrzałem sporo polskojęzycznych blogów z opisem “Jak to zrobić”. +Wszyscy mówili, że jest to niesamowicie proste. +Za wyjątkiem jednego malkontenta, Gutka (). + +Pierwszy wieczór spędziłem na nieudanej instalacji Jekylla i próbie uruchomienia przykładowego bloga opartego na motywie graficznym BlackDocs +(). +Wieczór zakończyłem w momencie przeczytania porady na StackOverflow “usuń Rubiego i zainstaluj wszystko ponownie”. +Wtedy stwierdziłem, że może jednak Gutek ma rację i Jekyll bywa problematyczny. + +Jednak nie poddałem się i po paru wieczorach miałem pusty blog z możliwością komentowania postów i skryptem raportującym statystyki +(funkcjonalność dodana według ) +oraz nie do końca skonfigurowaną wyszukiwarką (). +W tym momencie uznałem, że dalsze usprawnianie bloga, który nie zawiera żadnej treści, nie ma żadnego sensu. +W związku z czym odłożyłem na czas późniejszy dodawanie tagów i kategorii “bez użycia pluginu” +( i ). +A nuż w międzyczasie plugin dla kategorii znajdzie się na liście pluginów wspieranych przez **[GitHub Pages]** +(Aktualna lista wspieranych pluginów ). + +Po opublikowaniu pierwszego posta czułem jednak, że dalej czegoś brakuje. Wciśnięcie F12 i spojrzenie w kod strony było przerażające. +Ujrzałem spaghetti w postaci naprzemiennie występujących fragmentów HTML, CSS i JS. +Ale najgorsze było, gdy spojrzałem na zakładkę “Storage”. +Okazało się, że moja prosta strona internetowa zapisuje, o zgrozo, ciasteczka. +O ile ciasteczko dla systemu komentarzy Disqus można jeszcze obronić, że jest technicznie potrzebne do działania funkcjonalności, +o tyle ciasteczko dla Google Analytics jest już jawnym śledzeniem użytkownika! + +Poszukiwanie rozwiązania mojego problemu doprowadziło mnie do strony , +a dokładniej do sekcji “Without plugins” (). +Znalazłem tam skrypt wyświetlający banner informujący o plikach Cookies. Po moich przeróbkach wygląda on tak: + +```html + + + + +``` + +Czuję, że na stronie spędzę więcej czasu z czego wynikają dwie smutne informacje: +* później zacznę pisać o języku **[Scala]** +* istnieje niebezpieczeństwo, że nauczę się jQuery + +[Scala]: /langs/scala + +[Jekyll]: /tools/jekyll + +[GitHub Pages]: /tags/github-pages diff --git a/_collections/_posts/2018-08-30-alias-komenda-powloki-bash.md b/_collections/_posts/2018-08-30-alias-komenda-powloki-bash.md new file mode 100644 index 00000000..0831ac76 --- /dev/null +++ b/_collections/_posts/2018-08-30-alias-komenda-powloki-bash.md @@ -0,0 +1,105 @@ +--- +title: "Alias - polecenie powłoki Bash" +author: TheKamilAdam +category: cli +tags: alias cli +tools: bash git +redirect_from: + - alias-komenda-powloki-bash + - cli/alias-komenda-powloki-bash + - cli/2018/08/30/alias-komenda-powloki-bash.html +--- + +Jeśli: +* masz problemy z zapamiętywaniem skomplikowanych poleceń Basha lub innych programów wywoływanych przez wiersz poleceń +* nie lubisz wpisywać w terminalu długich poleceń z podkomendami i przełącznikami + +istnieje rozwiązanie twoich problemów! Jest to polecenie powłoki Bash o nazwie `alias`. + +Polecenie `alias` można wywołać bez żadnego parametru: +```bash +alias +``` + +wyświetli wtedy listę wszystkich zdefiniowanych aliasów dostępnych w danym terminalu. + +## Tworzenie aliasów + +Żeby utworzyć nowy alias należy wywołać: +```bash +alias NAME=VALUE +``` + +gdzie: +* `NAME` oznacza nazwę nowego "polecenia" do wywoływania w wierszu poleceń +* `VALUE` jest poleceniem, lub ciągiem poleceń, które zostanie wywołana naprawdę. +Jeśli chcemy, żeby `VALUE` zawierało spację należy wszystko umieścić w parze apostrofów (') lub cudzysłowów (") + +Przykłady użycia: +```bash +alias rf='fm -rf' +alias ..='cd ..' +alias cwd='cd `pwd`' +``` +Od teraz : +* polecenie `rf <ścieżka_do_folderu>` będzie usuwać foldery, +* polecenie `..` przechodzić do folderu wyżej w drzewie hierarchii folderów, +* polecenie `cwd` będzie odświeżać aktywny folder. + +## Tworzenie permanentnych aliasów +Problem z aliasami jest tylko jeden. Działają tylko w ramach terminala w którym zostały zdefiniowane. +Można to rozwiązać w prosty sposób poprzez stworzenie wszystkich aliasów w pliku, np. pod nazwą `~/.bash_aliases`, +a następnie wczytywania go za pomocą polecenia `source`. +Teraz po otwarciu nowego terminala wystarczy wywołać: +```bash +source ~/.bash_aliases +``` + +by cieszyć się swoimi aliasami w każdym terminalu. + +## Automatyczne wczytywanie aliasów +Można to jednak jeszcze bardziej uprościć. +Przy starcie każdego nowego terminala jest wczytywany plik `~/.bashrc`. +Wystarczy na końcu tego pliku dodać: +```bash +source ~/.bash_aliases +``` +lub bezpieczniej: +```bash +if [ -f ~/.bash_aliases ]; then + . ~/.bash_aliases +fi +``` +Dodatkowy `if` uchroni nas przed błędem, gdy plik `~/.bash_aliases` nie istnieje. +Kropka na początku drugiej linii jest aliasem na `source`. + +Dodatkowo do pliku `~/.bash_aliases` warto dodać aliasy: +```bash +alias vish='vim ~/.bashrc' +alias srcsh='source ~/.bashrc' +``` + +## Niezniszczalne aliasy +Używanie aliasów nie jest niczym nowym. +Możliwe, że w waszej dystrybucji będzie już istnieć plik `~/.bash_aliases` czekający na uzupełnienie +lub nawet będzie już wczytywany w pliku `~/.bashrc`. +Także cała powyższa procedura jest opisana na wielu stronach i blogach. + +Mój główny problem z aliasami polegał na tym, +że gdy już miałem uzbierany pokaźny zestaw aliasów ułatwiających mi życie padł mi dysk w służbowym laptopie. +Komputer poszedł do działu IT, wymienili mi dysk, ale ja straciłem wszystkie aliasy. + +Stwierdziłem wtedy "Moja wina, bo wszystko, co tylko można, należy trzymać w chmurze". +Więc założyłem repozytorium na githubie [cli](https://github.com/writeonly/cli), +gdzie w pliku [bash_aliases](https://github.com/writeonly/cli/blob/master/bash_aliases) ponownie zbieram potrzebne mi aliasy. +Dodatkowo zrobiłem mały skrypt instalujący, tak by jedną linią +```bash +wget https://raw.githubusercontent.com/writeonly/cli/master/bash_aliases_install.sh | bash +``` +móc odzyskać wszystko to, do czego przywykłem. + +A polecenie: +```bash +update_aliases +``` +można zaktualizować aliasy. \ No newline at end of file diff --git a/_collections/_posts/2018-09-05-docker-usuwanie-obrazow.md b/_collections/_posts/2018-09-05-docker-usuwanie-obrazow.md new file mode 100644 index 00000000..3de1475d --- /dev/null +++ b/_collections/_posts/2018-09-05-docker-usuwanie-obrazow.md @@ -0,0 +1,56 @@ +--- +title: "Docker - usuwanie obrazów" +author: TheKamilAdam +category: cli +tags: alias cli +tools: docker +redirect_from: + - docker-usuwanie-obrazow + - cli/docker-usuwanie-obrazow + - cli/2018/09/05/docker-usuwanie-obrazow.html +--- + +Dziesięć lat pracy na Linuksie nauczyło mnie, +że jeśli Linuks zaczyna magicznie i bez ostrzeżenia sam z siebie nie działać +to najprawdopodobniej skończyło się miejsce na dysku. +Identycznie jest z Dockerem. +Jeśli lokalnie stawiamy chmurę mikroserwisów, +które często pojawiają się w nowych wersjach, +to prędzej czy później zabraknie nam miejsca na dysku. +W skrajnym wypadku, na laptopie zastępczym, musiałem dwa razy w tygodniu usuwać obrazy Dockerowe. + +## Procedura usuwania obrazów Dockerowych +Szczęśliwie procedura usuwania obrazów Dockerowcyh nie jest czynnością skomplikowaną i składa się z trzech kroków. +Na początek włączamy terminal i teraz kolejno wykonujemy kroki: + +Krok 1. Zatrzymujemy wszystkie kontenery: +```bash +docker kill $(docker ps -q) +``` + +Krok 2. Usuwamy wszystkie kontenery: +```bash +docker rm $(docker ps -a -q) +``` + +Krok 3. Usuwamy wszystkie obrazy: +```bash +docker rmi $(docker images -q) +``` + +## Wszystko razem +Można także wykonać wszystko razem jako jedno, połączone polecenie w terminalu: + +```bash +docker kill $(docker ps -q); docker rm $(docker ps -a -q); docker rmi $(docker images -q) +``` + +Lepiej jest jednak dodać wpis do pliku `~/.bash_aliases` : +```bash +alias docker_rmi_all='docker kill $(docker ps -q); docker rm $(docker ps -a -q); docker rmi $(docker images -q)' +``` + +I wtedy wystarczy z wywołać w terminalu: +```bash +docker_rmi_all +``` diff --git a/_collections/_posts/2018-09-12-git-submoduly-i-aliasy.md b/_collections/_posts/2018-09-12-git-submoduly-i-aliasy.md new file mode 100644 index 00000000..3c2fdf96 --- /dev/null +++ b/_collections/_posts/2018-09-12-git-submoduly-i-aliasy.md @@ -0,0 +1,124 @@ +--- +title: "Git - submoduły i aliasy" +author: TheKamilAdam +category: cli +tags: cli alias interpreter +labels: submodule +langs: java javascript +tools: git +redirect_from: + - git-submoduly-i-aliasy + - cli/git-submoduly-i-aliasy + - cli/2018/09/12/git-submoduly-i-aliasy.html +--- + +*Post oparty na prawdziwych wydarzeniach i traumach* + +## Submoduły + +Zrobili to! +Mimo że protestowałem. +Do projektu przy którym pracowałem dodali submoduły. + +- Jak mogli to zrobić - zadaje pytanie oburzony. +- Prosto - pada odpowiedź - za pomocą polecenia o składni: +```bash +git submodule add link-do-repozytorium opcjonalna-nazwa-folderu +``` +A w tym wypadku było to dokładnie: +```bash +git submodule add https://github.com/paulp/sbt-extras.git sbtx +``` +wykonane w folderze projektu. + +- Ale po co? - drążę dalej temat. +- Żeby współdzielić kod. +- Przecież do tego służą biblioteki! - moje oburzenie sięga zenitu. +- Ale nie zawsze jest to łatwe i możliwe: + 1. Biblioteki trzeba wydawać i trzymać je w menedżerze repozytoriów binarnych (ang. *Binary Repository Manager*), +a takiego menedżera trzeba gdzieś zainstalować. + 2. Menedżer repozytorium binarnych może kosztować, np. dla języka **[Java]** jest za darmo, a dla języka **[JavaScript]** już niekoniecznie. + 3. W językach skryptowych biblioteki są często instalowane do **[interpretera]** + co może utrudniać instalację programu klientowi +(przykład z pierwszej wersji [Git-Tools-Submodules]()). + 4. Nie wszystko da się umieścić w bibliotece, w tym wypadku jest to skrypt do budowania projektu. + +## Dodatkowe polecenia +Tak przekonany przestałem marudzić + i przejrzałem [Git-Tools-Submodules](), + [git-submodule]() + oraz [git-clone](). + Okazało się, że praca z submodułami nie jest taka straszna i sprowadza się głównie do dwóch poleceń: + +```bash +git clone --recurse-submodules +git submodule update --init --recursive +``` +Pierwsze polecenie ściąga repozytorium ze wszystkimi submodułami. +Drugie - aktualizuje submoduły po przełączeniu się na inną rewizję (ang. *commit*), gałąź lub po aktualizacji gałęzi. + +Niestety polecenia te wydłużają i tak długie już polecenia gita. + +## Aliasy Basha + +Szczęśliwie umiem rozwiązywać problem długich poleceń, +bo znam [aliasy basha](/alias-komenda-powloki-bash). + +Pierwsza wersja moich aliasów wyglądała następująco: +```bash +alias g='git' +alias gcl='git clone' +alias gclr='gcl --recurse-submodules' +alias gupdate='git submodule update --init --recursive' +alias gpr='git pull --rebase' +alias gpru='gpr && gupdate' +alias master='git checkout master && gupdate' +alias develop='git checkout develop && gupdate' +``` + +## Aliasy Gita + +Byłem bardzo zadowolony ze swoich aliasów, więc postanowiłem się nimi pochwalić koledze: +- To bez sensu - odpowiedział zdziwiony - +przecież git ma swój system [aliasów](). + +Po przeczytaniu [Git-Basics-Git-Aliases]() +i [git-config]() +mój zbiór aliasów zamienił się w zestaw poleceń gita do wykonania: + +```bash +git config --global alias.cl 'clone' +git config --global alias.clr 'cl --recurse-submodules' +git config --global alias.update 'submodule update --init --recursive' +git config --global alias.pr 'pull --rebase' +git config --global alias.pru '!git pr && git update' +git config --global alias.master '!git checkout master && git update' +git config --global alias.develop '!git checkout develop && git update' +git config --global alias.tig '!tig' +``` +Widać tutaj, że podkomenda `alias` ma dwie składnie: +```bash +git config --global alias.nowa_komenda_gita 'stara_komenda_gita_z_parametrami' +git config --global alias.nowa_komenda_gita '!dowolna_komenda_basha' +``` + +I od teraz: +* `git cl` - klonuje repozytorium; +* `git clr` - klonuje repozytorium razem z submodułami; +* `git update` - aktualizuje submoduły; +* `git pr` - pobiera zmiany ze zdalnego repozytorium; +* `git pru` - pobiera zmiany ze zdalnego repozytorium i aktualizuje submoduły; +* `git master` - przełącza na gałąź master i aktualizuje submoduły; +* `git develop` - przełącza na gałąź develop i aktualizuje submoduły; +* `git tig` - uruchamia program `tig` (o ile mamy go zainstalowany). + +Polecenia te zapisałem w pliku [git_config.sh]() +i można je wykonać poleceniem: +```bash +curl -s https://raw.githubusercontent.com/writeonly/cli/master/git_config.sh | bash +``` + +[Java]: /langs/java +[JavaScript]: /langs/javascript + +[interpretera]: /tags/interpreter diff --git a/_collections/_posts/2018-09-26-jekyllcodex-org-czesc-druga.md b/_collections/_posts/2018-09-26-jekyllcodex-org-czesc-druga.md new file mode 100644 index 00000000..9dcb051d --- /dev/null +++ b/_collections/_posts/2018-09-26-jekyllcodex-org-czesc-druga.md @@ -0,0 +1,55 @@ +--- +title: "jekyllcodex.org - część druga" +author: TheKamilAdam +category: jekyll +tags: blog jekyllcodex seo +tools: jekyll +redirect_from: + - jekyllcodex-org-czesc-druga + - jekyll/jekyllcodex-org-czesc-druga + - writeonlydoc/jekyllcodex-org-czesc-druga + - writeonlydoc/2018/09/26/jekyllcodex-org-czesc-druga.html +--- + +Ponad miesiąc temu, w panice, szukałem prostego skryptu, który by wyświetlał ostrzeżenie o plikach cookies. +Skrypt odnalazłem na stronie [jekyllcodex.org](). +Strona ta zawiera repozytorium skryptów, które rozszerzają możliwości generatora statycznych stron Jekyll. + +Dziś już na spokojnie postanowiłem przejrzeć to repozytorium skryptów. +Przeszukiwanie prowadziłem pod kątem możliwości rozszerzenia funkcjonalności mojego bloga. + +## Skrypty, które użyłem +Skrypty wykorzystane przeze mnie podzieliłem na trzy kategorie ze względu na miejsce dodania skryptu: + +### Skrypty ogólne +Są to skrypty, które w większości trafiły do układu strony `default.html`: + +- [ostrzeżenie o plikach cookies]() +- [seo]() +- [kanał rss]() +- [przyciski linkujące do kont na portalach społecznościowych]() +- [czat na żywo]() - jeszcze nie wiem po co, ale dodałem. + +### Skrypty dla postów +Są to skrypty, które trafiły do układu strony `post.html`: + +- [wskaźnik czasu czytania]() +- [przyciski udostępniania dla mediów społecznościowych]() + +### Skrypty dla stron +Jest to w zasadzie jeden skrypt, który trafił do układu strony `page.html`: + +- [okruszki chleba]() - dla bloga nie za bardzo nie ma sens, +wydaje się przydatny tylko dla stron o zagnieżdżonej hierarchii + +## Skrypty, których jeszcze nie użyłem +Niestety nie zdążyłem wypróbować wszystkich skryptów, które mnie ciekawią i które mogłyby być użyteczne na blogu. +Te które pozostały do przetestowania dzielą się na dwie kategorie: + +### Skrypty robiące jakąś magię z menu +- [proste menu]() +- [mobilne menu]() + +### Skrypty dodające wyszukiwarki +- wyszukiwarka oparta o [google]() +- wyszukiwarka oparta o [skrypt lunar]() diff --git a/_collections/_posts/2018-10-03-przenosna-scala.md b/_collections/_posts/2018-10-03-przenosna-scala.md new file mode 100644 index 00000000..588602ad --- /dev/null +++ b/_collections/_posts/2018-10-03-przenosna-scala.md @@ -0,0 +1,149 @@ +--- +title: "Przenośna Scala" +author: TheKamilAdam +category: scala-native +tags: compiler native nojvm transpiler +langs: crystal erlang haskell go javascript pony rust scala +tools: clang jvm llvm node-js sbt scala-jvm scala-js scala-native +libs: akka +redirect_from: + - przenosna-scala + - scala-native/przenosna-scala + - resentiment/przenosna-scala + - resentiment/2018/10/03/przenosna-scala.html +--- + +Znajomy zajarał się językiem **[Rust]**. +Opowiada mi jaki to wspaniały język i pokazuje przykłady kodu. +**[Rust]** na pierwszy rzut oka wygląda jak skrzyżowanie **[C]** i języka **[Haskell]** plus kanały jak w języka **[Go]**. +Czyni go to pretendentem do bycia najbardziej skomplikowanym językiem programowania na świecie. +Pretendentem, bo istnieje wśród programistów JVM opinia, że najbardziej skomlikowanym językiem na świecie jest **[Scala]**. +**[Scala]** jest skrzyżowaniem Javy i Haskella plus aktory z języka **[Erlang]**. + +Przykłady kodu coś mi przypominają. +Wyglądają prawie jak w Scali tylko trochę mniej obiektowe, więc pytam: +- Dlaczego nie Scala? Jest to w tej chwili jedyny funkcyjny język programowania, który odniósł sukces komercyjny. +Nie licząc niszowego Erlanga - dodaję. +- Bo ja nie lubię **[JVM]** - odpowiada. - Za dużo musiałem robić w apletach. + +Po tej rozmowie zacząłem się zastanawiać czy istnieje możliwość używania Scali poza JVM. +Czy Scala jest językiem przenaszalnym na inne platformy? + +## Portable Scala - dwa razy tak i jeden raz nie + +### .Net Framework +Podobno największą konkurencją dla JVM jest .Net. + +Z kilka lat temu, jak o Scali zaczynano dopiero mówić, podobno ruszyły prace nad przeniesieniem Scali na .Net. +Podobno nawet M$ dofinansował ten projekt. + +Jednak do dziś nie ma języka Scala.NET ani S#. +Podobno różnice między JVM a .Net są zbyt duże, +żeby w sensowny sposób udało się przenieść statycznie typowany język programowania +z jednego środowiska uruchomieniowego na drugie. + +Podobno istniał jeden statycznie typowany język programowania, +który działał na obu platformach. +Jednak było to zalane taką ilością warstw abstrakcyjnych, +że czyniło go to bezużytecznym. + +### JavaScript +**[JavaScript]** jest platformą docelową dla każdego, +kto chce uruchamiać cokolwiek na stronie internetowej. +Dlatego jeśliby posiadać **[transpilator]** Scali do JavaScriptu +to w łatwy sposób można by z każdego programisty Scali stworzyć legendarnego Full-Stack Developera. +Szczęśliwie taki **[transpilator]** istnieje i nazywa się **[Scala.js]**. +Istnieje nawet [Akka.js], framework aktorów. + +Niestety **[Scala.js]** i Akka.js posiadają wszystkie wady **[Node.js]** to znaczy jednowątkowość, +ale do front-endu wydają się idealne. + +### Native +Największą zaletą języków +**[Rust]**, **[Pony]**, **[Crystal]** czy **[Go]**, +jest to, +że są to języki kompilowane do postaci natywnej, +a programy w nich pisane mogą być dostarczane do klienta pod postacią jednego pliku. + +To samo ze Scalą robi **[kompilator]** **[Scala Native]** oparty na **[LLVM]**. +Niestety dalej jest w wersji eksperymentalnej i nie posiada np. wielowątkowości. +Co czyni go na razie tylko zabawką dla nerdów i np. ... DevOpsów oraz QA. +Bo o ile jednowątkowy program na produkcji zwykle nie ma sensu, +to ScalaNative może zastąpić inne języki używane do wdrażania aplikacji i testowania. +Zwłaszcza, że zwykle i tak są to języki jednowątkowe. + +## Portable Scala - wszystkie części mocy + +W łatwy sposób można stworzyć projekt, który będzie kompilowany na wszystkie trzy platformy, +to znaczy JS, JVM i Native. + +Na początek instalujemy [zależności] dla **[Scala Native]**: + +```bash +sudo apt install clang libunwind-dev +sudo apt install libgc-dev libre2-dev +``` + +Następnie pobieramy przykładową [aplikację skośną] z portable-scala: +```bash +sbt new portable-scala/sbt-crossproject.g8 +``` + +Kompilujemy: +```bash +sbt clean compile test +``` + +I uruchamiamy: +```bash +sbt barJS/run barJVM/run barNative/run +``` + +## Mój Resentiment + +Stworzyłem projekt na portalu [GitHub], +gdzie będę starał się rozwijać aplikację kompilowaną na wszystkie trzy platformy. +Na razie pod tagiem [portable-scala] +znajduje się wersja z podniesionymi zależnościami i ze zmienioną konfiguracją projektu. + +Pobranie projektu: +``` +git clone https://github.com/writeonly/resentiment.git +``` + +Kompilacja: +```bash +sbt clean compile test +``` + +I uruchomienie: +```bash +sbt re/run reJS/run reJVM/run +``` + +[Crystal]: /langs/crystal +[Erlang]: /langs/erlang +[Go]: /langs/go +[Haskell]: /langs/haskell +[JavaScript]: /langs/javascript +[Pony]: /langs/pony +[Rust]: /langs/rust +[Scala]: /langs/scala + +[C]: /tools/clang +[JVM]: /tools/jvm +[LLVM]: /tools/llvm +[Node.js]: /tools/node-js +[Scala Native]: /tools/scala-native +[Scala.js]: /tools/scala-js + +[Akka.js]: /libs/akka-js + +[Transpilator]: /tags/transpiler +[Kompilator]: /tags/compiler + +[zależności]: http://www.scala-native.org/en/v0.3.8/user/setup.html#installing-clang-and-runtime-dependencies +[aplikację skośną]: https://github.com/portable-scala/sbt-crossproject.g8 + +[GitHub]: https://github.com/writeonly/resentiment +[portable-scala]: https://github.com/writeonly/resentiment/tree/portable-scala diff --git a/_collections/_posts/2018-10-10-git-flow-i-aliasy.md b/_collections/_posts/2018-10-10-git-flow-i-aliasy.md new file mode 100644 index 00000000..0c818f5a --- /dev/null +++ b/_collections/_posts/2018-10-10-git-flow-i-aliasy.md @@ -0,0 +1,79 @@ +--- +title: "Git-flow i aliasy" +author: TheKamilAdam +category: cli +tags: alias cli +labels: flow +tools: git +redirect_from: + - git-flow-i-aliasy + - cli/git-flow-i-aliasy + - cli/2018/10/10/git-flow-i-aliasy.html +--- + +Gitflow jest wspaniałą koncepcją pracy z gałęziami w repozytorium Git. +Strategia ta jest świetnie opisana na +[A successful Git branching model](). + +Jednak początkowo może wydawać się zbyt skomplikowana. +Straszy zwłaszcza ilością poleceń, które trzeba wykonać, żeby scalić gałąź z nową funkcjonalnością: +```bash +$ git checkout develop +$ git merge --no-ff myfeature +$ git branch -d myfeature +$ git push origin develop +``` + +Ilość poleceń, parametrów i przełączników może powodować niekończące się problemy i dyskusje + "czy na pewno potrzebujemy czegoś tak skomplikowanego?". + +Na szczęście istnieje 'git-flow'. + +## Narzędzie git-flow + +> git-flow jest zbiorem rozszerzeń gita dostarczającym wysokopoziomowe operacje na repozytorium, + wspierającym strategię rozgałęziania opracowaną przez Vincenta Driessena. +Za [ściągawka do git-flow]() + +Tutaj już nie mogą toczyć się dyskusje czy powinniśmy scalać z przełącznikiem `--no-ff` czy bez niego. +Możemy wziąć całe narzędzie ustandaryzowane i przetestowane przez społeczność i nie kroimy niczego własnego. + +Zalety tego są oczywiste: +* programista, tester i/lub wdrożeniowiec, +który raz nauczył się pracować z Gitflow ma jedną rzecz mniej do nauki przy przenoszeniu się do innego zespołu, +gdzie będą stosować dokładnie ten sam Gitflow bez żadnych lokalnych modyfikacji +* zamiast wpisywania długich wielolinijkowców w konsoli możemy używać pojedynczych poleceń. + +## Moje aliasy + +Jednak nawet te polecenia są dla mnie za długie. +Dlatego przygotowałem [plik]() z [aliasami Basha](/git-submoduly-i-aliasy) + +```bash +# git flow +git config --global alias.fi 'flow init' +# git flow feature +git config --global alias.ffstart 'flow feature start' +git config --global alias.fffinish 'flow feature finish ' +git config --global alias.ffpublish 'flow feature publish' +git config --global alias.ffpull 'flow feature pull origin' +git config --global alias.fftrack 'git flow feature track' +# git flow release +git config --global alias.frstart 'flow release start' +git config --global alias.frpusblish 'flow release publish' +git config --global alias.frfinish 'flow release finish' +# git flow hotfix +git config --global alias.fhstart 'flow hotfix start' +git config --global alias.fhfinish 'flow hotfix finish' +``` + +Można go zastosować poleceniem +```bash +curl -s https://raw.githubusercontent.com/writeonly/cli/master/git_config.sh | bash +``` + +i cieszyć się krótkimi poleceniami jak: +```bash +git ffstart myfeature +git fffinish myfeature +``` diff --git a/_collections/_posts/2018-10-17-dynamiczna-analiza-kodu.md b/_collections/_posts/2018-10-17-dynamiczna-analiza-kodu.md new file mode 100644 index 00000000..ea45d0ba --- /dev/null +++ b/_collections/_posts/2018-10-17-dynamiczna-analiza-kodu.md @@ -0,0 +1,183 @@ +--- +title: "Dynamiczna analiza kodu dla SBT - testy jednostkowe" +author: TheKamilAdam +category: scala-native +tags: code-analysis dynamic-code-analysis +labels: minitest greenlight +langs: scala +tools: scala-jvm scala-js scala-native +libs: specs2 scalatest utest +redirect_from: + - dynamiczna-analiza-kodu + - scala-native/dynamiczna-analiza-kodu + - resentiment/dynamiczna-analiza-kodu + - resentiment/2018/10/17/dynamiczna-analiza-kodu.html +--- + +> Dynamiczna analiza programu to analiza oprogramowania komputerowego wykonywanego przez wykonywanie programów na rzeczywistym lub wirtualnym procesorze. +> Korzystanie z metryk testów, takich jak pokrycie kodu, zapewnia, że przetestowano odpowiednią ilość możliwych zachować programu. +> Aby analiza dynamiczna programu była skuteczna, program docelowy musi być wykonany z wystarczającą ilością danych wejściowych do testów, aby uzyskać interesujące zachowanie. +> Testy jednostkowe, testy integracyjne, testy systemowe i testy akceptacyjne wykorzystują dynamiczną analizę programu. + +Za [wikipedią](). + +W tym poście skupię się tylko na frameworkach do testów, testach jednostkowych i mierzeniu pokrycia kodu testami. + +## Frameworki dla testów + +* [ScalaTest]() - +jest to chyba najbardziej znany i rozbudowany framework dla testów do języka Scala. +Wspiera Scala.js w wersji 0.6.x. Ostatnia wersja snapshot wspiera Scala Native. +Posiada osiem różnych [stylów pisania testów](), +jednak autorzy zachęcają do wybrania dwóch dla projektu. +Jeden styl dla testów jednostkowo-integracyjnych, drugi - dla akceptacyjnych. +Najciekawsze style to: + * `FunSuite` i `FlatSpec` - są to proste, płaskie style pisania testów podobne do JUnit. + Jeśli jednak są dla kogoś zbyt awangardowe jest możliwość pisania testów w Scali z użyciem [JUnit i assercji z ScalaTest]() + * `FunSpec`, `FreeSpec` i `WordSpec` - są to style testów umożliwiające pisanie zagnieżdżonych testów, + jednak zagnieżdżenia nie są wymagane (za wyjątkiem stylu `FunSpec`, gdzie trzeba użyć przynajmniej jeden poziom `Describe`) + * `FeatureSpec` - jest to zaawansowany styl dla pisania testów akceptacyjnych. +* [specs2]() - +jest to drugi najbardziej znany framework do pisania testów dla języka Scala. +Od wersji czwartej wspiera Scala.js w wersji 0.6.x. +Ma dwa style pisania testów: + * `org.specs2.mutable.Specificatio` - najbardziej przypomina `FreeSpec` i jest przewidziany do pisania testów jednostkowych i integracyjnych. + * `org.specs2.Specification` - styl testów przewidziany do pisania testów akceptacyjnych, +* [uTest]() - +czarny koń tego zestawienia. +Wspiera Scala.js w wersji 0.6.x i 1.0.x oraz Scala Native. +Autor frameworka skromnie chwali się, że wspiera wszystkie wersje języka Scala. +Ma jeden styl pisania testów który wygląda jak `FreeTest`. +* [MiniTest]() - +stworzony przez [Monix]() do testowania swojej biblioteki. +Wspiera Scala.js w wersji 0.6.x. +Ma jeden styl pisania testów, który wygląda jak `FunSuite`. +* [Greenlight]() - +projekt niestety umarł. +Wspiera Scala.js w wersji 0.6.x. +Ma jeden styl pisania testów, który wygląda jak `FlatSpec` + +## Test jednostkowy w uTest + +W `build.sbt` dodajemy `uTest` do zależności projektu: +```scala + libraryDependencies += "com.lihaoyi" %%% "utest" % "0.6.5" % "test" +``` + +i ustawiamy jako framework testowy: +```scala + testFrameworks += new TestFramework("utest.runner.Framework"), +``` + +Tworzymy klasę `Calculator`, którą będziemy testować : +```scala +package pl.writeonly.re.shared + +class Calculator { + type T = Int + + def add(a: T, b: T): T = a * b + + def mul(a: T, b: T): T = a + b + + def leq(a: T, b: T): Boolean = a < b + +} +``` + +Oraz testy dla niej: + +```scala +package pl.writeonly.re.shared + +import utest._ + +object CalculatorTest extends TestSuite { + override val tests: Tests = Tests { + val calculator = new Calculator() + 'addition - { + val addition: (Int, Int) => Int = (x, y) => calculator.add(x, y) + "0 + 0 == 0" - { + assert(addition(0, 0) == 0) + } + "2 + 2 == 4" - { + assert(addition(2, 2) == 4) + } + } + 'multiplication - { + val multiplication: (Int, Int) => Int = (x, y) => calculator.mul(x, y) + "0 + 0 == 0" - { + assert(multiplication(0, 0) == 0) + } + "2 + 2 == 4" - { + assert(multiplication(2, 2) == 4) + } + } + 'less_or_equal - { + val less_or_equal: (Int, Int) => Boolean = (x, y) => calculator.leq(x, y) + "0 <= 2 == true" - { + assert(less_or_equal(0, 2)) + } + "2 <= 0 == false" - { + assert(!less_or_equal(2, 0)) + } + } + } +} +``` + +Wszystko kompilujemy i uruchamiamy testy za pomocą polecenia: +```bash +sbt clean compile test +``` + +Testy przeszły, jesteśmy szczęśliwi: +```log +[info] -------------------------------- Running Tests -------------------------------- +[info] + pl.writeonly.re.shared.CalculatorTest.addition.0 + 0 == 0 0ms +[info] + pl.writeonly.re.shared.CalculatorTest.addition.2 + 2 == 4 0ms +[info] + pl.writeonly.re.shared.CalculatorTest.multiplication.0 + 0 == 0 0ms +[info] + pl.writeonly.re.shared.CalculatorTest.multiplication.2 + 2 == 4 0ms +[info] + pl.writeonly.re.shared.CalculatorTest.less_or_equal.0 <= 2 == true 0ms +[info] + pl.writeonly.re.shared.CalculatorTest.less_or_equal.2 <= 0 == false 0ms +[info] Tests: 6, Passed: 6, Failed: 0 +``` + +## Testowanie testów - mierzenie pokrycia kodu testami +Skąd mamy mieć pewność, że przetestowaliśmy klasę `Calculator` w wystarczający sposób? +Możemy to częściowo sprawdzić mierząc pokrycie kodu produkcyjnego (tj. klasy `Calculator`) testami. + +Jeśli chodzi o narzędzia do mierzenia pokrycia kodu testami to tutaj król jest jeden +i jest nim [scoverage](). +Posiada on wtyczki do: +* [sbt]() +* [maven]() +* [gradle]() +* [scalac]() +* [sonarqube]() + +Przy czym użyjemy tutaj tylko pierwszej z nich. + +Dodajemy `sbt-scoverage` do `build.sbt`: +```scala +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1") +``` +I wykonujemy: +```bash +sbt clean coverage test && sbt coverageReport +``` +gdzie: +* `coverage` - wykonuje kompilacje z instrumentacją kodu +* `coverageReport` - generuje raport + +Niestety powyższe polecenie działa tylko dla implementacji Scala/JVM i Scala.js. +Scala Native nie wspiera instrumentacji kodu. +Dlatego projekt [resentiment](https://github.com/writeonly/resentiment) trzeba kompilować za pomocą polecenia: +```bash +sbt clean compile re/test coverage reJVM/test reJS/test && sbt coverageReport +``` + +Teraz możemy otworzyć pliki `/re/js/target/scala-2.11/scoverage-report/index.html` +oraz `/re/jvm/target/scala-2.11/scoverage-report/index.html` +i zobaczyć, że klasa `Calculator` ma 100% pokrycia kodu testami. +Lider nietechniczny i Product Owner powinni być z nas zadowoleni. diff --git a/_collections/_posts/2018-10-24-statyczna-analiza-kodu-1.md b/_collections/_posts/2018-10-24-statyczna-analiza-kodu-1.md new file mode 100644 index 00000000..bf1a928f --- /dev/null +++ b/_collections/_posts/2018-10-24-statyczna-analiza-kodu-1.md @@ -0,0 +1,167 @@ +--- +title: "Statyczna analiza kodu dla języka Scala w SBT - część 1." +author: TheKamilAdam +category: scala-native +tags: code-analysis static-code-analysis +labels: scalafmt scalariform +langs: scala +tools: sbt scalafix +redirect_from: + - statyczna-analiza-kodu-1 + - scala-native/statyczna-analiza-kodu-1 + - resentiment/statyczna-analiza-kodu-1 + - resentiment/2018/10/24/statyczna-analiza-kodu-1.html +--- + +> Statyczna analiza programu to analiza oprogramowania komputerowego wykonywanego bez faktycznego uruchamiania programów, +w przeciwieństwie do analizy dynamicznej, +która jest analizą wykonywaną na programach podczas ich wykonywania. + +> Termin ten jest zwykle stosowany do analizy wykonywanej przez zautomatyzowane narzędzie, +analiza wykonywana przez człowieka jest nazywana przeglądem kodu. + +Za [wikipedią](). + +Jest to moja ulubiona część konfigurowania projektu, +ponieważ odpowiednio dobrany zestaw wtyczek do statycznej analizy kodu +potrafi znacząco skrócić czas potrzebny do zrobienia przeglądu kodu. + +## Wtyczki modyfikujące kod źródłowy + +Z powodu ogromu różnego rodzaju wtyczek do statycznej analizy kodu dla języka Scala +w tym poście skupię się tylko na wtyczkach modyfikujących kod źródłowy. + +### sbt-scalariform - sbt plugin adding support for source code formatting using Scalariform +[sbt-scalariform]() +to wtyczka sbt dodająca obsługę formatowania kodu źródłowego przy użyciu formatera kodu Scalariform + +Dodajemy `scalariform` do pliku `projektu/plugins.sbt`: +```scala +addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.2") +``` + +Konfiguracja jest możliwa za pomocą: +* zmiennej `scalariformPreferences` w pliku `build.sbt` +* pliku `.scalariform.conf` + +Formatowanie kodu jest wykonywane automatycznie podczas kompilacji: + +```bash +sbt compile test:compile it:compile +``` + +### Scalafmt - Code formatter for Scala +[Scalafmt]() to formater kodu dla języka Scala. + +Dodajemy `scalafmt` do pliku `projektu/plugins.sbt`: +```scala +addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1") +``` + +Konfiguracja jest możliwa za pomocą pliku `.scalafmt.conf`, np.: +```hocon +style=IntelliJ +maxColumn=80 +``` + +Formatowanie domyślnie nie wykonuje się podczas kompilacji. +Aby sformatować kod należy wykonać: +```bash +sbt scalafmtSbt scalafmt test:scalafmt it:scalafmt +``` +Kolejno formatowany jest plik `build.sbt`, kod produkcyjny, testy jednostkowe i testy integracyjne. + +Możliwe jest także tylko sprawdzenie czy kod jest sformatowany poprawnie za pomocą polecenia: +```bash +sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck it:scalafmtCheck +``` + +### Scalafix - Refactoring and linting tool for Scala +[Scalafix]() to narzędzie do analizy statycznej kodu w języku Scala. +Jest to jedyne narzędzie, które potrafi znalezione błędy poprawić. + +Dodajemy `scalafix` do pliku `projektu/plugins.sbt`: +```scala +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.0") +``` + +W pliku `build.sbt` dodajemy następujące linie: +```scala + addCompilerPlugin(scalafixSemanticdb) + scalacOptions ++= Seq( + //"-Xfatal-warnings", // it should be disabled for scalafix + "-Ywarn-adapted-args", // for NoAutoTupling + "-Ywarn-unused", // for RemoveUnused + ) +``` + +Konfiguracja jest możliwa za pomocą pliku `.scalafix.conf`. +Poniższa konfiguracja włącza wszystkie istniejące obecnie reguły: +```hocon +rules = [ + +// Semantic Rules - Reguły semantyczne + NoAutoTupling + RemoveUnused + +// Syntactic Rules - Reguły składniowe + DisableSyntax + LeakingImplicitClassVal + NoValInForComprehension + ProcedureSyntax +] + +DisableSyntax.noVars = true +DisableSyntax.noThrows = true +DisableSyntax.noNulls = true +DisableSyntax.noReturns = true +DisableSyntax.noAsInstanceOf = true +DisableSyntax.noIsInstanceOf = true +DisableSyntax.noXml = true +DisableSyntax.noDefaultArgs = true +DisableSyntax.noFinalVal = true +DisableSyntax.noFinalize = true +DisableSyntax.noValPatterns = true +DisableSyntax.noUniversalEquality = true +DisableSyntax.noUniversalEqualityMessage = "== is unsafe since it allows comparing two unrelated types" +DisableSyntax.regex = [] +``` + +Aby naprawić kod należy wykonać: +```bash +sbt scalafix test:scalafix it:scalafix +``` +Kolejno naprawiany jest kod produkcyjny, testy jednostkowe i testy integracyjne. + +Możliwe jest także tylko sprawdzenie czy kod nie zawiera błędów za pomocą polecenia: +```bash +sbt 'scalafix --check' 'test:scalafix --check' 'it:scalafix --check' +``` + +## Podsumowanie + +Wszystkie wymienione wyżej wtyczki dodałem do projektu +[resentiment](). + +Na chwilę obecna moje polecenie do zbudowania tego projektu to: +```bash +sbt clean compile test:compile it:compile re/test && \ +sbt coverage reJVM/test reJS/test && \ +sbt coverageReport +``` + +Wcześniej jednak powinienem wywołać polecenie refaktoryzującą i formatującą kod: +```bash +sbt scalafix test:scalafix it:scalafix && \ +sbt scalafmtSbt scalafmt test:scalafmt it:scalafmt +``` + +Ewentualnie, gdy robię przegląd kodu mogę sprawdzić czy kod jest poprawny za pomocą polecenia: +```bash +sbt 'scalafix --check' 'test:scalafix --check' 'it:scalafix --check' && \ +sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck it:scalafmtCheck +``` + +Niestety ponieważ w testach używam porównania `==` +musiałem zakomentować linię `DisableSyntax.noUniversalEquality` w pliku `.scalafix.conf`. +Problem ten rozwiąże w następnym poście. diff --git a/_collections/_posts/2018-10-31-statyczna-analiza-kodu-2.md b/_collections/_posts/2018-10-31-statyczna-analiza-kodu-2.md new file mode 100644 index 00000000..c0a521c6 --- /dev/null +++ b/_collections/_posts/2018-10-31-statyczna-analiza-kodu-2.md @@ -0,0 +1,176 @@ +--- +title: "Statyczna analiza kodu dla języka Scala w SBT - część 2." +author: TheKamilAdam +category: scala-native +tags: code-analysis static-code-analysis +labels: scalastyle wartremover scapegoat linter scala-clippy sbt-cpd sbt-stats +langs: scala +tools: sbt scala-jvm scala-js scala-native +redirect_from: + - statyczna-analiza-kodu-2 + - scala-native/statyczna-analiza-kodu-2.html + - resentiment/statyczna-analiza-kodu-2.html + - resentiment/2018/10/31/statyczna-analiza-kodu-2.html +--- + +Jest to kontynuacja posta +[Statyczna analiza kodu dla języka Scala w SBT - część 1](/statyczna-analiza-kodu-1.html). + +## Wtyczki nie modyfikujące kodu źródłowego. + +Jest to o wiele większy zestaw wtyczek niż poprzednio. + +### Scalastyle - Scala style checker + +[Scalastyle]() + +Dodajemy `Scalastyle` do pliku `project/plugins.sbt`: +```scala +addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0") +``` + +Generujemy domyślny plik konfiguracyjny `scalastyle-config.xml` zawierający reguły: +```bash +sbt scalastyleGenerateConfig +``` + +Wchodzimy do pliku `scalastyle-config.xml` i zmieniamy poziomy z `warning` na `error`. + +Następnie wyłączamy sprawdzanie nagłówka: +```xml + +``` +A na końcu poprawiamy formatowanie dwuliniowej instrukcji `if`: +```xml + + + + + + +``` + +Teraz analizujemy kod i testy za pomocą polecenia: +```bash +sbt scalastyle test:scalastyle it:scalastyle +``` + +Pełna lista reguł jest dostępna pod linkiem +[rules-1.0.0](). + +Dla mnie najważniejszą funkcjonalnością jest liczenie [złożoności cyklomatycznej](): +```xml + + + + + +``` + +### WartRemover: a flexible scala linter +[WartRemover]() +jest to wtyczka do usuwania brodawek z kodu. +Przez brodawkę autor rozumie brzydkie rzeczy, które można napisać w języku Scala, +bo składnia języka jest zbyt liberalna. +Nazwa wtyczki jest trochę na wyrost, +ponieważ w tej chwili `WartRemover` pozwala tylko odnaleźć brodawki, +a usunąć musimy je sami. + +Dodajemy `wartremover` do pliku `project/plugins.sbt`: +```scala +addSbtPlugin("org.wartremover" % "sbt-wartremover" % "2.3.7") +``` + +W pliku `build.sbt` włączamy `wartremover`: +```scala +wartremoverErrors ++= Warts.unsafe +``` + +Od teraz przy każdej kompilacji będą poszukiwane brodawki. +Pełna lista [brodawkek]() + +### Scapegoat - Scala compiler plugin for static code analysis +[Scapegoat]() to linter dla języka Scala. + +Dodajemy `scapegoat` do pliku `project/plugins.sbt`: +```scala +addSbtPlugin("com.sksamuel.scapegoat" %% "sbt-scapegoat" % "1.3.8") +``` + +Ustawiamy wersję `scapegoat` w `build.sbt`: +```scala +scapegoatVersion := "1.1.0" +``` + +Analizujemy kod za pomocą polecenia: +```bash +sbt scapegoat +``` + +### Linter - Static Analysis Compiler Plugin for Scala +[Linter]() +to niestety nie rozwijany już linter. + +Dodajemy `Linter` do pliku `project/plugins.sbt`: +```scala +addCompilerPlugin("org.psywerx.hairyfotr" %% "linter" % "0.1.17") +``` + +Linter uruchamia się automatycznie podczas kompilacji. + +### Scala clippy - Good advice for Scala compiler errors +[Scala clippy]() +to wtyczka dodająca porady jak poprawić błędy kompilacji. + +Dodajemy `clippy` do pliku `project/plugins.sbt`: +```scala +addSbtPlugin("com.softwaremill.clippy" % "plugin-sbt" % "0.5.3") +``` + +### sbt-cpd - Copy & Paste Detector plugin using PMD for sbt. +[sbt-cpd]() +to wtyczka do wykrywania duplikatów kodu za pomocą Copy/Paste Detector (CPD) z projektu PMD. + +Dodajemy `sbt-cpd` do pliku `project/plugins.sbt`: +```scala +addSbtPlugin("com.github.sbt" % "sbt-cpd" % "2.0.0") +``` + +I analizujemy kod za pomocą polecenia: +```bash +sbt cpd +``` + +### stats - An sbt plugin for source code statistics + +[sbt-stats]() +to wtyczka licząca ilość plików, linii i znaków w projekcie. + +Dodajemy `stats` do pliku `project/plugins.sbt`: +```scala +addSbtPlugin("com.orrsella" % "sbt-stats" % "1.0.7") +``` + +Analizujemy kod za pomocą polecenia: +```bash +sbt stats +``` + +## Podsumowanie + +Wszystkie wymienione wyżej wtyczki dodałem do projektu +[resentiment]() + +Moje polecenie generowania raportów wygląda następująco: +```bash +sbt scalastyle test:scalastyle it:scalastyle && \ +sbt scapegoat && \ +sbt cpd && \ +sbt stats +``` + +Niestety, +ponieważ używam metody `println` w klasie `Core`, +muszę obniżyć poziom raportowania błędów +dla reguły `org.scalastyle.file.RegexChecker` w `ScalaStyle` +z `error` na `warning`. \ No newline at end of file diff --git a/_collections/_posts/2018-11-07-flagi-kompilatora-scalac.md b/_collections/_posts/2018-11-07-flagi-kompilatora-scalac.md new file mode 100644 index 00000000..2f7aa2e0 --- /dev/null +++ b/_collections/_posts/2018-11-07-flagi-kompilatora-scalac.md @@ -0,0 +1,156 @@ +--- +title: "Flagi kompilatora Scalac" +author: TheKamilAdam +category: scala-native +tags: compiler +labels: scalafix scalac +langs: perl scala +tools: sbt scalafix +redirect_from: + - flagi-kompilatora-scalac + - scala-native/flagi-kompilatora-scalac + - resentiment/flagi-kompilatora-scalac + - resentiment/2018/11/07/flagi-kompilatora-scalac.html +--- + +Nie bójmy się tego powiedzieć, Scala to nowy **[Perl](/langs/perl)**. +I tak jak w Perlu, w Scali obowiązuje zasada TIMTOWTDI (ang. There is more than one way to do it), +czyli "Można to zrobić na różne sposoby". + +Jednak z biegiem czasu twórcy języka Scala uznali, +że niektóre sposoby są lepsze od innych i powinna istnieć możliwość wyłączenia gorszych sposobów. +Dodatkowo niektóre funkcjonalności języka są tak inne od tego co do tej pory widzieli programiści języków obiektowych, +że nie powinny być domyślnie włączone. +Oba te warunki, i pewnie jeszcze kilka innych, powodują, że Scalac, **[kompilator](/tags/compiler)** języka Scala, +posiada [flagi kompilacji](). +Dokładniej Scalac posiada ogromną ilość flag kompilacji. + +## Rekomendowana lista flag kompilatora + +Na szczęście istnieją tacy ludzie jak [tpolecat](), +który na swoim blogu zebrał listę [rekomendowanych flag kompilatora](https://tpolecat.github.io/2017/04/25/scalac-flags.html) +Są to: +```scala +scalacOptions ++= Seq( + "-deprecation", // Emit warning and location for usages of deprecated APIs. + "-encoding", "utf-8", // Specify character encoding used by source files. + "-explaintypes", // Explain type errors in more detail. + "-feature", // Emit warning and location for usages of features that should be imported explicitly. + "-language:existentials", // Existential types (besides wildcard types) can be written and inferred + "-language:experimental.macros", // Allow macro definition (besides implementation and application) + "-language:higherKinds", // Allow higher-kinded types + "-language:implicitConversions", // Allow definition of implicit functions called views + "-unchecked", // Enable additional warnings where generated code depends on assumptions. + "-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access. + "-Xfatal-warnings", // Fail the compilation if there are any warnings. + "-Xfuture", // Turn on future language features. + "-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver. + "-Xlint:by-name-right-associative", // By-name parameter of right associative operator. + "-Xlint:constant", // Evaluation of a constant arithmetic expression results in an error. + "-Xlint:delayedinit-select", // Selecting member of DelayedInit. + "-Xlint:doc-detached", // A Scaladoc comment appears to be detached from its element. + "-Xlint:inaccessible", // Warn about inaccessible types in method signatures. + "-Xlint:infer-any", // Warn when a type argument is inferred to be `Any`. + "-Xlint:missing-interpolator", // A string literal appears to be missing an interpolator id. + "-Xlint:nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'. + "-Xlint:nullary-unit", // Warn when nullary methods return Unit. + "-Xlint:option-implicit", // Option.apply used implicit view. + "-Xlint:package-object-classes", // Class or object defined in package object. + "-Xlint:poly-implicit-overload", // Parameterized overloaded implicit methods are not visible as view bounds. + "-Xlint:private-shadow", // A private field (or class parameter) shadows a superclass field. + "-Xlint:stars-align", // Pattern sequence wildcard must align with sequence component. + "-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope. + "-Xlint:unsound-match", // Pattern match may not be typesafe. + "-Yno-adapted-args", // Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver. + "-Ypartial-unification", // Enable partial unification in type constructor inference + "-Ywarn-dead-code", // Warn when dead code is identified. + "-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined. + "-Ywarn-inaccessible", // Warn about inaccessible types in method signatures. + "-Ywarn-infer-any", // Warn when a type argument is inferred to be `Any`. + "-Ywarn-nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'. + "-Ywarn-nullary-unit", // Warn when nullary methods return Unit. + "-Ywarn-numeric-widen", // Warn when numerics are widened. + "-Ywarn-unused:implicits", // Warn if an implicit parameter is unused. + "-Ywarn-unused:imports", // Warn if an import selector is not referenced. + "-Ywarn-unused:locals", // Warn if a local definition is unused. + "-Ywarn-unused:params", // Warn if a value parameter is unused. + "-Ywarn-unused:patvars", // Warn if a variable bound in a pattern is unused. + "-Ywarn-unused:privates", // Warn if a private member is unused. + "-Ywarn-value-discard" // Warn when non-Unit expression results are unused. +) +``` + +Lista jest długa i działa dla języka Scala w wersji 2.12. +Jeśli używasz języka Scala w wersji wcześniejszej to część flag będziesz musiał wyłączyć. +Dla wersji 2.11 jest to: +```scala +scalacOptions --= Seq( + "-Xlint:constant", + "-Ywarn-extra-implicit", + "-Ywarn-unused:implicits", + "-Ywarn-unused:imports", + "-Ywarn-unused:locals", + "-Ywarn-unused:params", + "-Ywarn-unused:patvars", + "-Ywarn-unused:privates", +) +``` + +## .. i wtyczka do nich + +Lista opcji jest długa i może zaciemniać plik `build.sbt`. +Na szczęście [DavidGregory084](https://github.com/DavidGregory084) +stworzył wtyczkę [sbt-tpolecat](https://github.com/DavidGregory084/sbt-tpolecat) +dodającą flagi kompilatora do projektu. + +W pliku ` project/plugins.sbt` dodajemy: +```scala +addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.4") +``` +i wtyczka automatycznie ustawia odpowiednie flagi dla wersji 2.10/2.11/2.12/2.13 + +## Portable Scala & Multi-project + +Jeśli kompilujesz projekt w Scali na różne platformy (JVM/JS/Native) +lub posiadasz [multi-project](), +tak jak ja w projekcie [resentiment](https://github.com/writeonly/resentiment), +to musisz ręcznie dodać flagi dla kompilatora. +Flagi generuje metoda `scalacOptionsFor`, która jako parametr pobiera wersję języka Scala. +Flagi dodajemy do każdego projektu z osobna lub do wydzielonych ustawień jak w moim przypadku: + +```scala +val SharedSettings = Seq( + scalaVersion := "2.11.12", + scalacOptions ++= scalacOptionsFor(scalaVersion.value), + // ... +) +``` + +### ScalaFix i modyfikacja domyślnych flag + +Właśnie ustawiliśmy ponad 30 różnych flag, +ale pewnie w niektórych wypadkach chciałbyś zmodyfikować tę listę. +Np. żeby móc używać wtyczki [ScalaFix](/statyczna-analiza-kodu-1.html). + +Wtyczka ta wymaga dodania dwóch flag (`-Ywarn-adapted-args`, `-Ywarn-unused"`) oraz usunięcia jednej (`-Xfatal-warnings`). +Dla wygody, flagi przypisuję do zmiennych, a następnie dodaje do `scalacOptions`. +```scala +val ScalaFixScalacOptions = Seq( + "-Ywarn-adapted-args", // for NoAutoTupling + "-Ywarn-unused", // for RemoveUnused +) + +val ScalaFixScalacOptionsOff = Seq( + "-Xfatal-warnings", // it should be disabled for scalafix +) + +val SharedSettings = Seq( + scalaVersion := "2.11.12", + scalacOptions ++= scalacOptionsFor(scalaVersion.value), + scalacOptions ++= ScalaFixScalacOptions, + scalacOptions --= ScalaFixScalacOptionsOff, + // ... +) +``` + +W identyczny sposób można włączać i wyłączać każdą inną flagę kompilatora Scalac. diff --git a/_collections/_posts/2018-11-14-bardziej-dynamiczna-analiza-kodu.md b/_collections/_posts/2018-11-14-bardziej-dynamiczna-analiza-kodu.md new file mode 100644 index 00000000..31e0ae64 --- /dev/null +++ b/_collections/_posts/2018-11-14-bardziej-dynamiczna-analiza-kodu.md @@ -0,0 +1,242 @@ +--- +title: "Bardziej dynamiczna analiza kodu dla języka Scala - Property-based testing" +author: TheKamilAdam +category: scala-native +tags: code-analysis dynamic-code-analysis +labels: property-based scalacheck scalaprops nyaya +langs: scala haskell +tools: scala-js scala-native +libs: scalatest scalaz specs2 +redirect_from: + - bardziej-dynamiczna-analiza-kodu + - scala-native/bardziej-dynamiczna-analiza-kodu + - resentiment/bardziej-dynamiczna-analiza-kodu + - resentiment/2018/11/14/bardziej-dynamiczna-analiza-kodu.html +--- + +Testy modułowe (jednostkowe) napisane w poście +[Dynamiczna analiza kodu](/dynamiczna-analiza-kodu.html) +dla projektu [resentiment]() +zawiodły. +Mimo posiadania 100% pokrycia kodu dla klasy Calculator klasa ta nie działała w sposób poprawy. + +To dlatego, że skupiłem się na drugiej linii definicji +> Korzystanie z metryk testów, takich jak pokrycie kodu, zapewnia, +że przetestowano odpowiednią ilość możliwych zachować programu + +Zapominając jednocześnie o trzeciej: +> Aby analiza dynamiczna programu była skuteczna, +> program docelowy musi być wykonany z wystarczającą ilością danych wejściowych do testów, +> aby uzyskać interesujące zachowanie. + +Problem można rozwiązać za pomocą *property-based testing* + +## Property-based testing (Testowanie oparte na właściwościach) + +Przy pisaniu normalnych testów, czyli modułowych (jednostkowych), integracyjnych i systemowych, +wyznaczamy przypadki brzegowe i klasy równoważności. +Chcemy by danych testowych było jak najmniej, tak by testy wykonywały się jak najszybciej. + +W przypadku *property-based testing* jest inaczej. +Tutaj zamiast wyznaczać konkretne dane wejściowe definiujemy tylko ogólne ograniczenia jakie mają spełniać dane. +Na podstawie ograniczeń generowane są dane wejściowe dla testów. +Dużo danych wejściowych. +Dlatego testy te są wolne, chociaż testują pojedyncze moduły i jednostki. + +## Biblioteki dla property-based testing w języku Scala + +* [ScalaCheck]() - +pierwsza i najbardziej popularna biblioteka *property-based testing*. +Wspiera Scala.js w wersji 0.6 i 1.0.0. +Inspirowana biblioteką [QuickCheck]() dla języka **[Haskell]**. +Jeden z projektów [typelevel](). +Posiada integracje z [ScalaTest]() +i [Specs2](). +* [scalaprops]() - +druga najbardziej popularna biblioteka *property-based testing*. +Wspiera **[Scala.js]** w wersji 0.6 i **[Scala Native]** w wersji 0.3. +Posiada integrację z biblioteką **[Scalaz]**. +* [Nyaya]() - +projekt niestety umarł. +Wspierał Scala.js w wersji 0.6. + +## Testowanie oparte na właściwościach za pomocą ScalaProps +Ponieważ chcę utrzymać możliwość kompilacji krzyżowej (cross compilation), +wybieram bibliotekę `scalaprops` dla testów. + +Dodajemy wtyczkę `sbt-scalaprops` do pliku `project/plugins.sbt`: +``` +addSbtPlugin("com.github.scalaprops" % "sbt-scalaprops" % "0.2.6") +``` + +Dodajemy wymagane zależności do `libraryDependencies`: +```scala + libraryDependencies ++= Seq( + "com.github.scalaprops" %%% "scalaprops" % ScalaPropsVersion % "test,it", + "com.github.scalaprops" %%% "scalaprops-scalazlaws" % ScalaPropsVersion % "test,it", + ), +``` + +Ponieważ testy jednostkowe powinny być szybkie, +konfigurujemy `scalaprops` jako testy integracyjne. + +Na początku musimy dodać do cross-projektu ustawienia dla `scalaprops` za pomocą linii: +```scala +lazy val re = crossProject(JSPlatform, JVMPlatform, NativePlatform) + // ... + .settings(scalapropsCoreSettings) +``` + +Następnie musimy wskazać folder, który będzie zawierać testy integracyjne: +```scala +lazy val re = crossProject(JSPlatform, JVMPlatform, NativePlatform) + // ... + .settings( + unmanagedSourceDirectories in IntegrationTest ++= CrossType.Full.sharedSrcDir(baseDirectory.value, "it").toSeq + ) +``` + +Niestety Scala Native nie wspiera obecnie testów integracyjnych. +Może kiedyś będzie wspierać, +może jak będę miał czas sam ogarnę makra i napiszę stosownego pull-requesta. +Do tego czasu będzie mi o tym przypominać zakomentowana linia: +```scala +lazy val re = crossProject(JSPlatform, JVMPlatform, NativePlatform) + // ... + //.nativeSettings(scalapropsNativeSettings) +``` + +Ostatecznie konfiguracja projektu wygląda następująco: +```scala +lazy val re = crossProject(JSPlatform, JVMPlatform, NativePlatform) + .withoutSuffixFor(NativePlatform) + .crossType(CrossType.Full) + .settings(SharedSettings) + .jsSettings(jsSettings) + .jvmSettings(jvmSettings) + .nativeSettings(nativeSettings) + // IntegrationTest + .configs(IntegrationTest) + .settings(Defaults.itSettings) + .settings( + inConfig(IntegrationTest)(scalafixConfigSettings(IntegrationTest)), + inConfig(IntegrationTest)(ScalafmtPlugin.scalafmtConfigSettings), + inConfig(IntegrationTest)(scalariformItSettings), + unmanagedSourceDirectories in IntegrationTest ++= CrossType.Full.sharedSrcDir(baseDirectory.value, "it").toSeq + ) + .jsSettings(inConfig(IntegrationTest)(ScalaJSPlugin.testConfigSettings)) + .nativeSettings(inConfig(IntegrationTest)(Defaults.testSettings)) + // PropsTest + .settings(scalapropsCoreSettings) + //.nativeSettings(scalapropsNativeSettings) +``` + +W folderze `re/shared/src/it/scala/` tworzymy testy dla klasy `pl.writeonly.re.shared.Calculator`: +```scala +package pl.writeonly.re.shared + +import scalaprops.{ Property, Scalaprops } + +object CalculatorIT extends Scalaprops { + val calculator = new Calculator() + + val addition: (Int, Int) => Int = (x, y) => calculator.add(x, y) + val additionTest = Property.forAll { (a: Int, b: Int) => + addition(a, b) == a + b + } + + val multiplication: (Int, Int) => Int = (x, y) => calculator.mul(x, y) + val multiplicationTest = Property.forAll { (a: Int, b: Int) => + multiplication(a, b) == a * b + } + + val lessOrEqual: (Int, Int) => Boolean = (x, y) => calculator.leq(x, y) + val lessOrEqualTest = Property.forAll { (a: Int, b: Int) => + lessOrEqual(a, b) == (a <= b) + } +} +``` + +Wywołujemy: +```bash +sbt clean coverage reJS/it:test reJVM/it:test +``` + +I naszym oczom powinien ukazać się piękny komunikat: +```bash +pl.writeonly.re.shared.CalculatorIT$ ++- additionTest Falsified(0,0,[Arg(0, 18591416),Arg(0, 241819340)],LongSeed(1542137236582000128)) 4ms ++- lessOrEqualTest ................................................. Passed(50,0,LongSeed(1542137236604999936)) 11ms +`- multiplicationTest Falsified(0,0,[Arg(0, -795557759),Arg(0, -1)],LongSeed(1542137236617999872)) 0ms +[error] falsified CalculatorIT additionTest Falsified(0,0,[Arg(0, 18591416),Arg(0, 241819340)],LongSeed(1542137236582000128)) +[error] falsified CalculatorIT multiplicationTest Falsified(0,0,[Arg(0, -795557759),Arg(0, -1)],LongSeed(1542137236617999872)) +[info] pl.writeonly.re.shared.CalculatorIT$ 39 ms +11 pl.writeonly.re.shared.CalculatorIT$.lessOrEqualTest 50 50 +4 pl.writeonly.re.shared.CalculatorIT$.additionTest 50 50 +0 pl.writeonly.re.shared.CalculatorIT$.multiplicationTest 50 50 +[info] 11 pl.writeonly.re.shared.CalculatorIT$.lessOrEqualTest 50 50 +[info] 4 pl.writeonly.re.shared.CalculatorIT$.additionTest 50 50 +[info] 0 pl.writeonly.re.shared.CalculatorIT$.multiplicationTest 50 50 +[error] Failed tests: +[error] pl.writeonly.re.shared.CalculatorIT +[error] (reJS / IntegrationTest / test) sbt.TestsFailedException: Tests unsuccessful +``` +Testy nie przeszły, mamy błąd w kodzie. W związku z tym poprawiamy klasę `Calculator`: +```scala +package pl.writeonly.re.shared + +class Calculator { + type T = Int + + def add(a: T, b: T): T = a + b + + def mul(a: T, b: T): T = a * b + + def leq(a: T, b: T): Boolean = a < b + +} +``` + +I ponownie wywołujemy: +```bash +sbt clean coverage reJS/it:test reJVM/it:test +``` + +Teraz dostajemy poprawną odpowiedź: +```bash +pl.writeonly.re.shared.CalculatorIT$ ++- additionTest ................................................. Passed(50,0,LongSeed(1542137486777999872)) 12ms ++- lessOrEqualTest ................................................. Passed(50,0,LongSeed(1542137486793999872)) 5ms +`- multiplicationTest ................................................. Passed(50,0,LongSeed(1542137486800999936)) 5ms +[info] pl.writeonly.re.shared.CalculatorIT$ 31 ms +12 pl.writeonly.re.shared.CalculatorIT$.additionTest 50 50 +5 pl.writeonly.re.shared.CalculatorIT$.lessOrEqualTest 50 50 +5 pl.writeonly.re.shared.CalculatorIT$.multiplicationTest 50 50 +[info] 12 pl.writeonly.re.shared.CalculatorIT$.additionTest 50 50 +[info] 5 pl.writeonly.re.shared.CalculatorIT$.lessOrEqualTest 50 50 +[info] 5 pl.writeonly.re.shared.CalculatorIT$.multiplicationTest 50 50 +``` + +Ostatecznie moje pełne polecenie do kompilacji to: +```bash +sbt scalafix test:scalafix it:scalafix && \ +sbt scalafmtSbt scalafmt test:scalafmt it:scalafmt && \ +sbt clean compile test:compile it:compile re/test && \ +sbt coverage reJS/test reJVM/test reJS/it:test reJVM/it:test && \ +sbt coverageReport && \ +sbt scalastyle test:scalastyle it:scalastyle && \ +sbt scapegoat cpd stats +``` + +## Smutne podsumowania +Klasyczne testy modułowe (jednostkowe) nie wystarczają, +ponieważ poprawnie napisane testy modułowe, +które pokrywają 100% kodu aplikacji, +mogą być niepoprawne, jeśli dane wejściowe są źle dobrane. + +[Haskell]: /langs/haskell + +[Scalaz]: /libs/scalaz + +[Scala Native]: /tools/scala-native +[Scala.js]: /tools/scala-js diff --git a/_collections/_posts/2018-12-12-ciagla-intergracja.md b/_collections/_posts/2018-12-12-ciagla-intergracja.md new file mode 100644 index 00000000..c428c41e --- /dev/null +++ b/_collections/_posts/2018-12-12-ciagla-intergracja.md @@ -0,0 +1,275 @@ +--- +title: "Ciągła integracja, ciągła kontrola, ciągła Scala" +author: TheKamilAdam +category: scala-native +labels: coveralls continuous-integration travis-ci +langs: scala java rust +tools: clang sbt scala-native ubuntu +libs: +redirect_from: + - ciagla-intergracja + - scala-native/ciagla-intergracja + - resentiment/ciagla-intergracja + - resentiment/2018/12/12/ciagla-intergracja.html +--- + +W poprzednich wpisach zbudowaliśmy ogromne polecenie +do analizy statycznej i dynamicznej kodu projektu oraz generacji raportów. +Jednak wykonanie tego polecenia trwa. +A programiści nie lubią czekać. + +Istnieje podejrzenie graniczące z pewnością, +że programiści pracujący przy projekcie będą wywoływać polecenie fragmentarycznie, +a całe polecenie tylko przed wysłaniem kodu do repozytorium. +O ile i tego nie zapomną lub zignorują. + +W scentralizowanych systemach kontroli wersji takich jak SVN lub CVS +problem ten rozwiązywano za pomocą *hooków* po stronie klienta (tj programisty). +Np. nie można było zrobić *commita*, jeśli kod nie był sformatowany, +a pokrycie kodu testami na odpowiednio wysokim poziomie. +Nie było to jednak dobre rozwiązanie ponieważ każdą walidację po stronie klienta można oszukać, obejść i/lub wyłączyć. + +Dziś istnieją zdecentralizowane systemy kontroli wersji jak Git czy Mercurial. +Pozwalają one w łatwy sposób tworzyć *feature branche*, +dzięki czemu kod nie jest wysyłany bezpośrednio do głównej gałęzi repozytorium. +*Feature branche* nie muszą zawierać sformatowanego kodu, nie muszą się nawet kompilować. + +Jednocześnie chcielibyśmy mieć pewność, że w momencie łączenia *feature branch* z główną gałęzią repozytorium, +kod zawarty w *feature branchy* działa poprawnie i spełnia standardy zdefiniowane w projekcie. +Rozwiązaniem jest tutaj serwer ciągłej integracji. + +## Serwer ciągłej integracji i zamieszanie ze słownictwem + +Serwer ciągłej integracji jest to serwer konfigurowany skryptem, zwanym także *pipeline*. +Większość serwerów, w zależności od konfiguracji jest wstanie robić trzy rzeczy: +* ciągłą integrację +* ciągłe dostarczanie +* ciągłe wdrażanie + +**Ciągła integracja** (ang. *Continuous Integration*, *CI*) jest to proces, +który powinien wykonać się po każdym *commicie* wysłanym do zdalnego repozytorium kodu źródłowego. +W jego skład wchodzą: +* sprawdzenie poprawności formatowania +* analiza statyczna +* kompilacja +* analiza dynamiczna (testy jednostkowe i integracyjne) +* generowanie raportów + +i wszystko inne co zostanie uznane za słuszne dla pojedynczego *commitu*. + +**Ciągłe dostarczanie** (ang. *Continuous Delivery*, *CD*) jest to proces, +który powinien wykonać się po każdym *commicie* (zwykle *merge'u*) do gałęzi głównej (np. master/develop). +Składa się ze wszystkich etapów ciągłej integracji plus dodatkowo: +* nadania numeru wersji (głównie w przypadku bibliotek) +* zbudowaniu paczki wykonywalnej (biblioteki, mikroserwisu, aplikacji) +* wdrożeniu na serwer developerski (w przypadku mikroserwisów i aplikacji) +i uruchomieniu testów systemowych oraz akceptacyjnych, a następnie wdrożeniu na serwer testowy/demonstracyjny +* opublikowaniu w repozytorium artefaktów (głównie w przypadku bibliotek) + +Oczywiście jest to tylko jedna z wielu wersji procesu. +Możliwe punkty zmiany to np.: +* polityka firmy może zakładać, że numery wersji bibliotek powinny być nadawane ręcznie +* aplikacja jest na tyle duża, że niemożliwością jest uruchamianie wszystkich testów po każdej zmianie + +**Ciągłe wdrażanie** (ang. *Continuous Deployment*) jest rozszerzeniem procesu ciągłego dostarczania dla aplikacji +i zawiera tylko jeden dodatkowy punkt, zaufanie. Zaufanie, że: +* aplikacja została odpowiednio przetestowana +* jeśli włączy się alarm, sygnalizujący błąd w aplikacji, to ktoś się nim zajmie + +Dodatkowy krok polega na automatycznym wdrażaniu wydanej aplikacji na serwer produkcyjny. + +Teoretycznie możnaby wprowadzać podział na serwery CI i serwery CD. +Jednak jeśli z poziomu konfiguracji serwera CI mamy dostęp do basha, +lub możemy pisać wtyczki w innych językach programowania, +to z łatwością możemy zamienić serwer CI w serwer CD. +Łatwo więc zauważyć że granica jest tutaj bardzo płynna. + +## Wybór serwera CI/CD + +W świecie Javy jeśli ktoś mówi o serwerze CI zwykle ma na myśli Jenkinsa. +Dostępnych jest jednak wiele serwerów ciągłej integracji. +Chcąc jednak jak najszybciej (najprościej) pokazać zalety ciągłej integracji należy wybrać oprogramowanie darmowe +i dodatkowo dostępne jako usługa (ang. *Software as a Service*, *SaaS*). +Dobrze także, aby *po wyjęciu z pudełka* wspierało używane przez nas języki programowania. +Przy takich założeniach wybór padł na dwa serwisy: +* popularniejszy [Travis CI]() używający kontenerów z **[Ubuntu]** +* młodszy [CircleCI]() używający kontenerów z Debianem + +Niestety nie udało mi się skonfigurować CircleCI dla języka **[ScalaNative]**. +Problemem były zależności dla Debiana. + +Dodatkowo przydatne są także serwisy agregujące raporty z pokrycia kodu testami. +Ja znalazłem dwa działające jako serwis: +* [Code Coverage Done Right | Codecov]() +* [Coveralls - Test Coverage History & Statistics]() + +oba są darmowe dla projektów opensource. + +## Konfiguracja generowania raportów + +Do pliku `project/plugins.sbt` dodajemy dwie wtyczki: +```yaml +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1") +addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.2.7") +``` +* [sbt-scoverage]() umożliwia generowanie raportów z pokrycia kodu testami, +* [sbt-coveralls]() umożliwia wysłanie raportu +do [Coveralls]() + +## Konfiguracja projektu dla Travis Ci + +TravisCi jest konfigurowany za pomocą pliku `.travis.yml`. + +Najpierw wybieramy język programowania, jego wersję, wersję Ubuntu oraz wersję maszyny wirtualnej Javy: +```yaml +language: scala +scala: 2.11.12 +dist: xenial +jdk: openjdk8 +``` + +Niestety `openjdk-8-jdk` nie jest domyślnie zainstalowane na Ubuntu w wersji `xenial`. +Na szczęście możemy doinstalować potrzebną nam wersję Javy z pakietów Ubuntu. +Pozostałe pakiety są dla ScalaNative +```yaml +addons: + apt: + packages: + - openjdk-8-jdk + - libunwind-dev + - libgc-dev + - libre2-dev + - clang-6.0 +``` + +Niestety to nie wystarcza i musimy podmienić wersję Javy w zmiennej środowiskowej `PATH`: +```yaml +env: + global: + - JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/ + - PATH=$JAVA_HOME/bin:$PATH +``` + +Włączamy cache dla folderów zawierających zależności: +```yaml +cache: + directories: + - $HOME/.sbt + - $HOME/.ivy2/cache +``` + +Uruchamiamy analizę statyczną i analizę dynamiczną kodu oraz generujemy raport z testów: +```yaml +script: + - sbt 'scalafix --check' 'test:scalafix --check' 'it:scalafix --check' && + sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck it:scalafmtCheck && + sbt clean re/compile re/test:compile re/it:compile && + sbt coverage reJS/test reJVM/test reJS/it:test reJVM/it:test coverageReport && + sbt coverageAggregate && + sbt scalastyle test:scalastyle it:scalastyle && + sbt scapegoat cpd stats +``` + +Przesyłamy raport do usług agregujących wyniki testów: +```yaml +after_success: + - sbt coveralls + - bash <(curl -s https://codecov.io/bash) +``` + +Pełny plik konfiguracyjny `.travis.yml`: +```yaml +language: scala +scala: 2.11.12 +dist: xenial +jdk: openjdk8 + +addons: + apt: + packages: + - openjdk-8-jdk + - libunwind-dev + - libgc-dev + - libre2-dev + - clang-6.0 + +env: + global: + - JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/ + - PATH=$JAVA_HOME/bin:$PATH + +cache: + directories: + - $HOME/.ivy2/cache + - $HOME/.sbt + +script: + - sbt 'scalafix --check' 'test:scalafix --check' 'it:scalafix --check' && + sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck it:scalafmtCheck && + sbt clean re/compile re/test:compile re/it:compile && + sbt coverage reJS/test reJVM/test reJS/it:test reJVM/it:test coverageReport && + sbt coverageAggregate && + sbt scalastyle test:scalastyle it:scalastyle && + sbt scapegoat cpd stats + +after_success: + - sbt coveralls + - bash <(curl -s https://codecov.io/bash) +``` + +## Podsumowanie + +Uważny czytelnik może zauważyć, że nie wywołuje testów dla **[ScalaNative]**. +Mimo zainstalowania wszystkich pakietów wywołanie testów dla ScalaNative kończy się błędem: +```bash +[error] /usr/bin/ld: warning: libunwind.so.8, needed by /usr/bin/../lib/gcc/x86_64-linux-gnu/5.4.0/../../../x86_64-linux-gnu/libunwind-x86_64.so, may conflict with libunwind.so.1 +[error] /usr/bin/ld: /home/travis/build/writeonly/resentiment/re/native/target/scala-2.11/native/lib/gc/immix/Heap.c.o: undefined reference to symbol '_Ux86_64_getcontext' +[error] //usr/lib/x86_64-linux-gnu/libunwind.so.8: error adding symbols: DSO missing from command line +[error] clang: error: linker command failed with exit code 1 (use -v to see invocation) +[info] Linking native code (immix gc) (184 ms) +[info] Starting process '/home/travis/build/writeonly/resentiment/re/native/target/scala-2.11/re-out' on port '32951'. +Exception in thread "Thread-386" java.io.IOException: Cannot run program "/home/travis/build/writeonly/resentiment/re/native/target/scala-2.11/re-out": error=2, No such file or directory + at java.lang.ProcessBuilder.start(ProcessBuilder.java:1048) + at scala.sys.process.ProcessBuilderImpl$Simple.run(ProcessBuilderImpl.scala:71) + at scala.sys.process.ProcessBuilderImpl$AbstractBuilder.run(ProcessBuilderImpl.scala:102) + at scala.sys.process.ProcessBuilderImpl$AbstractBuilder.$anonfun$runBuffered$1(ProcessBuilderImpl.scala:150) + at scala.runtime.java8.JFunction0$mcI$sp.apply(JFunction0$mcI$sp.java:12) + at scala.sys.process.ProcessLogger$$anon$1.buffer(ProcessLogger.scala:99) + at scala.sys.process.ProcessBuilderImpl$AbstractBuilder.runBuffered(ProcessBuilderImpl.scala:150) + at scala.sys.process.ProcessBuilderImpl$AbstractBuilder.$bang(ProcessBuilderImpl.scala:116) + at scala.scalanative.testinterface.ComRunner$$anon$1.run(ComRunner.scala:31) +Caused by: java.io.IOException: error=2, No such file or directory + at java.lang.UNIXProcess.forkAndExec(Native Method) + at java.lang.UNIXProcess.(UNIXProcess.java:247) + at java.lang.ProcessImpl.start(ProcessImpl.java:134) + at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029) + ... 8 more +``` + +Jest to kolejny problem **[ScalaNative])**. po braku możliwości wygenerowania pokrycia kodu +oraz braku możliwości uruchomienia testów integracyjnych. +Dowodzi to że ScalaNative niestety dalej jest zabawką +i jeśli chce się pisać monady w języku kompilowanym natywnie należy wybrać **[Rust]**. + +## Postscriptum + +Poszukując serwisów CI natrafiłem na jeszcze jeden termin z kategorii *Continuous cośtam*. + +**Ciągła analiza statyczna** (ang. *Continuous static analysis*) jest to proces podobny do ciągłej integracji, ale ograniczony tylko do jednego kroku, +analizy statycznej. +Cechą charakterystyczną serwerów ciągłej analizy statycznej jest posiadanie ogromnej ilości reguł według których sprawdzany jest kod. +Klasycznym przykładem oprogramowania w świecie Javy jest tutaj [SonarQube](). +Ja oczywiście poszukiwałem oprogramowania działającego jako darmowa usługa dla projektów opensorsowych i znalazłem: +* [Codacy: Automated code reviews & code analytics]() +* [Rocro INSPECODE - Code review without the hassle.]() + +## Postscriptum 2 + +Termin **ciągła analiza statyczna** ułożyłem sam, +jednak występujący w jego miejscu termin **ciągła kontrola jakości kodu** (ang. *Continuous Inspection of Code Quality*) +jest według mnie zbyt ogólny. + +[Rust]: /langs/rust + +[Ubuntu]: /tools/ubuntu +[ScalaNative]: /tools/scala-native diff --git a/_collections/_posts/2019-01-09-biblioteki-do-logowania.md b/_collections/_posts/2019-01-09-biblioteki-do-logowania.md new file mode 100644 index 00000000..dbd27711 --- /dev/null +++ b/_collections/_posts/2019-01-09-biblioteki-do-logowania.md @@ -0,0 +1,370 @@ +--- +title: "Biblioteki do logowania dla języka Scala" +author: TheKamilAdam +category: scala-native +tags: library cli factory logging +labels: scala-logging scribe +langs: scala java +tools: scala-js scala-native +lisb: utest slogging +redirect_from: + - biblioteki-do-logowania + - scala-native/biblioteki-do-logowania + - resentiment/biblioteki-do-logowania + - resentiment/2019/01/09/biblioteki-do-logowania.html +--- + +Chcąc dowiedzieć się co dzieje się wewnątrz naszej aplikacji mamy dwie drogi. +Pierwszym sposobem jest debugowanie. +Jednak im więcej wątków w aplikacji i im bardziej komunikują się one w sposób asynchroniczny tym trudniej jest debugować. +Drugim sposobem jest logowanie informacji. +Najprostszym sposobem logowania informacji w Javie jest `System.out.println`, a w Scali upraszcza się to do `println`. +Ale jest to złe z dwóch powodów: +* Po pierwsze, jeśli piszemy aplikację "konsolową" (ang. *command line interface*, **[CLI]**) +to użytkownik będzie niepotrzebnie widział nieinteresujące go informacje z wewnętrznego procesu przetwarzania. +* Tak wypisanych informacji nie można zapisać w bazie danych ani wysłać do innego systemu. + +Dlatego powstały biblioteki do logowania. +Biblioteki takie pozwalają przekierować logi do pliku, zapisać je w bazie danych oraz wysłać je do dowolnego innego systemu. + +## Przegląd bibliotek + +* [scala-logging]() - +wygodna i wydajna biblioteka logowania opakowywująca bibliotekę `SLF4J` dla języka Scala. +Niestety działa tylko dla Scala/JVM +* [util-logging]() - +jest małym opakowaniem wbudowanego logowania Javy, aby uczynić go bardziej przyjaznym dla Scali. +Niestety także, działa tylko dla Scala/JVM +* [scalajs-java-logging]() - +implementacja `java.logging` dla **[Scala.js]**. +Wspiera **[Scala.js]** w wersji 0.6.x i 1.0.x +* [airframe-log]() - +biblioteka do ulepszania logowania aplikacji Scala z kolorami i lokalizacjami kodów źródłowych. +Wspiera Scala.js w wersji 0.6.x i 1.0.x +* [slogging]() - +biblioteka logowania zgodna z `scala-logging` (i `SLF4J`) oparta na makrach +dla Scala/JVM, **[Scala.js]** (wersja 0.6.x) i **[Scala Native]** +* [scribe]() - +praktyczny szkielet logowania, który nie wymaga żadnej innej struktury logowania +i może być w pełni skonfigurowany programowo. +Wspiera **[Scala.js]** w wersji 0.6.x oraz **[Scala Native]**. + +## I konkretne próby zastosowania + +### scala-logging +Jest to najprawdopodobniej najpopularniejsza biblioteka do logowania w języku Scala. +Niestety jej wadą jest to, że działa tylko dla JVM. + +W pliku `build.sbt` dodajemy bibliotekę do wspólnych zależności: +```scala + libraryDependencies ++= Seq( + "com.typesafe.scala-logging" %% "scala-logging" % "3.9.0", + ), +``` + +`Scala-logging` można używać na dwa sposoby. +Za pomocą traitów `StrictLogging` i `LazyLogging`. +Oba traity tworzą zmienną `logger`, która jest loggerem. + +Trait `StrictLogging` inicjalizuje `logger` w momencie utworzenia klasy: +```scala +package com.typesafe.scalalogging + +import org.slf4j.LoggerFactory + +trait StrictLogging { + protected val logger: Logger = Logger(LoggerFactory.getLogger(getClass.getName)) +} +``` + +```scala +package pl.writeonly.re.shared + +import com.typesafe.scalalogging.StrictLogging + +object StrictLoggingCore extends Core with StrictLogging { + def apply(arg: String): Unit = { + logger.info(s"Hello Scala $arg!") + } +} +``` + +Trait `LazyLogging` inicjalizuje `logger` w momencie pierwszego użycia loggera: +```scala +package com.typesafe.scalalogging + +import org.slf4j.LoggerFactory + +trait LazyLogging { + @transient + protected lazy val logger: Logger = Logger(LoggerFactory.getLogger(getClass.getName)) +} +``` + +```scala +package pl.writeonly.re.shared + +import slogging.LazyLogging + +object LazyLoggingCore extends Core with LazyLogging { + def apply(arg: String): Unit = { + logger.info(s"Hello Scala $arg!") + } +} +``` + +Łączymy wszystko w obiekcie `Core`: +```scala +package pl.writeonly.re.shared + +trait Core { + def apply(arg: String): Unit +} + +object Core extends Core { + override def apply(arg: String): Unit = { + StrictLoggingCore(arg) + LazyLoggingCore(arg) + } +} +``` + +Tworzymy test jednostkowy we frameworku **[uTest]**: +```scala +package pl.writeonly.re.shared + +import utest._ + +object CoreTest extends TestSuite { + override val tests: Tests = Tests { + 'core - { + Core("Awesome") + } + } +} +``` + +Wywołujemy: +```bash +sbt clean re/test +``` + +I wszystko się wysypuje, bo `scala-logging` wspiera tylko JVM. + +### slogging +Jest to przepisana biblioteka `scala-logging`, +która działa dla Scala Native, Scala.js oraz oczywiście Scala/JVM. + +W pliku `build.sbt` dodajemy bibliotekę do wspólnych zależności: +```scala + libraryDependencies ++= Seq( + "biz.enef" %%% "slogging" % SloggingVersion, + ), +``` + +`Slogging` używa się identycznie jak `scala-logging`. + +Trait `StrictLogging` inicjalizuje `logger` w momencie utworzenia klasy: +```scala +package slogging + +trait StrictLogging extends LoggerHolder { + protected val logger : Logger = LoggerFactory.getLogger(loggerName) +} +``` + +```scala +package pl.writeonly.re.shared + +import slogging.StrictLogging + +object StrictLoggingCore extends Core with StrictLogging { + def apply(arg: String): Unit = { + logger.info(s"Hello Scala $arg!") + } +} +``` + +Trait `LazyLogging` inicjalizuje `logger` w momencie pierwszego użycia loggera: +```scala +package slogging + +trait LazyLogging extends LoggerHolder { + protected lazy val logger = LoggerFactory.getLogger(loggerName) +} +``` + +```scala +package pl.writeonly.re.shared + +import slogging.LazyLogging + +object LazyLoggingCore extends Core with LazyLogging { + def apply(arg: String): Unit = { + logger.info(s"Hello Scala $arg!") + } +} +``` + +Łączymy wszystko w obiekcie `Core`: +```scala +package pl.writeonly.re.shared + +trait Core { + def apply(arg: String): Unit +} + +object Core extends Core { + override def apply(arg: String): Unit = { + StrictLoggingCore(arg) + LazyLoggingCore(arg) + } +} +``` + +Tworzymy test jednostkowy we frameworku **[uTest]**: +```scala +package pl.writeonly.re.shared + +import utest._ + +object CoreTest extends TestSuite { + override val tests: Tests = Tests { + 'core - { + Core("Awesome") + } + } +} +``` + +Wywołujemy: +```bash +sbt clean re/test +``` + +I wszystko działa! + +### Scribe + +W pliku `build.sbt` dodajemy bibliotekę do wspólnych zależności: +```scala + libraryDependencies ++= Seq( + "com.outr" %%% "scribe" % ScribeVersion, + ), +``` + +`Scribe` można używać na dwa sposoby. +Za pomocą traitu `Logging` oraz za pomocą obiektu pakietu `scribe`. + +Trait `Logging` w prostu sposób tworzy `logger` dla każdej instancji klasy: +```scala +trait Logging { + protected def loggerName: String = getClass.getName + + protected def logger: Logger = Logger(loggerName) +} +``` + +```scala +package pl.writeonly.re.shared + +import scribe.Logging + +object LoggingCore extends Core with Logging { + override def apply(arg: String): Unit = { + logger.info(s"Hello Scala $arg!") + } +} +``` + +Obiekt pakietu `scribe` zawiera magię opartą na makrach, dlatego dziedziczenie nie jest potrzebne: +```scala +package object scribe extends LoggerSupport { + lazy val lineSeparator: String = System.getProperty("line.separator") + + protected[scribe] var disposables = Set.empty[() => Unit] + + override def log[M](record: LogRecord[M]): Unit = Logger(record.className).log(record) + + def dispose(): Unit = disposables.foreach(d => d()) + + implicit class AnyLogging(value: Any) { + def logger: Logger = Logger(value.getClass.getName) + } + + def async[Return](f: => Return): Return = macro Macros.async[Return] + + def future[Return](f: => Return): Future[Return] = macro Macros.future[Return] + + object Execution { + implicit def global: ExecutionContext = macro Macros.executionContext + } +} +``` + +```scala +package pl.writeonly.re.shared + +object ScribeCore extends Core { + def apply(arg: String): Unit = { + scribe.info(s"Hello Scala $arg!") + } +} +``` + +Łączymy wszystko obiektem `Core`: +```scala +package pl.writeonly.re.shared + +trait Core { + def apply(arg: String): Unit +} + +object Core extends Core { + override def apply(arg: String): Unit = { + LoggingCore(arg) + ScribeCore(arg) + } +} +``` + +Tworzymy test: +```scala +package pl.writeonly.re.shared + +import utest._ + +object CoreTest extends TestSuite { + override val tests: Tests = Tests { + 'core - { + Core("Awesome") + } + } +} +``` + +Wywołujemy: +```bash +sbt clean re/test +``` + +Niestety pojawia się błąd: +``` +[error] cannot link: @java.util.Calendar$::getInstance_java.util.Calendar +[error] cannot link: @java.util.Calendar::setTimeInMillis_i64_unit +[error] unable to link +[error] (re / Nativetest / nativeLink) unable to link +``` + +## Podsumowanie + +Jak zwykle składnia Scali pozwala zapisać te same rzeczy prościej niż w Javie, +jednocześnie dzięki temu można wymusić konwencję tworzenia loggerów na etapie kompilacji. +Dzięki temu nie mamy w kodzie loggerów o nazwach innych niż `loggger` jak np. `LOGGER` lub `LOG`. + +[Scala Native]: /tools/scala-native +[Scala.js]: /tools/scala-js + +[uTest]: /libs/utest + +[CLI]: /tags/cli diff --git a/_collections/_posts/2019-01-16-kategorie-i-tagi.md b/_collections/_posts/2019-01-16-kategorie-i-tagi.md new file mode 100644 index 00000000..c0105a5a --- /dev/null +++ b/_collections/_posts/2019-01-16-kategorie-i-tagi.md @@ -0,0 +1,247 @@ +--- +title: "Jekyll - kategorie i tagi" +author: TheKamilAdam +category: jekyll +tags: blog +tools: jekyll +redirect_from: + - kategorie-i-tagi + - jekyll/kategorie-i-tagi + - writeonlydoc/kategorie-i-tagi + - writeonlydoc/2019/01/16/kategorie-i-tagi.html +--- + +Opublikowałem już kilka artykułów na blogu i chciałem je w jakiś sposób pogrupować. +Najlepiej za pomocą kategorii i tagów. + +## Metadane + +Dla każdego postu oprócz zawartości zapisywałem także metadane, +w tym kategorie i tagi. +W przypadku tego artykułu są to: + +{%raw%} +```yaml +--- +#layout: post +title: "Jekyll - kategorie i tagi" +author: TheKamilAdam +category: jekyll +tags: jekyll blog +--- +``` +{%endraw%} + +Artykuł może zawierać wiele tagów, ale musi mieć dokładnie jedną kategorię. + +## Strony pomocnicze + +Jest to najprawdopodobniej największa wada Jekylla. +Dla każdej kategorii i dla każdego taga trzeba utworzyć stronę pomocniczą. +Strony pomocnicze kategorii muszą znajdować się w folderze `_categories`, +a strony pomocnicze tagów w folderze `_tags`. + +Przykładowy strona pomocnicza dla kategorii: + +{%raw%} +```yaml +--- +permalink: /categories/resentiment +labelcategory: "resentiment" +--- +Opis powstawania projektu resentiment. +``` +{%endraw%} + +Przykładowy strona pomocnicza dla tagu: + +{%raw%} +```yaml +--- +permalink: /tools/scala-native +#layout: page-tag +tag: "scala-native" +--- +``` +{%endraw%} + +## Kolekcje + +Ponieważ stron pomocniczych jest dużo, +dlatego źle jest je trzymać w przestrzeni głównej projektu. +Problem ten można rozwiązać za pomocą kolekcji. +W pliku `_config.yml` dodajemy: + +```yaml +collections: + categories: + output: true + tags: + output: true +``` +Od tej pory strony dla kategorii będą znajdować się w katalogu `_categories`, +a strony dla tagów w katalogu `_tags`. + +Ale dla mnie nawet to było za dużo. +Dlatego kolekcje przeniosłem do folderu `data`. +W pliku `_config.yml` dodajemy: +```yaml +collections_dir: data +``` + +Od tej pory: +* w katalogu `data/_categories` znajdują się kategorie artykułów +* w katalogu `data/_posts` znajdują się artykuły +* w katalogu `data/_tags` znajdują się tagi + +## Layouts - układy stron + +W stronach pomocniczych użyliśmy layoutów `page-categories` i `page-tags`. +Teraz trzeba je zdefiniować. + +Układ strony `_layout/page-category.html` zawierający wszystkie artykuły z danej kategorii: + +{%raw%} +```yaml +--- +#layout: default +title: {{ page.category }} +--- +
+ {% include breadcrumbs.html %} +

Kategoria: {{ page.title }}

+ + {{ posts.content }} + +
+ {% for post in site.posts %} + {% if post.category == page.category %} +
+ + {{ post.title }} + + + + +
+
+ {% assign tags = post.tags | sort %} + Tagi: + {% for tag in tags %} + {{ tag }} + {% endfor %} +
+ {% endif %} + {% endfor %} +
+
+``` +{%endraw%} + +Układ strony `_layout/page-tag.html` zawierający wszystkie artykuły z danym tagiem: +{%raw%} +```yaml +--- +#layout: default +title: {{ page.tag }} +--- +
+ {% include breadcrumbs.html %} +

Tag: {{ page.tag | capitalize }}

+ +
+ {% for post in site.posts %} + {% if post.tags contains page.tag %} +
+ + {{ post.title }} + + + + +
+
+ Kategoria : + {{ post.category | capitalize }} +
+ + {% endif %} + {% endfor %} +
+
+``` +{%endraw%} + +W obu przypadkach iterujemy po liście wszystkich artykułów i prostym `if`em wybieramy te które nas interesują. + +## Pages - strony specjalne + +Potrzebujemy jeszcze jednej rzeczy. + +Strona `pages/categories.html` zawierająca listę wszystkich kategorii: +{%raw%} +```yaml +--- +#layout: posts +title: Kategorie +permalink: /categories/ +description: Kategorie artykułów +--- +
+ {% assign categories = site.categories | sort: "title" %} +
+ {% for node in categories %} + {% if node.title != null and node.layout == "page-category" %} +
+ {{ node.title }} +
+
+ {{ node.content }} +
+ {% endif %} + {% endfor %} +
+
+``` +{%endraw%} + +Strona `pages/tags.html` zawierająca listę wszystkich tagów: +{%raw%} +```yaml +--- +#layout: posts +title: Tagi +permalink: /tags/ +description: Tagi artykułów +--- +
+
    + {% assign tags = site.tags | sort: "title" %} + {% for node in tags %} + {% if node.title != null and node.layout == "page-tag" %} +
  • + {{ node.title }} +
  • + {% endif %} + {% endfor %} +
+
+``` +{%endraw%} + +W obu przypadkach pobieramy listę wszystkich kategorii/tagów i sortujemy po tytułach. +A następnie odrzucamy te które nie są poprawne do wyświetlenia, +czyli zawierają błędny `layout` lub nie posiadają tytułu. + +## Podsumowanie + +Żeby wszystko działało trzeba było napisać trochę kodu. +Łącznie 17 stron pomocniczych dla tagów, 3 - dla kategorii, +dwa układy stron i dwie strony specjalne do agregacji kategorii i tagów. +Ale oczywiście nie to zajeło najwięcej czasu. +Największym problemem była wolność jaką daje jekyll, +czyli możliwość zbudowania układów stron w dowolny sposób. diff --git a/_collections/_posts/2019-01-30-no-universal-equality.md b/_collections/_posts/2019-01-30-no-universal-equality.md new file mode 100644 index 00000000..4ee26774 --- /dev/null +++ b/_collections/_posts/2019-01-30-no-universal-equality.md @@ -0,0 +1,179 @@ +--- +title: "Scala - No Universal Equality" +author: TheKamilAdam +category: scala-native +tags: compiler library monad operator type-class +labels: scalatic +langs: haskell java scala +tools: scala-jvm scala-js scala-native scalafix +libs: cats scalatest scalaz utest +projects: resentiment +redirect_from: + - no-universal-equality + - scala-native/no-universal-equality + - resentiment/no-universal-equality + - resentiment/no-universal-equality.html +--- + +Ostatnim błędem zgłaszanym w kodzie projektu **[resentiment]** przez **[scalafix]** +jest "No Universal Equality" wynikający z użycia operatora `==`. + +W języku **[Scala]** obiekty domyślnie porównuje się za pomocą operatora `==`. +Operator ten wywołuje pod spodem znaną z Javy metodę `equals`. +Operator `==` pozwala jednak na bezsensowne porównywanie obiektów, które są różnych klas. +Mimo że poprawnie napisana metoda `equals` zawsze zwróci w takim przypadku `false`. +Np. porównanie `"A" == 'A'` zawsze zwróci `false`, +ponieważ `"A"` jest klasy `String`, a `'A'` jest klasy `Char`. +Umieszczenie czegoś takiego w kodzie zawsze jest błędem programisty i powinno spowodować nieskompilowanie się kodu. +Jest to nazywane problemem `Universal Equality`. +Można ten problem rozwiązać na kilka sposobów. + +## Dotty - Multiversal Equality + +Najprostszym sposobem rozwiązania problemu `Universal Equality` jest poczekać. +Nowa wersja **[kompilator]** Scala - *[Dotty]()* będzie posiadać +[Multiversal Equality]() +co rozwiązuje problem. + +## Biblioteki zewnętrzne +Jeśli jednak jesteście niecierpliwi istnieje kilka bibliotek rozwiązujących ten problem. +Wykorzystują one to, +że Scala pozwala implementować metody, +których wywołanie wygląda jak wywołanie operatora i zwykle tym nowym operatorem jest `===`. + +### Scalactic +**[Scalactic]()** +jest to zestaw utilsów/narzędzi wydzielony ze **[ScalaTest]**, +który może być przydatny także w kodzie produkcyjnym. + +**Scalactic** posiada wiele klas do porównywania wartości, +między innymi klasę `org.scalactic.TypeCheckedTripleEquals`, +która wymaga by oba porównywane obiekty były tej samej klasy. + +Największą zaletą biblioteki **Scalactic** jest to, +że nie zagłębia się w teorię czystego programowania funkcyjnego (ang. *pure functional programming*) +i nie pojawiają się tam takie straszne terminy jak **[monada]** czy **[klasy typów]**. + +### Scalaz i Cats +*Kolejność chronologiczna.* + +Są to dwie wspaniałe biblioteki, które robią ze Scali język funkcyjny przypominający język **[Haskell]**. + +Biblioteki te dzielą się na dwie główne części: +* *Data types* - tutaj znajdują się monady, które są w Haskellu, ale nie ma ich w bibliotece standardowej języka Scala +* *Type classes* - typ konstruktu systemowego, który obsługuje polimorfizm *ad hoc*. + +Zarówno **[Scalaz]** jak i **[Cats]** posiadają klasę `Equal`, +która pozwala porównywać obiekty za pomocą operatora `===`. + +Dobre porównanie obu bibliotek można znaleźć na [githubie](), +chociaż różnice są bardzo małe. +Obie biblioteki wspierają **[scala.js])**, +ale tylko **Scalaz** - **[scala native]**. + +## "No Universal Equality" z Scalaz w projekcie Resentiment + +W `build.sbt` do `SharedSettings` dodajemy bibliotekę `Scalaz`: +```scala + libraryDependencies += "org.scalaz" %%% "scalaz-core" % "7.2.27", +``` + +Jeśli używamy scalafix w pliku `.scalafix.conf` można dodać/odkomentować linię: +```conf +DisableSyntax.noUniversalEquality = true +``` + +Teraz należy poprawić cały kod zamieniając `==` na `===`. +W przypadku projektu **[resentiment]** są to klasy `CalculatorTest` i `CalculatorIT` + +```scala +package pl.writeonly.re.shared + +import scalaz.Scalaz._ +import utest._ + +object CalculatorTest extends TestSuite { + override val tests: Tests = Tests { + val calculator = new Calculator() + 'addition - { + val addition: (Int, Int) => Int = (x, y) => calculator.add(x, y) + "0 + 0 == 0" - { + assert(addition(0, 0) === 0) + } + "2 + 2 == 4" - { + assert(addition(2, 2) === 4) + } + } + 'multiplication - { + val multiplication: (Int, Int) => Int = (x, y) => calculator.mul(x, y) + "0 + 0 == 0" - { + assert(multiplication(0, 0) === 0) + } + "2 + 2 == 4" - { + assert(multiplication(2, 2) === 4) + } + } + 'less_or_equal - { + val less_or_equal: (Int, Int) => Boolean = (x, y) => calculator.leq(x, y) + "0 <= 2 == true" - { + assert(less_or_equal(0, 2)) + } + "2 <= 0 == false" - { + assert(!less_or_equal(2, 0)) + } + } + } +} +``` + +```scala +package pl.writeonly.re.shared + +import scalaprops.{Property, Scalaprops} +import scalaz.Scalaz._ + +object CalculatorIT extends Scalaprops { + val calculator = new Calculator() + + val addition: (Int, Int) => Int = (x, y) => calculator.add(x, y) + val additionTest = Property.forAll { (a: Int, b: Int) => + addition(a, b) === a + b + } + + val multiplication: (Int, Int) => Int = (x, y) => calculator.mul(x, y) + val multiplicationTest = Property.forAll { (a: Int, b: Int) => + multiplication(a, b) === a * b + } + + val lessOrEqual: (Int, Int) => Boolean = (x, y) => calculator.leq(x, y) + val lessOrEqualTest = Property.forAll { (a: Int, b: Int) => + lessOrEqual(a, b) === (a <= b) + } +} + +``` + +## Podsumowanie +Język Scala posiada kilka mniejszych lub większych błedów projektowych. +Część z nich zostanie poprawiona w nowej wersji kompilatora Dotty. +Jeśli jednak nie chcemy czekać do tego czasu warto się rozejrzeć w internecie, +ponieważ prawdopodobnie istnieją już rozwiązania naszych problemów. +"Universal Equality" jest jednym z problemów, +które w prosty sposób mogą być rozwiązane już dziś. + +[Haskell]: /langs/haskell +[Scala]: /langs/scala + +[Cats]: /libs/cats +[ScalaTest]: /libs/scalatest +[Scalaz]: /libs/scalaz + +[scala native]: /tools/scala-native +[scalafix]: /tools/scalafix +[scala.js]: /tools/scala-js + +[resentiment]: /projects/resentiment + +[klasy typów]: /tags/type-class +[kompilator]: /tags/compiler +[monada]: /tags/monad diff --git a/_collections/_posts/2019-02-13-setxkbmap.md b/_collections/_posts/2019-02-13-setxkbmap.md new file mode 100644 index 00000000..eb1fe957 --- /dev/null +++ b/_collections/_posts/2019-02-13-setxkbmap.md @@ -0,0 +1,74 @@ +--- +title: "setxkbmap - Jak szybko zmienić układ klawiatury z konsoli na Ubuntu?" +author: TheKamilAdam +category: cli +tags: cli +langs: +tools: bash ubuntu +libs: +redirect_from: + - setxkbmap + - cli/setxkbmap + - cli/2019/02/13setxkbmap.html +--- + +Po długiej przerwie prosty artykuł "Jak szybko zmienić układ klawiatury z konsoli na **[Ubuntu]**?". + +## Problem + +Podczas pracy na Xubuntu zainstalowanym na wirtualnej maszynie często zmienia mi się układ klawiatury z polskiej na amerykańską. +Na **[Ubuntu]** można odwrócić ten proces w łatwy sposób za pomocą ikonki na pasku zadań, +jednak już w Xubuntu ta opcja jest ukryta głęboko w trzewiach ustawień systemowych. +Dlatego prościej jest to zrobić z linii poleceń. + +## Rozwiązanie - setxkbmap + +Aby szybko zmienić układ klawiatury, wystarczy zainstalować 'setxkbmap' za pomocą polecenia: +```bash +sudo apt-get install x11-xkb-utils +``` + +Następnie można łatwo zmieniać układ klawiatury za pomocą polecenia: +```bash +setxkbmap pl +``` + +Pomoc można wyświetlić za pomocą polecenia: +```bash +setxkbmap -help +``` + +Pomoc polecenia w wersji 'setxkbmap 1.3.1' : +``` +Usage: setxkbmap [options] [ [ [