Closed rizomaa closed 5 years ago
@lptn =)
Я согласен, жду поста @lptn
@Tyuba4 @fr0zen
вось галоўныя тулзы каб правяраць код-стайл:
Так жа есьць тулза каторыя аб'ядноўвае іх: https://github.com/Symplify/EasyCodingStandard Тут артукул пра тое як яна гэта робіць https://www.tomasvotruba.cz/blog/2017/05/03/combine-power-of-php-code-sniffer-and-php-cs-fixer-in-3-lines/
Прычым яна не проста аб'ядноўвае, але і дадае некаторыя плюшкі як:
Як запускаць праверку:
vendor/bin/ecs check ./
Як запускаць аўта-фікс:
vendor/bin/ecs check ./ --fix
Вядома ж для любога з трох варыянтаў стварыць кастомную каманду для кампозер, напрыклад:
# lint only
composer php:lint
# lint and fix
composer php:fix
Заўважу, што усе гэта не проста СЛАЙЛ чэкеры, яны могуць і некаторыя крутыя штукі, напрыклад пошук кода з высокім (Cyclomatic_complexity)[https://en.wikipedia.org/wiki/Cyclomatic_complexity] (ці, карацей кажучу, будзе біць па рукам за некаторы гаўна-код)
Запуск любой з выбраных тулзовін трэба рабіць на CI (Jenkins у нашым выпадку).
На дадзены момант у мяне есць досвед працы толькі з PHPCode-Sniffer, вось канфіг для яго з майго рабочага праекту:
<?xml version="1.0"?>
<!--
This is describes our custom inspection rules deeply relaying on PSR2 coding standard.
We are divided CS configuration into 2 (second is 'phpcs.xml') files because PHPStorm can't use
this ruleset as default standard when no arguments provided for phpcs command line tool.
Also we want to keep this standard keep from any our file or directory-specific settings.
@see https://github.com/InteractionDesignFoundation/IDF-web/blob/develop/docs/environment/tools/code-quality-tools.md
@see https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-ruleset.xml
@see https://github.com/squizlabs/PHP_CodeSniffer/blob/mastersrc/Standards/PSR2/ruleset.xml
-->
<ruleset name="IDF">
<description>The IDF coding standard for Laravel applications</description>
<!--1) EXTEND RULE-SETS-->
<rule ref="PSR2">
<exclude name="PSR1.Methods.CamelCapsMethodName.NotCamelCaps"/>
<include name="PSR2.Classes.PropertyDeclaration"/><!--This sniff is also able to fix the order of the modifier keywords if they are incorrect-->
</rule>
<rule ref="PSR2.Classes.PropertyDeclaration"/><!--Checks for empty catch clause without a comment.-->
<rule ref="PSR12">
<exclude name="PSR12.Operators.OperatorSpacing"/><!-- Need to exclude concatenation rule but there is no such option :( -->
</rule>
<rule ref="vendor/slevomat/coding-standard/SlevomatCodingStandard/ruleset.xml"><!-- relative path to your ruleset.xml -->
<exclude name="SlevomatCodingStandard.Types.EmptyLinesAroundTypeBraces"/>
<exclude name="SlevomatCodingStandard.Classes.SuperfluousExceptionNaming"/><!--We DO use Exception postfix-->
<exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedClassNameInAnnotation"/>
<exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedGlobalFunctions"/>
<exclude name="SlevomatCodingStandard.Namespaces.UseOnlyWhitelistedNamespaces"/>
<exclude name="SlevomatCodingStandard.Files.TypeNameMatchesFileName"/>
<exclude name="SlevomatCodingStandard.ControlStructures.DisallowEmpty"/>
<exclude name="SlevomatCodingStandard.ControlStructures.RequireYodaComparison"/>
<exclude name="SlevomatCodingStandard.Operators.DisallowIncrementAndDecrementOperators"/>
<exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedExceptions"/>
<exclude name="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming"/>
<exclude name="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming"/>
<exclude name="SlevomatCodingStandard.Commenting.DisallowOneLinePropertyDocComment"/>
<exclude name="SlevomatCodingStandard.ControlStructures.DisallowShortTernaryOperator"/>
<exclude name="SlevomatCodingStandard.Commenting.UselessInheritDocComment"/>
<exclude name="SlevomatCodingStandard.Functions.StaticClosure"/>
<exclude name="SlevomatCodingStandard.Variables.UselessVariable"/><!--Improve code readability in some cases- -->
<!--@todo temporary disabled due to a lot of violations. Desirable to use in the future -->
<exclude name="SlevomatCodingStandard.Commenting.DocCommentSpacing">
<properties>
<property name="linesCountBetweenDescriptionAndAnnotations" value="0"/>
<property name="linesCountBetweenDifferentAnnotationsTypes" value="0"/>
</properties>
</exclude>
<exclude name="SlevomatCodingStandard.ControlStructures.DisallowEqualOperators"/>
<exclude name="SlevomatCodingStandard.ControlStructures.EarlyExit"/>
<exclude name="SlevomatCodingStandard.ControlStructures.NewWithoutParentheses"/>
<exclude name="SlevomatCodingStandard.ControlStructures.ControlStructureSpacing.IncorrectLinesCountBeforeControlStructure"/>
<exclude name="SlevomatCodingStandard.ControlStructures.ControlStructureSpacing.IncorrectLinesCountAfterControlStructure"/>
<exclude name="SlevomatCodingStandard.ControlStructures.ControlStructureSpacing.IncorrectLinesCountBeforeFirstControlStructure"/>
<exclude name="SlevomatCodingStandard.ControlStructures.ControlStructureSpacing.IncorrectLinesCountAfterLastControlStructure"/>
<exclude name="SlevomatCodingStandard.ControlStructures.RequireMultiLineTernaryOperator"/>
<exclude name="SlevomatCodingStandard.Classes.UnusedPrivateElements"/>
<exclude name="SlevomatCodingStandard.Classes.TraitUseSpacing"/>
<exclude name="SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter"/>
<exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedClassNameAfterKeyword"/>
<exclude name="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly"/>
<exclude name="SlevomatCodingStandard.TypeHints.DeclareStrictTypes"/>
<exclude name="SlevomatCodingStandard.TypeHints.NullableTypeForNullDefaultValue"/>
<exclude name="SlevomatCodingStandard.TypeHints.TypeHintDeclaration"/>
<exclude name="SlevomatCodingStandard.Variables.UnusedVariable"/>
<exclude name="SlevomatCodingStandard.PHP.UselessParentheses.UselessParentheses"/><!--the problem is clone directive-->
</rule>
<rule ref="SlevomatCodingStandard.Namespaces.UnusedUses">
<properties>
<property name="searchAnnotations" value="true"/>
</properties>
</rule>
<!-- <rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration">
<properties>
<property name="traversableTypeHints" value="array">
<element value="array"/>
<element value="Illuminate\Database\Eloquent\Collection"/>
<element value="Illuminate\Support\Collection"/>
</property>
</properties>
</rule>-->
<!--2) ADD SOME OTHER RULES ON THE TOP OF INCLUDED RULE-SETS-->
<rule ref="PSR2.ControlStructures.ElseIfDeclaration.NotAllowed"><!-- Disallow else if in favor of elseif -->
<type>error</type>
</rule>
<rule ref="Squiz.Commenting.EmptyCatchComment"/><!--Checks for empty catch clause without a comment.-->
<rule ref="Squiz.Commenting.FunctionComment"><!-- Force rules for function phpDoc -->
<exclude name="Squiz.Commenting.FunctionComment.EmptyThrows"/><!-- Allow `@throws` without description -->
<exclude name="Squiz.Commenting.FunctionComment.IncorrectParamVarName"/><!-- Does not work properly with PHP 7 / short-named types -->
<exclude name="Squiz.Commenting.FunctionComment.IncorrectTypeHint"/><!-- Does not support collections, i.e. `string[]` -->
<exclude name="Squiz.Commenting.FunctionComment.InvalidReturn"/><!-- Forces incorrect types -->
<exclude name="Squiz.Commenting.FunctionComment.InvalidReturnNotVoid"/><!-- Breaks with compound return types, i.e. `string|null` -->
<exclude name="Squiz.Commenting.FunctionComment.InvalidTypeHint"/><!-- Breaks when all params are not documented -->
<exclude name="Squiz.Commenting.FunctionComment.Missing"/><!-- Doc comment is not required for every method -->
<exclude name="Squiz.Commenting.FunctionComment.MissingParamComment"/><!-- Do not require comments for `@param` -->
<exclude name="Squiz.Commenting.FunctionComment.MissingParamTag"/><!-- Do not require `@param` for all parameters -->
<exclude name="Squiz.Commenting.FunctionComment.MissingReturn"/><!-- Do not require `@return` for void methods -->
<exclude name="Squiz.Commenting.FunctionComment.ParamCommentFullStop"/><!-- Comments don't have to be sentences -->
<exclude name="Squiz.Commenting.FunctionComment.ParamCommentNotCapital"/><!-- Comments don't have to be sentences -->
<exclude name="Squiz.Commenting.FunctionComment.ParamNameNoMatch"/><!-- Breaks when all params are not documented -->
<exclude name="Squiz.Commenting.FunctionComment.ScalarTypeHintMissing"/><!-- Doesn't respect inheritance -->
<exclude name="Squiz.Commenting.FunctionComment.TypeHintMissing"/><!-- Doesn't work with self as typehint -->
<exclude name="Squiz.Commenting.FunctionComment.ThrowsNotCapital" />
<exclude name="Squiz.Commenting.FunctionComment.ThrowsNoFullStop" />
<exclude name="Squiz.Commenting.FunctionComment.SpacingAfterParamType" />
<exclude name="Squiz.Commenting.FunctionComment.SpacingAfterParamName" />
</rule>
<rule ref="Squiz.Classes.ClassFileName"/><!-- Forbid class being in a file with different name -->
<rule ref="Squiz.Operators.ValidLogicalOperators"/><!-- Forbid `AND` and `OR`, require `&&` and `||` -->
<rule ref="Squiz.PHP.LowercasePHPFunctions"/><!-- Require PHP function calls in lowercase -->
<rule ref="Squiz.PHP.DiscouragedFunctions"/><!--Discourages the use of debug functions.-->
<rule ref="Squiz.PHP.NonExecutableCode"/><!--Warns about code that can never been executed.-->
<rule ref="Squiz.Scope.MemberVarScope"/><!--Verifies that class members have scope modifiers.-->
<rule ref="Squiz.Scope.StaticThisUsage"/><!--Checks for usage of $this in static methods, which will cause runtime errors.-->
<rule ref="Squiz.Strings.ConcatenationSpacing"><!--Makes sure there are no spaces around the concatenation operator.-->
<properties>
<property name="ignoreNewlines" type="bool" value="true" />
</properties>
</rule>
<rule ref="Squiz.Strings.DoubleQuoteUsage.NotRequired"/><!-- Use singular quotes by default -->
<rule ref="Squiz.Strings.EchoedStrings"/><!-- Forbid braces around string in `echo` -->
<rule ref="Squiz.WhiteSpace.LogicalOperatorSpacing"/><!-- Require space around logical operators -->
<rule ref="Squiz.WhiteSpace.ObjectOperatorSpacing"><!-- Forbid spaces around `->` operator -->
<properties>
<property name="ignoreNewlines" value="true"/>
</properties>
</rule>
<rule ref="Squiz.WhiteSpace.OperatorSpacing"><!-- It's like PRS-12 OperatorSpacing rule, but has do not check for concatenation -->
<properties>
<property name="ignoreNewlines" type="bool" value="true" />
</properties>
</rule>
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace"><!-- Forbid superfluous whitespaces -->
<properties>
<property name="ignoreBlankLines" value="false"/><!-- turned on by PSR2 -> turning back off -->
</properties>
</rule>
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/><!-- Forbid `array(...)` -->
<rule ref="Generic.CodeAnalysis.EmptyStatement"><!-- Forbid empty statements -->
<exclude name="Generic.CodeAnalysis.EmptyStatement.DetectedCatch"/><!--But allow empty catch -->
</rule>
<rule ref="Generic.Classes.DuplicateClassName"/>
<rule ref="Generic.CodeAnalysis.AssignmentInCondition"/><!-- Warn about variable assignments inside conditions -->
<rule ref="Generic.CodeAnalysis.UnnecessaryFinalModifier"/><!-- Forbid final methods in final classes -->
<rule ref="Generic.CodeAnalysis.UselessOverridingMethod"/><!-- Forbid useless empty method overrides -->
<rule ref="Generic.CodeAnalysis.UnusedFunctionParameter"/>
<rule ref="Generic.Commenting.Fixme"/>
<rule ref="Generic.ControlStructures.InlineControlStructure"/>
<rule ref="Generic.Formatting.DisallowMultipleStatements"/>
<rule ref="Generic.Formatting.MultipleStatementAlignment"/>
<rule ref="Generic.Functions.CallTimePassByReference"/><!--Ensures that variables are not passed by reference when calling a function.-->
<rule ref="Generic.Formatting.SpaceAfterCast"/><!-- Force whitespace after a type cast -->
<rule ref="Generic.Files.OneObjectStructurePerFile"/><!-- Ensure there is a single class/interface/trait per file -->
<rule ref="Generic.Files.LineLength.TooLong">
<properties>
<property name="lineLimit" value="120"/><!--@todo change to 80-->
<property name="absoluteLineLimit" value="250"/><!--@todo change to 120-->
<property name="ignoreComments" value="true"/>
</properties>
</rule>
<rule ref="Generic.Files.ByteOrderMark"/>
<rule ref="Generic.CodeAnalysis.JumbledIncrementer"/><!-- detects the usage of one and the same incrementer into an outer and an inner -->
<rule ref="Generic.CodeAnalysis.UnconditionalIfStatement"/>
<rule ref="Generic.Files.LineEndings"><!-- Use Unix newlines -->
<properties>
<property name="eolChar" value="\n"/>
</properties>
</rule>
<rule ref="Generic.Metrics.CyclomaticComplexity">
<properties>
<property name="complexity" value="10" /><!-- the cyclomatic complexity above which this sniff will generate warnings -->
<property name="absoluteComplexity" value="15" /> <!-- the cyclomatic complexity above which this sniff will generate errors -->
</properties>
</rule>
<rule ref="Generic.Metrics.NestingLevel"><!--Checks the nesting level for methods.-->
<properties>
<property name="nestingLevel" value="10"/>
<property name="absoluteNestingLevel" value="30"/>
</properties>
</rule>
<rule ref="Generic.PHP.ForbiddenFunctions"><!-- Array values are specified by using a string representation of the array. -->
<properties>
<property name="forbiddenFunctions" type="array">
<element key="array_push" value="null"/>
<element key="chop" value="rtrim"/>
<element key="close" value="closedir"/>
<element key="create_function" value="null"/>
<element key="delete" value="unset"/>
<element key="doubleval" value="floatval"/>
<element key="fputs" value="fwrite"/>
<element key="ini_alter" value="ini_alter"/>
<element key="is_double" value="is_float"/>
<element key="is_integer" value="is_int"/>
<element key="is_long" value="is_int"/>
<element key="is_real" value="is_float"/>
<element key="is_writeable" value="is_writable"/>
<element key="join" value="implode"/>
<element key="key_exists" value="array_key_exists"/>
<element key="pos" value="current"/>
<element key="print" value="echo"/>
<element key="show_source" value="highlight_file"/>
<element key="sizeof" value="count"/>
<element key="strchr" value="strstr"/>
<element key="die" value="null"/>
<element key="exit" value="null"/>
<element key="env" value="config"/>
</property>
</properties>
<exclude-pattern>.config/*</exclude-pattern>
</rule>
<rule ref="Generic.PHP.CharacterBeforePHPOpeningTag"/><!-- Forbid any content before opening tag -->
<rule ref="Generic.PHP.DeprecatedFunctions"/><!-- Forbid deprecated functions -->
<rule ref="Generic.PHP.DisallowShortOpenTag"/><!-- Forbid short open tag -->
<rule ref="Generic.VersionControl.GitMergeConflict"/><!--Detects merge conflict artifacts left in files-->
<rule ref="Generic.WhiteSpace.IncrementDecrementSpacing"/><!--Ensures there is no space between the operator and the variable it applies to-->
<rule ref="Zend.Debug.CodeAnalyzer"/><!--Runs the Zend Code Analyzer (from Zend Studio) on the file.-->
<rule ref="Zend.Files.ClosingTag"/><!--Checks that the file does not end with a closing tag.-->
</ruleset>
З назваў правілаў і каментароў можна зразумець што мы правяраем. Правяраць можна амаль усе :)
@lptn можаш апісаць
Дзеянні: пры парушэнні правілаў білд будзе чырвоны, гэта будзе бачна ў ПР на ГХ
Ставіцца як dev-залежнасць composer'а, тб разам з composer install
@lptn Каб мы не чакали да НГ, прапаную з гэтага пачаць: PHP_CodeSniffer калі напрацуем неёкую практыку падумаем ці варта нешта іншае. Што ад мяне трэба каб яно запрацавала?)
@rizomaa есьць яшчэ прсацейшы стартавы варыянт -- https://github.styleci.io
Лагінься сваім акаўнтам гітхабавым, дадавай сервіс для репы дойкі
У наладах інтэграцыі уключай 1 і 2, для 3кі сам выбірай што хочаш)
на усе гэта есьць права толькі у адмінаў, так што я не магу 😛
А сення-заўтра канфінг дапалірую (пасля уключэння інтэграцыі створыцца файл канфіга ў репе -- я змагу змяняць яго)
Паехалі! 🚀 🚀 🚀
@rizomaa мы нічога не губляем, трэба спрабаваць. Не ведаю, наколькі моцна гэта паўплывае на якасць PHP коду, але горш не будзе. Шмат хто выкарыстоўвае тулзу, нам трэба таксама на зуб паспрабаваць
у выпадку з styleci.io ці scrutinizer-ci гэтыя тулзы нават самі будуць ствараць ПР ці каміты каб фіксіць стылі Прыклад каміта https://github.com/spatie/calendar-links/commit/861aaf44b359ef9dfe9b8fa7ab37c82f161422c2
Прыклад інфы якую выдае scrutinizer: https://scrutinizer-ci.com/g/spatie/calendar-links/inspections/09d3b5c8-66ae-44c8-abf9-fe77150446c1/log
на прыкладзе дойкі https://github.styleci.io/analyses/q17dKj?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link
@lptn Як ўвогуле ты лічыш, ці мае добры профіт выкарыстанне гэтай штукі на малых праектах з вялікай колькасцю распрацоўшчыкаў? Вядома што ў энтерпрайзах без такой стандартызацыі ўжо нікуды. Ці не будзе шмат смецце-рэквестаў?
@fr0zen @rizomaa
ну код-стайл штука не крытычная, але думаю ніхто не хоча чытаць кніжку дзе пастаянна мянябцца шрыфты, водступы, кульгае пунктуацыя, клясыка:
Але @fr0zen правільна зазначыў што гэта можна ствараць больш перашкоды чым карысці (асабліва на пачатку, асабліва падаванам (дарэчы мне гэты тэрмін падабаецца болей чым "лічынка")).
Сэрвісаў і наладаў і як след варыянтаў даволі шмат (калі пункт пачытацаа з плюса -- значыць усе тое ж самае што ў папярэднім пункце + штосці яшчэ):
+
пазначаць білд на GH чырвоным (па-ранейшаму можна мержыць)phpcs
як кампозер залежнасць)Я думаю есьць яшчэ розныя варыянты і іх камбірацыі
@lptn весткі з палёў гэта тулса не дае правяраць код на рэпах якія занодзяцца ў арганізацыйнай прасторы ва усялякім разе бясплатны акаунт. -- https://github.styleci.io Таму варта падумаць пра іншыя варыянты.
Ідзем па шэтаму варыянту PHP_CodeSniffer ? @Tyuba4 @fr0zen
@rizomaa @lptn здаецца гэта найлепшы варыянт тады, на мой погляд styleci - гэта ўжо залішне ў нашым выпадку пакуль.
@rizomaa
гэта тулса не дае правяраць код на рэпах якія занодзяцца ў арганізацыйнай прасторы ва усялякім разе бясплатны акаунт. -- github.styleci.io
Што ты маеш на ўвазе -- прыватныя рэпазіторыі?
@lptn не, падключаючы праз акаунт, паказала, толькі рэпазіторыі, якія былі створаны непасрэдна юзэрам, а не ў прасторы арганізацыі.
зы у нас усе рэпазіторыі адкрытыя
@rizomaa усе адно не дайшло да мяне, паразмаўляем сення на офісе пра гэта
@lptn адпішы калі ласка сюды, калі зразумееш, што там са StyleCI, нешта я таксама не зразумеў поўнасцю, чаму немагчыма яго дадаць
@rizomaa
@lptn не, падключаючы праз акаунт, паказала, толькі рэпазіторыі, якія былі створаны непасрэдна юзэрам, а не ў прасторы арганізацыі.
сення на свежую галаву я здаецца зразумеў Вашу шыфроўку, таварыш Ісаеў
Магчыма праблема у тым што інснуе 2 ааканта (@rizomaa і @falanster) і ты спрабаваў сэрвіс падключыць з таго акаўнта, якому не стае прапоў
Правер калі ласка яшчэ раз
@lptn прыйшоў запыт на нейкую сафціну scrutinizer-ci.com вы абмеркавалі ужо гэту прыладу, мне проста трэба клікнуць на адабрэнне?
@fr0zen @Tyuba4 @pro2s ?
@rizomaa
scrutinizer-ci.com
гэта з маей падычы. Учора былі праьлемы з тэставаннем міграцый, таму была задумка выкарыстаць гэты сэрвіс
Хацелася б яшчэ travis дадаць і наладзіць калі можна, магчыма яшчэ styleci, уче яны бясплатныя для опэн-сорса, з гякам чаму зразумеем што трэба а што на наш джэнкінс пераедзе
@lptn "з гякам " ?
Напішы ўвесь спіс, дзе мне трэба зрабіць аўтарызацыю, каб я пасля з-за кожнага тулса не бегаў.
Таксама усё гэта занясі неяк для зусім пачынаючых кшталту
ЗЫ доступ да scrutinizer-ci.com расшарыў
@lptn наконт https://github.styleci.io місія невыканальна))) вось хаця падаецца доступы ёсць, але па факту ніяк не дастучацца да рэпаў у арганізацыі (праверыў на абодвух кантактах)
@rizomaa донт воррі, сёння выканаем разам 😎👍🚀
Для кодынг стайла па JS можна таксама паглядзець airbnb : https://github.com/airbnb/javascript
@lptn @fr0zen @Tyuba4 што у нас яшчэ тут засталося абмеркаваць?
Я бачу што у нас падлкючаны 2 сэрвіса
Ці правільна разумею сітуаюцыю?
@rizomaa усе амаль правільна
Scrutinizer можна замяніць на StyleCI (code style) + SensioLabs Insight (code quality) + Coveralls (measuring code coverage by tests) але не зараз.
Яшчэ у нас даключана dev-залежнасць friendsofphp/php-cs-fixer
каб правіць код-стайл аўтаматычна на машыне распрацоўшчыка, але думаю пакуль ніхто не выкарыстоўвае гэтую тулзу
@lptn крута, на мой погляд камплект) Калі эфектыўна будзем выкарыстоўваць хоць гэтыя тулзы, тады ўжо можна будзе паглядзець іншыя.
@lptn Атрымліваецца Трэвіс і Скрыценайзер дублююць функцыю запуска аўтатэставання, можам прыбраць трэвіс? Або чаму ён патрэбны?
@rizomaa можам прыбраць трэвіс?
Можам, але б мне цікава было паглядзець як яны сябе паводзяць болей працяглы час і вырашыць пасля. Гэта таксама мой першы добры досвед з імі (трэвіс у мяне на адным малым праекце есьць але не лічыцца -- там амаль няма што тэсціць і там не PHP)
Трэвіс мае лепей дакументацыю і большае кам'юніці (і значна болей папулярны). Я б хацеў бы і надалей эксперыментаваць з падобнымі сэрвісамі (без шкоды праекту)
@lptn акей. прув прымаецца) адзінае распавядай нам нешта пра плюсы гэтай штукі часам.
@rizomaa добра! закрываем гэты такс?!
Што будзем выкарыстоўваць:
https://github.com/diglabby/doika/wiki/%D0%9A%D0%BE%D0%B4%D1%8B%D0%BD%D0%B3-%D1%81%D1%82%D0%B0%D0%BD%D0%B4%D0%B0%D1%80%D1%82
@fr0zen @Tyuba4 @pro2s вашы думкі?