Русский
Русский
English
Статистика
Реклама

Ruby

Перевод Кеширование гемов Ruby в GitHub Actions с использованием rubysetup-ruby или actionscache

17.04.2021 16:06:40 | Автор: admin
Как, загружая гемы Ruby из кеша в GitHub Actions, ускорить запуск сборки проекта в этой системе непрерывной интеграции (CI)? Если суметь подготовить к работе все зависимости Ruby on Rails (RoR)-проекта в кратчайшие сроки, это позволит сократить время, необходимое на запуск тестов для такого проекта. Тут-то нам и пригодится кеширование. Гемы Ruby, нужные в проекте, можно кешировать средствами GitHub Actions, благодаря чему их, при запуске CI-конвейера, можно будет загрузить гораздо быстрее, чем прежде. Существует два способа кеширования гемов Ruby, применимых при использовании CI GitHub Actions. Один из них предусматривает применение ruby/setup-ruby, а второй actions/cache.

  • actions/cache это популярное решение для кеширования гемов Ruby.
  • ruby/setup-ruby это решение, направленное на установку конкретной версии Ruby и на кеширование гемов Ruby с помощью бандлера. Фактически, речь идёт о двух возможностях одного экшена (того, что в терминологии GitHub Actions называется action).



Actions/cache кеширование зависимостей и больше ничего


Actions/cache это популярное решение, которое может быть использовано для размещения данных в кеше и для их извлечения из кеша при очередном запуске процесса сборки проекта в CI-системе. Часто этот экшен используют для RoR-проектов, в которых, кроме того, для управления версиями Ruby в GitHub Actions, используется экшн actions/setup-ruby.

Рассмотрим пример конфигурационного файла для организации кеширования в GitHub Actions, в котором используется actions/cache:

# .github/workflows/main.ymlname: Mainon: [push, pull_request]jobs:test:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v2- uses: actions/cache@v2with:path: vendor/bundlekey: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}restore-keys: |${{ runner.os }}-gems-- name: Bundle installenv:RAILS_ENV: testrun: |bundle config path vendor/bundlebundle install --jobs 4 --retry 3

Разберём основные особенности этого файла:

  • Нужно указать путь к директории, которую требуется кешировать. В нашем случае это vendor/bundle.
  • Кроме того, тут мы генерируем уникальный ключ (key) кеша, применяя при этом сведения о версии операционной системы и о файле Gemfile.lock. Изменение версии операционной системы или установка нового гема, из-за чего изменяется Gemfile.lock, приводит к генерированию нового значения key.
  • Далее, надо настроить бандлер так, чтобы он устанавливал бы все наши гемы Ruby в директорию vendor/bundle.
  • Тут можно использовать следующие параметры бандлера:

Если вам интересно взглянуть на полные YAML-файлы с настройками GitHub Actions для Rail-проектов вот, вот, вот и вот несколько наших статей на эту тему.

Ruby/setup-ruby установка Ruby и кеширование гемов


Выше мы упоминали о том, что в RoR-проектах часто используется actions/setup-ruby. Но экшен actions/setup-ruby был объявлен устаревшим. Вместо него в наши дни рекомендуется пользоваться экшеном ruby/setup-ruby. В нём, помимо прочих возможностей, имеется и возможность кеширования данных. Вот как выглядит конфигурационный файл, рассчитанный на использование ruby/setup-ruby:

# .github/workflows/main.ymlname: Mainon: [push, pull_request]jobs:test:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v2- uses: ruby/setup-ruby@v1with:# Не нужно при наличии файла.ruby-versionruby-version: 2.7# запускает 'bundle install' и автоматически кеширует установленные гемыbundler-cache: true# запуск RSpec-тестов- run: bundle exec rspec

Как видите, этот вариант организации кеширования гемов проще того, что мы описали в предыдущем разделе. И тут, кроме того, в нашем распоряжении оказывается система управления версиями Ruby. В сущности, если говорить о кешировании гемов, всё сводится к добавлению в конфигурационный файл строчки bundler-cache: true.

А вот что можно узнать, обратившись к документации по ruby/setup-ruby:

Можно, кроме того, кешировать гемы вручную, но поступать так не рекомендуется. При таком подходе, во-первых, увеличиваются объёмы конфигурационных файлов, а во-вторых вручную очень сложно настроить кеширование правильно. В расчёт нужно принять много всего, а это значит, что actions/cache никогда не бывает достаточно для кеширования гемов (например речь идёт о неполных ключах кеша, об удалении старых гемов при восстановлении данных с использованием ключа, отличающегося от текущего, о корректном хешировании .lock-файлов, сведения об изменениях которых не внесены в систему контроля версий, об учёте версии ОС, о принятии во внимание вопросов ABI-совместимости для ruby-head и так далее). Поэтому, пожалуйста, вместо ручной настройки кеширования, пользуйтесь опцией bundler-cache: true

Итоги


Мы рассмотрели два способа кеширования гемов Ruby в CI GitHub Actions. Надеемся, что тот, кто нуждается в ускорении сборки Ruby-проектов, нашёл здесь пищу для размышлений. Но, конечно, есть и другие способы ускорения CI-конвейеров, например параллельное выполнение тестов.

Пользуетесь ли вы GitHub Actions?

Подробнее..

Опыт сопряжения Java, JavaScript, Ruby и Python в одном проекте посредством GraalVM

25.12.2020 04:13:23 | Автор: admin
В прошлом месяце вышла стабильная LTS-версия многоязычной среды выполнения GraalVM 20.3.0 от корпорации Oracle и мне захотелось испробовать её для решения какой-нибудь интересной практической задачи. Для тех кто не в курсе, приведу краткое описание этой новой платформы. GraalVM позволяет использовать в едином окружении различные популярные языки программирования и обеспечивает их разностороннее взаимодействие в рамках некоторой общей среды выполнения.


Схематическое изображение архитектуры GraalVM из официальной документации.

Добавление новых языков в GraalVM осуществляется с помощью специального фреймворка Truffle, выполненного в виде библиотеки Java. Фреймворк предназначен для создания реализаций языков программирования в качестве интерпретаторов для самомодифицируемых абстрактных синтаксических деревьев (AST). При желании на его основе можно создать собственный язык, в официальных репозиториях GraalVM подробно рассмотрен пример реализации такого проекта под названием SimpleLanguage. Интерпретаторы, которые были написаны с использованием фреймворка Truffle, будут автоматически использовать GraalVM как JIT-компилятор непосредственно для самой реализации языка запускаемой на JVM-платформе и, соответственно, иметь возможность взаимодействия и двустороннего обмена данными в одном и том же пространстве памяти посредством специально разработанного протокола и программного интерфейса Polyglot API.

Платформа GraalVM вместе с исполняемой программой на смеси самых разных языков может быть представлена в виде автономного и самодостаточного исполняемого файла, либо работать поверх OpenJDK, Node.js или даже внутри Oracle Database.

На текущий момент сразу из коробки поддерживаются следующие языки программирования и технологии:

  • Java, Kotlin, Scala и другие языки JVM-платформы.
  • JavaScript вкупе с платформой Node.js и сопутствующим инструментарием.
  • C, C++, Rust и другие языки, которые могут быть скомпилированы в LLVM bitcode.

Помимо этого, с помощью встроенного пакетного менеджера в дистрибутив GraalVM можно добавить поддержку:

  • Python
  • Ruby
  • R
  • WebAssembly

Стоит заметить, что реализации языков и технологий из списка выше пока являются экспериментальными и в настоящее время не рекомендуются для производственного использования. Но для моей задачи это не столь принципиально.

Кроме того, посредством ahead-of-time (AOT) компиляции имеется возможность создавать автономные исполняемые файлы, называемые нативными образами, которые используют не классическую Java VM, а более специализированную Substrate VM, реализующую компактную среду выполнения. В итоге программы запускаются значительно быстрее и расходуют гораздо меньше оперативной памяти. Но при этом теряются некоторые преимущества just-in-time (JIT) компиляции, доступной на классических платформах. Для формирования подобных нативных образов в большинстве случаев требуются значительные ресурсы CPU и десятки гигабайт оперативной памяти, поэтому их создание лучше всего производить на каких-нибудь мощных сборочных серверах или рабочих станциях.

Корпорация Oracle в настоящее время позиционирует GraalVM как единую и идеальную платформу для создания различных микросервисов. При этом она уже имеет влияние на развитие классического OpenJDK. Например, встраиваемый движок JavaScript под названием Nashorn уже удалён из JDK 15, а в качестве его замены предлагается попробовать именно GraalVM. Неизвестно, как дальше будут развиваться события и будет ли GraalVM в будущем предлагаться в качестве рекомендуемой JVM-платформы по умолчанию, но судя по весьма активному развитию и маркетинговому продвижению в последнее время, Oracle настроен вполне серьёзно. Так что время покажет.

Для конечного использования предлагаются две редакции: бесплатная GraalVM Community и платная GraalVM Enterprise, отличия между которыми описаны на этой страничке официального сайта GraalVM. В основном они сводятся к обеспечению лучшей производительности, меньшего потребления оперативной памяти и официальной поддержке от специалистов корпорации Oracle в платной версии. В этой статье я буду ориентироваться только на возможности GraalVM Community, распространяемой свободно.

Теперь, когда у вас имеется общее представление о GraalVM, перейдём к более простым сущностям и обозначим практическую задачу, которая бы неплохо ложилась на плечи возможностей этой платформы.

Содержание:


1. Подсветка синтаксиса фрагментов кода на стороне сервера
2. Создание простейшего прототипа
3. Установка GraalVM и сопутствующих библиотек, запуск прототипа
4. От прототипа к готовому сервису
5. Подведём итоги

1. Подсветка синтаксиса фрагментов кода на стороне сервера


Когда-то давно мне стало жутко интересно, какими технологиями крупные хостеры IT-проектов вроде GitHub, Gitorious и Bitbucket подсвечивают наш исходный код на своих серверах. Проведя некоторое исследование, я пришёл к следующим результатам:

  1. Bitbucket использует библиотеку Pygments на языке программирования Python.
  2. GitHub использует Albino, обёртку библиотеки Pygments для языка программирования Ruby.
  3. Gitorious использует Makeup, тоже по своей сути обёртку библиотеки Pygments для Ruby.

Таким образом, я пришёл к выводу, что все крупные компании использовали и активно развивали библиотеку Pygments, благодаря чему этот проект сегодня поддерживает наибольшее количество языков программирования, которые он может подсвечивать.

Шло время и недавно я решил снова перепроверить чем же сейчас пользуются крупные хостеры исходного кода. За это время Gitorious был куплен GitLab'ом, который в итоге стал новым крупным и популярным сервисом. На сей раз получились такие результаты:

  1. Bitbucket как использовал библиотеку Pygments ранее, так и продолжает её использовать.
  2. GitHub отказался от использования обёртки Albino и перешёл на новую библиотеку Linguist. Однако, Linguist только детектирует язык в исходных файлах, а сам процесс подсветки выполняется какой-то другой библиотекой с закрытым исходным кодом, принадлежащей GitHub.
  3. GitLab применяет библиотеку Rouge на языке программирования Ruby, свободную от использования обёрток, но сохранившую некоторую совместимость с Pygments.

Видимо аудитория этих сервисов росла и использовать обёртки, запускающие Python из-под Ruby, стало несколько накладно и дорого. Если посмотреть на официальный сайт библиотеки Rouge, то одним из преимуществ там явно обозначают то, что теперь не требуется спавнить процессы интерпретатора Python, так как Rouge уже изначально написан на Ruby.

Особняком ещё стоит библиотека Highlight.js на языке программирования JavaScript, получившая огромную популярность и широкое распространение на самых разнообразных сайтах и сервисах. Её применяют в основном чтобы подсвечивать код на стороне клиента, но никто не запрещает использовать эту библиотеку и для подсветки на стороне сервера.

Если бы вы писали сайт на каком-либо языке JVM-стека вкупе с каким-нибудь популярным веб-фреймворком и перед вами бы стояла задача реализовать серверную подсветку синтаксиса различных фрагментов кода, то у вас бы испортилось настроение. К большому сожалению, JVM-платформа не обзавелась такими библиотеками, как Pygments, Rouge и Highlight.js, которые поддерживают сотни языков программирования. Все известные мне попытки портирования Pygments на Java на сегодня по сути заброшены и поэтому вам для выполнения этой задачи пришлось бы делать такие же обёртки над чужеродными библиотеками, которые были описаны выше.

Альтернатива видится в использовании Jython, JRuby или Nashorn, то бишь внешних реализаций Python, Ruby и JavaScript для платформы JVM. Но с ними не всё так гладко, как хотелось бы. Во-первых, размер вашего JAR-файла или WAR-файла и время его запуска существенно увеличится. Во-вторых, некоторые библиотеки предоставляют реализации версий языков далеко не первой свежести, например, Jython так и остался на Python 2, который уже устарел и новые версии Pygments на нём просто не работают. В-третьих, установка сторонних библиотек внутрь конечного файла для развёртывания в некоторых случаях далеко не так тривиальна и сопровождается грудой различных проблем и точек отказа.

В итоге все эти недостатки усложняют решение довольно тривиальной задачи. И именно в этот момент на сцену выходит GraalVM, который позволяет достаточно просто совмещать и склеивать между собой код и компоненты на различных языках программирования. Платформу JVM со своей кучей библиотек мы можем обогатить батарейками из экосистем других языков и использовать эксклюзивные библиотеки, альтернативы которых недоступны на нашей платформе.

Поэтому у меня появилась идея создать демонстрационный сервис с использованием GraalVM, который бы объединял в себе возможность работы различных библиотек для подсветки синтаксиса на стороне сервера. Он бы неплохо продемонстрировал готовность и удобство сопряжения нескольких языков программирования в рамках платформы GraalVM.

<< Перейти к содержанию

2. Создание простейшего прототипа


После обозначения практической задачи я приступил к созданию простейшего прототипа, который бы демонстрировал нужные мне возможности и подсвечивал скармливаемые ему фрагменты исходного кода. Я выбрал три библиотеки для использования в своём проекте:

  • Highlight.js на JavaScript, поддерживает ~190 языков программирования.
  • Rouge на Ruby, поддерживает ~205 языков программирования.
  • Pygments на Python, поддерживает ~500 языков программирования.

Их связующим звеном я решил выбрать Java 8, хотя с таким же успехом этот выбор мог быть сделан в сторону Kotlin, Scala или Java 11.

Итак, покопавшись по официальным документациям и гайдам этих библиотек и выяснив как именно подсвечивать с их помощью код, я написал такую вот простенькую консольную программку примерно из ста строк:

Исходный код Highlighter.java
// Highlighter.java, no comments, no checks.// $ javac Highlighter.java// $ jar -cvfe highlighter.jar Highlighter *.class// $ cat hello.py | java -jar highlighter.jar rouge pythonimport org.graalvm.polyglot.Context;import java.io.File;import java.io.FileNotFoundException;import java.util.Scanner;public class Highlighter {  private abstract class Highlight {    protected final Context polyglot =      Context.newBuilder("js", "python", "ruby").allowAllAccess(true).allowIO(true)        .build();    protected abstract String language();    protected abstract String renderHtml(String language, String rawCode);    protected String execute(String sourceCode) {      try {        return polyglot.eval(language(), sourceCode).asString();      } catch (RuntimeException re) { re.printStackTrace(); }      return sourceCode;    }    protected void importValue(String name, String value) {      try {        polyglot.getBindings(language()).putMember(name, value);      } catch (RuntimeException re) { re.printStackTrace(); }    }  }  private class Hjs extends Highlight {    @Override    protected String language() { return "js"; }    @Override    public String renderHtml(String language, String rawCode) {      importValue("source", rawCode);      String hjs = "";      try {        hjs = new Scanner(new File("highlight.min.js")).useDelimiter("\\A").next();      } catch (FileNotFoundException fnfe) { fnfe.printStackTrace(); }      final String renderLanguageSnippet =        hjs + "\n" +        "hljs.highlight('" + language + "', String(source)).value";      return execute(renderLanguageSnippet);    }  }  private class Rouge extends Highlight {    @Override    protected String language() { return "ruby"; }    @Override    public String renderHtml(String language, String rawCode) {      importValue("$source", rawCode);      final String renderLanguageSnippet =        "require 'rouge'" + "\n" +        "formatter = Rouge::Formatters::HTML.new" + "\n" +        "lexer = Rouge::Lexer::find('" + language + "')" + "\n" +        "formatter.format(lexer.lex($source.to_str))";      return execute(renderLanguageSnippet);    }  }  private class Pygments extends Highlight {    @Override    protected String language() { return "python"; }    @Override    public String renderHtml(String language, String rawCode) {      importValue("source", rawCode);      final String renderLanguageSnippet =        "import site" + "\n" +        "from pygments import highlight" + "\n" +        "from pygments.lexers import get_lexer_by_name" + "\n" +        "from pygments.formatters import HtmlFormatter" + "\n" +        "formatter = HtmlFormatter(nowrap=True)" + "\n" +        "lexer = get_lexer_by_name('" + language + "')" + "\n" +        "highlight(source, lexer, formatter)";      return execute(renderLanguageSnippet);    }  }  public String highlight(String library, String language, String code) {    switch (library) {      default:      case "hjs": return new Hjs().renderHtml(language, code);      case "rouge": return new Rouge().renderHtml(language, code);      case "pygments": return new Pygments().renderHtml(language, code);    }  }  public static void main(String[] args) {    Scanner scanner = new Scanner(System.in).useDelimiter("\\A");    if (scanner.hasNext()) {      String code = scanner.next();      if (!code.isEmpty()) {        System.out.println(new Highlighter().highlight(args[0], args[1], code));      }    }  }}


Как видно, в этом фрагменте смешаны четыре разных языка программирования. Утилита принимает на вход stdin в виде текста исходного файла, передаваемые аргументы определяют используемую библиотеку для подсветки и язык фрагмента, затем подсвечивается код и выводится готовый HTML на stdout терминала. Звучит просто и понятно, но для компиляции и запуска этой программы требуется установить GraalVM и сопутствующие пакеты нужных библиотек.

<< Перейти к содержанию

3. Установка GraalVM и сопутствующих библиотек, запуск прототипа


Для экспериментов я выбрал сервер с ванильным CentOS 7, на который без каких либо проблем разворачивается дистрибутив GraalVM.

Рецепт установки платформы у меня получился следующим:

curl -LOJ https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.3.0/graalvm-ce-java8-linux-amd64-20.3.0.tar.gz# curl -LOJ https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.3.0/graalvm-ce-java11-linux-amd64-20.3.0.tar.gzcd /opt/sudo mkdir graalvmsudo chown `whoami`:`whoami` graalvmcd /opt/graalvm/tar -xvzf ~/graalvm-ce-java8-linux-amd64-20.3.0.tar.gzrm ~/graalvm-ce-java8-linux-amd64-20.3.0.tar.gz

Стоит заметить, что в настоящий момент времени GraalVM 20.3.0 поддерживает только LTS-версии Java 8 и Java 11, поэтому при создании проектов на этой платформе следует помнить об этом и пока забыть про вкусности новых версий Java и JVM-платформ. Итак, после выполнения этих команд в директории /opt/ у вас будет развёрнут GraalVM, но в него ещё нужно добавить поддержку дополнительных компонентов.

Рецепт установки языков программирования в GraalVM и требуемых библиотек подсветки кода таков:

export GRAALVM_HOME=/opt/graalvm/graalvm-ce-java8-20.3.0export JAVA_HOME=$GRAALVM_HOMEexport PATH=$GRAALVM_HOME/bin:$PATHgu install pythongu install ruby# /opt/graalvm/graalvm-ce-java8-20.3.0/jre/languages/ruby/lib/truffle/post_install_hook.shgraalpython -m ginstall install setuptoolscurl -LOJ https://github.com/pygments/pygments/archive/2.7.3.tar.gztar -xvzf pygments-2.7.3.tar.gzcd pygments-2.7.3/graalpython setup.py install --usercd ..rm -Rf pygments-2.7.3/ pygments-2.7.3.tar.gzgem install rougecurl -LOJ https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.4.1/highlight.min.js

Перед использованием инструментов из дистрибутива GraalVM следует подгрузить нужные нам переменные окружения. При желании их подгрузку можно сделать при старте сервера для определённого пользователя или для всей системы сразу. Утилита gu является неким пакетным менеджером и предназначена для установки, удаления и обновления компонентов GraalVM. Нативные образы я не стал пересобирать, поскольку это достаточно долгий и ресурсоёмкий процесс. Он потребуется только в том случае, если вы захотите сформировать автономный исполняемый файл вашей программы. Пост-установочный скрипт после процесса инсталляции поддержки языка Ruby я тоже не стал выполнять, поскольку библиотека Rouge никак не взаимодействует с сетью и интернетом. Для использования Highlight.js достаточно просто скачать минифицированный файл библиотеки, платформа Node.js здесь никак не задействуется.

Ситуацию с Pygments следует разобрать немного подробнее. В случае с Python, его менеджер пакетов pip доступен лишь в виртуальном окружении virtualenv и для упрощения я решил отказаться от изоляции и использовать способ установки библиотеки Pygments посредством инструмента setuptools и загрузки исходников. Стоит заметить, что постоянные холодные вызовы интерпретатора graalpython довольно ресурсоёмки и на выполнение этих команд потребуется некоторое время, однако после завершения процесса требуемая библиотека будет сразу нам доступна. Кстати, установку и удаление библиотек можно производить и обычным системным pip, который входит в поставку дистрибутива операционной системы. В моём случае использовался CentOS 7, где по умолчанию установлен лишь Python 2, а усложнять инструкцию установкой современного Python 3 мне не хотелось, тем более раз для GraalVM доступна его собственная реализация Python.

Рецепт сборки и запуска консольной программки-прототипа из предыдущего раздела:

export GRAALVM_HOME=/opt/graalvm/graalvm-ce-java8-20.3.0export JAVA_HOME=$GRAALVM_HOMEexport PATH=$GRAALVM_HOME/bin:$PATHjavac Highlighter.javajar -cvfe highlighter.jar Highlighter *.classcat hello.py#!/usr/bin/env pythonprint("Hello, World!")cat hello.py | java -jar highlighter.jar hjs python<span class="hljs-comment">#!/usr/bin/env python</span>print(<span class="hljs-string">"Hello, World!"</span>)cat hello.py | java -jar highlighter.jar rouge python<span class="c1">#!/usr/bin/env python</span><span class="k">print</span><span class="p">(</span><span class="s">"Hello, World!"</span><span class="p">)</span>cat hello.py | java -jar highlighter.jar pygments python<span class="ch">#!/usr/bin/env python</span><span class="nb">print</span><span class="p">(</span><span class="s2">"Hello, World!"</span><span class="p">)</span>

Как видно, всё прекрасно работает! Только не стоит забывать что для Highlight.js требуется положить файлик highlight.min.js рядом с нашей программкой. Использование внутренних ресурсов JAR-файла усложнило бы этот пример, поэтому я решил выбрать именно такой простой способ.

При желании все эти рецепты можно аккуратно завернуть в контейнеры вроде Docker или Podman, кому что нравится. Официальные образы от Oracle с GraalVM вполне себе доступны на том же Docker Hub.

<< Перейти к содержанию

4. От прототипа к готовому сервису


Имея на руках готовый прототип я решил превратить его в простенький демонстрационный сервис, представляющий собой веб-сайт с возможностью сохранения фрагментов исходного кода и выбора библиотеки для запекания подсветки синтаксиса в HTML.

Для реализации этой задачи я взял известный в Java-экосистеме фреймворк Spring, который в настоящее время без особых нареканий работает на GraalVM и даже обозначен на официальном сайте платформы. В качестве проксирующего веб-сервера был выбран Nginx, а для базы данных я использовал PostgreSQL. Для front-end'а мной был выбран несколько устаревший в современном мире, но всё ещё использующийся подход с рендерингом HTML на стороне сервера при помощи шаблонизатора Thymeleaf. В общем, я остановился на одном из самых популярных фреймворков и его сопутствующих инструментах вроде Spring Boot 2.4.1 или Spring Data JPA, которые сильно ускоряют разработку и сокращают рутинный код. Но при этом с GraalVM вы вольны выбирать другие и более легковесные Java-фреймворки, которые больше ориентированы на создание микросервисов. Официальный сайт рекомендует посмотреть на Micronaut и Quarkus, преимущество которых состоит в возможности более лёгкого создания нативных образов из-за меньшего потребления оперативной памяти при их формировании.

Отправку фрагментов кода на веб-сайт я стилизовал под популярный ныне на многих IT-ресурсах язык разметки Markdown:

```python#!/usr/bin/env pythonprint("Hello, World!")```

В той строке, где вы определяете язык программирования, можно разместить дополнительные опции, например, номер или диапазон строк кода, на которых нужно сфокусировать особое внимание или параметр, отключающий колонку в виде линейки с номерами строк.


Сервис Code Polyglot с тёмной темой.

Тёмная тема в хакерском стиле создавалась под впечатлением от интерфейса старых мобильных телефонов Motorola на P2K, вроде E398, которые оставили в моей юности приятные и тёплые воспоминания. Оболочка называлась Techno и выглядела следующим образом:

Скриншоты с Motorola E398

Оболочка Techno на Motorola E398.


Надеюсь, вы не станете меня проклинать за моё извращённое чувство прекрасного и безумные умения в вёрстке, ибо я не имею должного опыта веб-разработки. Но если всё-таки станете, то на сайте доступна более нейтральная светлая тема, простой дизайн которой я позаимствовал с похожего сервиса по обмену фрагментами кода: paste.org.ru:


Сервис Code Polyglot со светлой темой.

Итак, прототип потихоньку начал обрастать различной функциональностью для выхода в мир, которую описывать здесь особого смысла я не вижу, всё же эта статья не о том, как быстро сделать подобный веб-сайт с помощью фреймворка Spring Boot. Расскажу лучше о тех проблемах, с которыми мне пришлось столкнуться и которые я попытался преодолеть.

Фрагменты кода на веб-сайте я решил отображать в таблицах, как это делает GitHub, но оказалось что с этим не всё так гладко. Библиотека Highlight.js, например, позиционируется разработчиком как максимально простая и поэтому принципиально не умеет оборачивать фрагменты кода в табличные строки. С другой стороны, генерируемые таблицы Pygments и Rouge слабо совместимы между собой. Поэтому мне пришлось выключать табличное отображение вообще, просто подсвечивать фрагмент кода выбранной библиотекой и построчно оборачивать его в таблицу уже на стороне Java.

Подобный подход привёл ещё к одной проблеме, которую проще всего продемонстрировать на следующем примере:

/* * Многострочный комментарий! */<span class="comment">/* * Многострочный комментарий! */</span><tr><td>1</td><td><span class="comment">/*</td></tr><tr><td>2</td><td> * Многострочный комментарий!</td></tr><tr><td>3</td><td> */</span></td></tr>

Библиотеки Highlight.js и Rouge не оборачивали каждую линию кода в автономные HTML-теги, поэтому при генерации таблицы они разрывались и подсветка кода работала некорректно. Я постарался исправить эту проблему с помощью обычного стека из стандартной библиотеки структур данных в Java. Упрощённый алгоритм выглядит примерно следующим образом: проходим по строке, когда детектим открывающий тег подсветки, то кладём его на стек, а когда детектим закрывающий, то просто убираем последний элемент со стека. Если к концу строки стек не оказывается пустым, то закрываем все открытые теги в текущей строке, а в начале следующей строки открываем те теги, которые остались у нас на стеке. Решение получилось немного топорным, но вполне себе рабочим.

<tr><td>1</td><td><span class="comment">/*</span></td></tr><tr><td>2</td><td><span class="comment"> * Многострочный комментарий!</span></td></tr><tr><td>3</td><td><span class="comment"> */</span></td></tr>

В итоге после обработки подобным алгоритмом пример выше приводится к удобоваримому виду, подсветка начинает работать, а сгенерированный HTML становится корректным. К слову, некоторые похожие сервисы по обмену фрагментов кода имеют схожую проблему, которая так и не решена, поэтому я отписался их владельцам и описал им этот метод исправления.

С последним препятствием я столкнулся уже на слабенькой VPS от Oracle. Оказалось, что экспериментальная поддержка языка программирования Python в GraalVM вкупе с библиотекой Pygments не влазят в мои скромные 1 GB оперативной памяти на виртуальном сервере. Да и сама реализация Python показалась мне ещё несколько сыроватой и медленной, например, иногда в процессе подсветки кода кое-где теряются пробелы и переводы строк, хотя в классическом системном Python всё работает отлично. Поэтому мне пришлось поменять сервер на другой, с более мощным железом и 4 GB RAM на борту.

<< Перейти к содержанию

5. Подведём итоги


В целом, я получил положительный опыт работы с GraalVM и его инструментарием, достаточно быстро смог решить поставленную перед собой задачу сопряжения полезных батареек из экосистем разных языков программирования в рамках одной общей платформы. Из недостатков могу лишь отметить несколько сыроватую и медленную реализацию Python, наверное как раз по этой причине его поддержка до сих пор является в большей степени экспериментальной. А вот с реализацией Ruby, которая тоже является экспериментальной, и с поддержкой JavaScript, который сразу входит в стандартную поставку GraalVM, я не заметил каких-либо проблем, да и работают они вполне себе быстро.

Все свои наработки, рецепты развёртывания и сборки, весь исходный код я разместил в репозитории на GitHub:

github.com/EXL/CodePolyglot

Сам демонстрационный сервис можно потыкать палочкой по этой ссылке:

code.exlmoto.ru

Примечание: из-за наплыва посетителей после публикации статьи может случиться так, что сервер станет недоступным, всё-таки там совсем слабенький канал. Так что прошу меня заранее простить за технические неполадки. Если вам интересен этот проект, то на своих рабочих станциях с кучей оперативной памяти вы можете легко его развернуть и поиграться.

Отдельно стоит поговорить про несколько неудачный опыт создания нативных образов. В тех случаях, когда используются и смешиваются несколько языков программирования, их формирование превращается в пытку. Забегая вперёд сообщу, что для маленького прототипа, который был описан в статье выше, в его нативном образе удалось заставить работать лишь библиотеку Highlight.js вкупе с JavaScript. При попытке добавления других языков программирования автономный исполняемый файл раздувался совсем уж до неприличных размеров в 500 MB и отказывался видеть не только сторонние батарейки, но и стандартные библиотеки Python и Ruby. Копание в переменных окружения и флажках сборки исправило пару проблем, но удобоваримого результата у меня так и не получилось. К тому же, нельзя не упомянуть продолжительное время AOT-компиляции (порядка 10-15 минут), значительное потребление RAM (порядка 20 GB) и ресурсов CPU в процессе сборки.

Если с такими проблемами создания нативного образа я столкнулся на простейшем прототипе, то что уж говорить о моих тщетных попытках преобразования всего сервиса в автономный исполняемый файл. В экосистеме Spring совсем недавно появился экспериментальный проект Spring Native 0.8.5, позволяющий формировать нативные образы сервисов использующих библиотеки Spring. Оригинальные версии Java-библиотек, которые не работают должным образом под Substrate VM, вырезаются или подменяются на патченные, например, в их число входит встраиваемый веб-сервер Tomcat. При этом при сборке нужно ещё сделать некоторые ухищрения и телодвижения вроде правильной настройки Hibernate, подгрузки конфигурационного JSON-файла с описанием некоторой рефлексии и расстановки обязательных флажков для утилиты native-image, которая формирует нативные образы. В качестве системы сборки в Spring Native используется Maven, так как поддержка Gradle находится ещё на начальном этапе развития. В общем, всё выглядит слишком экспериментальным и сырым. В итоге мне удалось добиться корректной сборки нативного образа, но без поддержки каких-либо сторонних языков программирования кроме тех, что доступны на JVM-платформе. При попытках их добавления процесс зависал и ничего полезного так и не происходило в течении 30 минут, после чего я просто останавливал задачу. Далее, при запуске исполняемого файла сыпались ошибки встраиваемого языка SpEL в шаблонах Thymeleaf и я пока отступил от решения этой проблемы.

Для себя я сделал вывод, что AOT-компиляция может быть интересной в том случае, если в проекте не задействован Polyglot API, не смешано множество языков программирования и сервис используется как back-end к чему либо. Вместо Spring для AOT пока лучше использовать более легковесный Quarkus, который как раз опирается на возможность формирования нативных образов в GraalVM. А для проектов, которые используют множество языков и серверные HTML-шаблонизаторы, неплохо работает традиционный подход с JIT-компиляцией и запуском на JVM.

Версии реализаций языков программирования и технологий доступных на платформе GraalVM 20.3.0:

  • Java 8 (1.8.0_272), Java 11 (11.0.9)
  • JavaScript ES2020 (ES11)
  • Ruby 2.6.6
  • Python 3.8.5
  • R 3.6.1
  • LLVM 10.0.0

Некоторые полезные ссылки:

  1. Платформа GraalVM.
  2. Документация GraalVM.
  3. Фреймворк Spring.
  4. Библиотека Pygments.
  5. Библиотека Rouge.
  6. Библиотека Highlight.js.

Надеюсь, мой опыт и эта статья будут полезны тем, кто когда-нибудь заинтересуется платформой GraalVM и её возможностями по использованию множества языков программирования в одном окружении.

P.S. Искренне поздравляю посетителей ресурса Хабр с наступающим 2021 годом, желаю вам ребята исполнения всех ваших желаний и крепкого сибирского здоровья!

P.P.S. Благодарю пользователя zorgrhrd за поддержку! Без него эта статья никогда бы не появилась на свет.

<< Перейти к содержанию
Подробнее..
Категории: Javascript , Ruby , Python , Java , Oracle , Spring , Graalvm , Spring boot , Graal , Polyglot

Насколько вам наплевать на фичи последней версии языка?

24.05.2021 00:09:13 | Автор: admin

Многие на собеседованиях любят гонять по последним фичам языка. У меня это всегда вызывало недоумение, во всяком случае в сфере веб-разработки. На фронтенде ты смотришь CanIUse(или сношаешься с полифиллами), а на бэкенде ты смотришь на шаблоны vps/vds, которые предоставляют хостеры и прикидываешь когда же в них появятся нужные тебе версии языка. И я абсолютно не против развертывания среды выполнения нужной версии, которая будет отличаться от системной, но давайте будем честными с самими собой. Какой процент из вас ориентируется на последнюю доступную версию языка, а не на то что будет на в ближайшие пару лет дано в ощущениях, браузерах и датацентрах. Внимание опрос!

Подробнее..
Категории: Javascript , Ruby , Python , Php , Хостинг , Vps , Perl , Vds

Опрос Насколько вам наплевать на фичи последней версии языка?

24.05.2021 02:21:51 | Автор: admin

Многие на собеседованиях любят гонять по последним фичам языка. У меня это всегда вызывало недоумение, во всяком случае в сфере веб-разработки. На фронтенде ты смотришь CanIUse (или сношаешься с полифиллами), а на бэкенде ты смотришь на шаблоны vps/vds, которые предоставляют хостеры и прикидываешь когда же в них появятся нужные тебе версии языка. И я абсолютно не против развертывания среды выполнения нужной версии, которая будет отличаться от системной, но давайте будем честными с самими собой. Какой процент из вас ориентируется на последнюю доступную версию языка, а не на то что будет на в ближайшие пару лет дано в ощущениях, браузерах и датацентрах. Внимание опрос!

Подробнее..
Категории: Javascript , Ruby , Python , Php , Хостинг , Vps , Perl , Vds

3 видео для мобильного разработчика

28.12.2020 20:06:16 | Автор: admin
Задумались писать собственный фреймворк для iOS-приложения? Послушайте доклад нашего разработчика о создании библиотеки для отрисовки графиков в сервисе ЮKassa. Тут про концепт, математические алгоритмы, реализацию взаимодействия с графиками и анимацию.

Второй спич посвящен автоматизации тестирования продуктовых метрик в UI-тестах на iOS. Расскажем про то, как тестировали метрику раньше, и во что захотели всё это превратить. Добавьте к этому челлендж-факторы в виде ограничений платформы и бизнес-процессов, и вот отличный повод поделиться с сообществом полезными мыслями.

Ну и напоследок. У вас бывает такое, что раз за разом на проектах встречаются одни и те же ошибки? И дело тут может быть вовсе не в человеческом факторе, а, например, в плохо понятых принципах программирования. Если ответили да, то смотрите 3 видео, в котором спикер разберёт причины возникновения таких ошибок и как можно их избежать.



Библиотека для рисования графиков


Сергей Тоболин, iOS-разработчик
Чем может быть полезна библиотека и как использовать её повторно снаружи

1:04 Об аналитике в ЮKassa
1:46 Концепт дизайна
2:46 Выбор фреймворка
4:08 SсiChart
4:54 SpriteKit
5:25 Первый этап: разработка алгоритма
7:53 Типы экстремумов графика
10:23 Второй этап: отрисовка графика и взаимодействие с ним
12:20 Третий этап: анимация
16:17 Результаты




Инструмент для тестирования продуктовых метрик в UI-тестах


Станислав Зеликсон, iOS-разработчик
Стас расскажет, как простая с виду задача превратилась в интересный челлендж из-за ограничений платформы и наших бизнес-процессов.

1:27 С чего все начиналось
2:39 Как мы тестировали метрику раньше?
3:16 Требования
4:40 Способы реализации
9:00 Реализация отправки метрик
22:10 Первый запуск на CI
25:53 Множество симуляторов
29:40 Интеграция в тесты
40:36 Ссылка на материалы на GitHub




Код, который работает, но плохо


Нина Семкина, старший Android-разработчик
Нина сделает обзор досадных ошибок на Kotlin, которые раз за разом появляются в проектах. А самое главное разберёт причины их возникновения.

1:47 Проблемы паттерна Singleton. Пример с инициализацией параметров
5:29 Context в Singleton. Пример с потокобезопасным получением
9:19 Нарушения принципа единой ответветственности (Single responsibility principle)
13:38 Нарушения принципа подстановки Барбары Лисков
19:24 Конкурирующие состояния View
25:24 Общие итоги




Все доклады с большой ИТ-конференции ЮMoneyDay.

Подробнее..

Пошаговый туториал по написанию Telegram бота на Ruby (native)

30.12.2020 02:06:40 | Автор: admin

Приветики-омлетики, как-то недавно у меня появилась идея написать Telegram бота наRubyна специфическую тематику, в двух словах этот бот должен был поднимать онлайн чатах по средством развлекательных событий которые этим же ботом вбрасывались в чат в рандомное время с рандомным контекстом.

И вот пока я занимался написанием этого бота то познакомился с библиотекой (gem) telegram-bot-ruby, научился её использовать вместе с gem 'sqlite3-ruby и кроме того проникся многими возможностями Telegram ботов чем и хочу поделится с уважаемыми читателями этого форума, внести вклад так сказать.

Много людей хочет писать Telegram боты, ведь это весело и просто.

На момент же написания своего бота я столкнулся с тем что сложно было найти хороший материал на тему Ruby бота для Telegram. Под хорошим я подразумеваю такой, где рассказывается про функционал изящные и красивый, такой, какой он есть в Telegram API.

Сразу кидаю ссылку на свой репозиторий по этому посту:here,
Ибо во время тестирования были баги, которые я мог сюда и не перенести, вдруг чего смотреть прямо в репозиторий.

В следствии прочтения этого топика, я надеюсь читатель сможет улучшить своего уже написаного бота, или прямо сейчас скачать Ruby, Telegram и создать что-то новое и прекрасное. Ведь как уже было сказано в Декларации Киберпространства:

В нашем же мире все, что способен создать человеческий ум, может репродуцироваться и распространяться до бесконечности безо всякой платы. Для глобальной передачи мысли ваши заводы больше не требуются.

  • Предлагаю начать :

    У меня версия Ruby - 2.7.2, но не исключено что всё будет работать и с более ранними/поздними версиями.

  • Примерная структура приложения будет выглядеть вот так

  • Первым делом создадим Gemfile - основной держатель зависимостей для сторонних gems в Ruby.

  • ФайлGemfile:

    source 'https://rubygems.org'gem 'json'gem 'net-http-persistent', '~> 2.9'gem 'sqlite3'#gem для БДgem 'telegram-bot-ruby'#основной гем для создания соеденения с Telegram ботом
    

    Сохраняем файл и выполняем в терминале операцию

    bundle install
    

    Увидим успешную установку всех гемов (ну это же прелесть Ruby) и на этом сGemfileбудет покончено.

  • Если вы (как и я) лабораторная крыса GitHuba, то создаем .gitignoreдля нашего репозитория, у меня прописан классический для продуктов JetBrains файл:

  • Файл .gitignore:

    /.idea/
    
  • Далее создадим первый класс в корне проекта, называем как хотим этот класс будет выступать в роли инициализатора, в моем случае этоFishSocket:

  • файлFishSocket.rb:

    require 'telegram/bot'require './library/mac-shake'require './library/database'require './modules/listener'require './modules/security'require './modules/standart_messages'require './modules/response'Entry point classclass FishSocket  include Database  def initialize    super    # Initialize BD    Database.setup    # Establishing webhook via @gem telegram/bot, using API-KEY    Telegram::Bot::Client.run(TelegramOrientedInfo::APIKEY) do |bot|      # Start time variable, for exclude message what was sends before bot starts      startbottime = Time.now.toi      # Active socket listener      bot.listen do |message|        # Processing the new income message    #if that message sent after bot run.        Listener.catchnewmessage(message,bot) if Listener::Security.messageisnew(startbottime,message)      end    end  endendBot startFishSocket.new
    

    Как видим в этот файле упомянуты сразу 5 различных файлов :Gem telegram/bot,Модули mac-shake, listener, security, database.

  • Поэтому предлагаю сразу их создать и показать что к чему:

  • Файлmac-shake.rb:

    # frozenstringliteral: truemodule TelegramOrientedInfoAPIKEY = ''end
    
  • Как видим в этом файле используется API-KEY для связи с нашим ботом, предлагаю сразу его получить, для этого обратимся к боту от Telegram API :@BotFather

    API-Key который нам вернул бот, следует вставить в константу API-Key, упомянутую ранее.

  • Файлsecurity.rb:

    class FishSocket  module Listener    # Module for checks    module Security      def messageisnew(starttime, message)        messagetime = (defined? message.date) ? message.date : message.message.date        messagetime.toi > starttime      end  def message_too_far    message_date = (defined? Listener.message.date) ? Listener.message.date : Listener.message.message.date    message_delay = Time.now.to_i - message_date.to_i    # if message delay less then 5 min then processing message, else ignore    message_delay > (5 * 60)  end  module_function :message_is_new, :message_too_farendendend
    

    В этом файле происходит две проверки : на то, что бы сообщение было отпарвлено после старта бота (не обрабатывать команды которые были отпраленны в прошлой сессии). И вторая проверка, что бы не обрабатывать сообщение которым больше 5 минут (вдруг вы добавите очередь, и таким образом мы ограничиваем её длину)

  • Файлlistener.rb:

    class FishSocket  # Sorting new message module  module Listener    attr_accessor :message, :botdef catch_new_message(message,bot)  self.message = message  self.bot = bot  return false if Security.message_too_far  case self.message  when Telegram::Bot::Types::CallbackQuery    CallbackMessages.process  when Telegram::Bot::Types::Message    StandartMessages.process  endendmodule_function(  :catch_new_message,  :message,  :message=,  :bot,  :bot=)endend
    

    В этом файле мы делим сообщения на две группы, являются ли они ответом на callback функцию, или они обычные.Сейчас проясню что такое callback сообщение в телеграме.Telegram API версии 2.0 предоставляет достаточно обширную поддержку InlineMessages. Это такие сообщение, которые в себе содержает UI элементы взаемодействия с пользователем, я в своем боте использоватInlineKeyboardMarkupэто кнопки, после нажатия на которые сообщение которые прийдет на бота, будет типаCallbackMessage, и текст сообщение будет равен тому, который мы указали в атрибут кнопки, при отправке запроса на Telegram API. Позже мы ешё вернёмся к этому принципу.

  • ФайлDatabase.rb

    # This module assigned to all database operationsmodule Database  attr_accessor :dbrequire 'sqlite3'  # This module assigned to create table action  module Create    def steamaccountlist      Database.db.execute <<-SQL    CREATE TABLE steamaccountlist (    accesses VARCHAR (128),    used INTEGER (1))      SQL      true    rescue SQLite3::SQLException      false    end    modulefunction(        :steamaccount_list    )  enddef setup    # Initializing database file    self.db = SQLite3::Database.open 'autosteam.db'    # Try to get custom table, if table not exists - create this one    unless gettable('steamaccountlist')      Create.steamaccount_list    end  end# Get all from the selected table  # @var tablename  def gettable(tablename)    db.execute <<-SQL    Select * from #{tablename}    SQL  rescue SQLite3::SQLException    false  endmodulefunction(    :gettable,    :setup,    :db,    :db=  )end
    

    В этом файле просто происходит инициализация бд и проверка/создание таблиц которые мы хотим использовать.

  • Можем попытатся запустить нашего бота, посредством выполнения файлаfishsocket.rbЕсли мы всё сделали правильно, то не должны увидеть никакого сообщения о завершеной работе, так как происходит Active Socket прослушывания ответа от Telegram API.Мы по-сути реестрируем наш локальный сервер прикрепляя его к Webhook от Telegram API, на который будут приходить сообщения о любых изменениях.

  • Попробуем добавить примитивный ответ на какое-то сообщение в боте

    Создадим файлstandartmessages.rb, модуль который будет обрабатывать Стандартные (текстовые) сообщение нашего бота. Как помним сообщение бывают двух типов : Standart и Callback.

    Файлstandartmessages.rb:

    class FishSocket  module Listener    # This module assigned to processing all standart messages    module StandartMessages      def process        case Listener.message.text        when '/getaccount'          Response.stdmessage 'Very sorry, нету аккаунтов на данный момент'        else          Response.stdmessage 'Первый раз такое слышу, попробуй другой текст'        end      end  module_function(      :process  )endendend
    

    В этом примере мы обрабатываем примитивный запрос /getaccount, и возвращаем ответ что на данный момент аккаунтов нету, ведь их дейстительно ещё нету.

  • Ах да, ответ мы отправляем с помощью модуляResponse, который прямо сейчас и создадим

    Файлresponse.rb

    class FishSocket  module Listener    # This module assigned to responses from bot    module Response      def stdmessage(message, chatid = false )        chat = (defined?Listener.message.chat.id) ? Listener.message.chat.id : Listener.message.message.chat.id        chat = chatid if chatid        Listener.bot.api.sendmessage(          parsemode: 'html',          chatid: chat,          text: message        )      end  module_function(    :std_message  )endendend
    

    В этом файле мы обращаемся к API Telegrama согласно документации, но уже используя gem telegram-ruby, а именно его функциюapi.sendmessage. Все атрибуты можно посмотреть в Telegram API и поигратся с ними, скажу только лишь что этот метод может отправлять только обычные сообщения.

  • Запускаем бота и тестируем две команды :(Бота можно найти по ссылке которую вам вернул BotFather, вместе с API ключем.

    Привет
    
    /getaccount
    

    Как видим всё отработала так как мы и хотели.

  • Предлагаю увеличить обороты и сразу создать Inline кнопку, добавить реакцию на неё, добавить метод для отправки сообщения с Inline кнопкой.

  • Создадим подпапку assets/ в ней модуль inlinebutton.Файлinlinebutton.rb:

    class FishSocket  # This module assigned to creating InlineKeyboardButton  module InlineButton    GETACCOUNT = Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Получить account', callbackdata: 'getaccount')  endend
    

    Сдесь мы обращаемся всё к тому жеtelegram-ruby-gemчто бы создать обьект типа InlineKeyboardButton.

  • Разширим наш файлReponseновыми методоми :

    def inlinemessage(message, inlinemarkup,editless = false, chatid = false)  chat = (defined?Listener.message.chat.id) ? Listener.message.chat.id : Listener.message.message.chat.id  chat = chatid if chatid  Listener.bot.api.sendmessage(    chatid: chat,    parsemode: 'html',    text: message,    replymarkup: inlinemarkup)enddef generateinlinemarkup(kb, force = false)  Telegram::Bot::Types::InlineKeyboardMarkup.new(    inlinekeyboard: kb  )end
    

    Не стоит забывать выносить новые методы в modulefunction() :

    modulefunction(  :stdmessage,  :generateinlinemarkup,  :inlinemessage)
    
  • Добавим на действия

    /start
    

    , вывод нашей кнопки, для этого разширим сначала модульStandartMessages

    def process  case Listener.message.text  when '/getaccount'    Response.stdmessage 'Very sorry, нету аккаунтов на данный момент'  when '/start'    Response.inlinemessage 'Привет, выбери из доступных действий', Response::generateinlinemarkup(        InlineButton::GETACCOUNT    )  else    Response.stdmessage 'Первый раз такое слышу, попробуй другой текст'  endend
    
  • Создадим файлcallbackmessages.rbдля обработки Callback сообщений :Файлcallbackmessages.rb

    class FishSocket  module Listener    # This module assigned to processing all callback messages    module CallbackMessages      attraccessor :callback_message  def process    self.callback_message = Listener.message.message    case Listener.message.data    when 'get_account'      Listener::Response.std_message('Нету аккаунтов на данный момент')    end  end  module_function(      :process,      :callback_message,      :callback_message=  )endendend
    

    По своей сути роботы отличия от StandartMessages обработчика только в том, что Telegram возвращает разную структуру сообщений для этих двух типов сообщений, и что бы не создаватьспагетти-кодвыносим разную логику в разные файлы.

  • Не забываем обновить список подключаемых модулей, новыми модулями.Файлfishsocket.rb

    require 'telegram/bot'require './library/mac-shake'require './library/database'require './modules/listener'require './modules/security'require './modules/standartmessages'require './modules/response'require './modules/callbackmessages'require './modules/assets/inlinebutton'Entry point classclass FishSocket  include Database  def initialize    super
    
  • Пытаемся запустить бота и посмотреть что будет когда напишем

    /start
    

    Нажимая на кнопку мы видим то - что хотели увидеть.

  • Я бы ещё очень много чем хотел поделится, но тогда это будет бесконечная статья по своей сути - мы же рассмотрим ещё буквально 2 примера на создание ForceReply кнопки, и на использование EditInlineMessage функции


  • ForceReply, создадим соответствующий метод в нашемResponseмодуле

    def forcereplymessage(text, chatid = false)  chat = (defined?Listener.message.chat.id) ? Listener.message.chat.id : Listener.message.message.chat.id  chat = chatid if chatid  Listener.bot.api.sendmessage(    parsemode: 'html',    chatid: chat,    text: text,    replymarkup: Telegram::Bot::Types::ForceReply.new(      forcereply: true,      selective: true    )  )end
    

    Не нужно забывать обновлять modulefunction нашего модуля после изминения кол-ва методов.

    Попробуем сделать банальную реакцию на ввод промокода (хз зачем, для примера)

  • Добавим новую кнопку :

    module InlineButton  GETACCOUNT = Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Получить account', callbackdata: 'getaccount')  HAVEPROMO = Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Есть промокод?', callbackdata: 'forcepromo')end
    
  • Добавить её в вывод по команде

    /start
    

    МодульStandartMessages

    when '/start'  Response.inlinemessage 'Привет, выбери из доступных действий', Response::generateinlinemarkup(    [        InlineButton::GETACCOUNT,        InlineButton::HAVEPROMO    ]  )
    

    Поскольку теперь используется больше одной кнопки, их стоит поместить в массив.

  • Добавим реакцию на нажатие на кнопку, с использованиемForceReply:МодульCallbackMessages

    def process  self.callbackmessage = Listener.message.message  case Listener.message.data  when 'getaccount'    Listener::Response.stdmessage('Нету аккаунтов на данный момент')  when 'forcepromo'    Listener::Response.forcereplymessage('Отправьте промокод')  endend
    
  • Проверим то что мы написали,

    На сообщение от бота сработал ForceReply, что это значит : сообщение выбрано как сообщение для ответа (Reply) так, как если бы мы сами выбрали ответим на сообщение. Оченьюзефулесли речь о пошаговых операциях где нам нужно наверняка знать что именно хочет сказатьюзер.

  • Добавим реакцию на ответ пользователя на сообщение "Отправьте промкод." Поскольку человек отправляет текст, то реагировать мы будем в StandartMessages :МодульStandartMessages

    def process  case Listener.message.text  when '/getaccount'    Response.stdmessage 'Very sorry, нету аккаунтов на данный момент'  when '/start'    Response.inlinemessage 'Привет, выбери из доступных действий', Response::generateinlinemarkup(      [          InlineButton::GETACCOUNT,          InlineButton::HAVEPROMO      ]    )  else    unless Listener.message.replytomessage.nil?      case Listener.message.replytomessage.text      when /Отправьте промокод/        return Listener::Response.std_message 'Промокод существует, вот бесплатный аккаунт :' if Promos::validate Listener.message.text    return Listener::Response.std_message 'Промокод не найден'  endendResponse.std_message 'Первый раз такое слышу, попробуй другой текст'endend
    
  • Создадим файлpromos.rbдля обрабоки промокодовФайлpromos.rb

    class FishSocket  module Listener    # This module assigned to processing all promo-codes    module Promos      def validate(code)        return true if code =~ /^1[a-zA-Z]*0$/        false      end  module_function(      :validate  )endendend
    

    Здесь мы используем регулярное выражение для проверки промокода.НЕ забываем подключить новый модуль в FishSocket модуле :МодульFishSocket

    require 'telegram/bot'require './library/mac-shake'require './library/database'require './modules/listener'require './modules/security'require './modules/standartmessages'require './modules/response'require './modules/callbackmessages'require './modules/assets/inline_button'require './modules/promos'Entry point classclass FishSocket  include Database  def initialize
    
  • Предлагаю протестировать с заведомо не рабочим промокодом, и правильно написаным:

    Функционал работает как и ожидалось, перейдем к последнему пункту: изминения InlineMessages:

  • Вынесем промокоды в отдельное "Меню", для этого добавим новую кнопку на ответ на сообщение

    /start
    

    заменив её кнопку "Есть промкод?"МодульInlineButton

    module InlineButton  GETACCOUNT = Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Получить account', callbackdata: 'getaccount')  HAVEPROMO = Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Есть промокод?', callbackdata: 'forcepromo')  ADDITIONMENU = Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Ништяки', callbackdata: 'advancedmenu')end
    

    Модуль StandartMessages

    when '/start'  Response.inlinemessage 'Привет, выбери из доступных действий', Response::generateinlinemarkup(    [        InlineButton::GETACCOUNT,        InlineButton::ADDITIONMENU    ]  )
    

    Отлично

  • Теперь добавим реакцию на новую кнопку в модуль СallbackMessages:МодульCallbackMessages

    def process  self.callbackmessage = Listener.message.message  case Listener.message.data  when 'getaccount'    Listener::Response.stdmessage('Нету аккаунтов на данный момент')  when 'forcepromo'    Listener::Response.forcereplyC222Cmenu'    Listener::Response.inlineC223CinlineC224CButton::HAVEC225Cmessage
    
  • Предлагаю реализовать обработку этого атрибута в модулеResponse, немного изменив методinlinemessageМодульResponse

    def inlinemessage(message, inlinemarkup, editless = false, chatid = false)  chat = (defined?Listener.message.chat.id) ? Listener.message.chat.id : Listener.message.message.chat.id  chat = chatid if chatid  if editless    return Listener.bot.api.editmessagetext(      chatid: chat,      parsemode: 'html',      messageid: Listener.message.message.messageid,      text: message,      replymarkup: inlinemarkup    )  end  Listener.bot.api.sendmessage(    chatid: chat,    parsemode: 'html',    text: message,    replymarkup: inline_markup  )end
    

    Какова идея? - Мы заменяем уже существующее сообщение на новое, с новым интерфейсом, этот переход позволяет меньше растягивать историю сообщений, и создавать модульные сообщения - такие как меню, оплата, список участников, витрина итд.

  • Что ж, попробуем :

    После того как нажали на кнопку, сообщение измененилось, отобразив другой ReplyKeyboard.
    И если мы клацнем на неё :

    Собственно всё работает как часы.

Послесловие:Много чего тут не было затронуто, но ведь на всё есть руки и документация, лично мне, было не достаточно описания либы на GitHub. Я считаю, что в наше время стать ботоводом может любой желающий, и теперь этот желающий знает что нужно делать. Всем мир.

Подробнее..

Практическое применение алгоритма для представления Цекендорфа

05.01.2021 20:17:13 | Автор: admin

Как то в прошлом

Я написал статью о рекурсивном алгоритме Цекендорфа : пост

Пример кода
def le_fib(limit, fib)  theoretical = fib[fib.count - 1] + fib[fib.count - 2]  return fib.last if theoretical > limit  fib << theoretical  le_fib(limit, fib)enddef main(target,result)  temporary = le_fib(target, [1,1])  result << temporary  return result if target - temporary <= 0  main(target - temporary, result)endpp main(gets.to_i,[])

Функцияle_fib- рекурсивно ищет ряд Фибоначчи с пределом, на то, что бы следующее число не было больше чем входное числоtarget. Здесь важно, что нас не интересует ряд Фибоначчи целиком, нам важно лишь его окончание.

Функцияmain- рекурсивно ищет масcив, числа которого есть числами Фибоначчи, и которые бы в сумме давали нам входное число.

Хотя по правде говоря в комментах предложили более изящное решение :

Один цикл и делов
n, F = 100, [1, 2]while F[-1] < n do  F << F[-2] + F[-1]endF.reverse.each do |f|  if f <= n    n -= f    print f    print '+' if n > 0  endend

На практике я буду применять именно второй алгоритм так как он мение перегружен лишними действиями.

Постановка задачи куда мы будем "впихивать этот алгоритм"

Есть некий набор продуктов, условно говоря :

[ Курица, томаты, лаваш, грибы ].

Эти продукты имеют ценность, как себестоимость так и ценность для конечного пользователя.
К примеру градацию можно произвести такую

[ курица > томаты > грибы > лаваш ] .

Задача состоит в том что бы генерировать такой набор продуктов, который имел бы по крайней мере 1 "Low cost", и 1 "High cost" продукт.

Вот тут то я хочу приспособить этот алгоритм (Цекендорфа).

Главная идея в том что бы создать хэш (структура данных в Ruby) где ключ это число Фиббоначи, а значение собственно имя продукта.

Задача есть, теперь перейдем к решению.

Для начала анализируем сам алгоритм

Запустим его в цикле от x до y и посчитаем количество вхождений каждого числа.

  1. [1,100][1,100][1,1000][1,1000]
  2. Как видим на малых Y вероятность того что число будет использовано в последовательности - равномерно распределенно так как верхняя граница чисел Фибоначчи постоянно растёт пропорционально к текущему числу в итерации.

    Что мы с этого получили - чем больше входное число, тем выше вероятность получения одного и того же граничнего числа последовательности Фибоначчи.

    Значит нам нужен отрезок с более равномерным распределением. Уменьшаем Y

    [1,143][1,143]
  3. Видим пик на крайних числах 1 и 89. Что собственно отвечает постановки задачи, но при этом мы не теряем случайное равномерное выпадение "Middle cost" продуктов.

    Предлагаю остановится на 3 варианте где x = 1 и y = 143.

Реализация

Программы куда будем прописывать алгоритм Цекендорфа, выглядит так :

  • Модуль-перечень продуктов (для возможной тематичности)

  • Collector, который загружает перечень продуктов и составляет Hash (где ключ -> число Фибоначчи, значение -> название продукта)

Такую струтуру я использую для возможной тематичности продуктов, достаточно просто создать новый перечень продуктов и передать в autoloader название новой тематики, что бы времено ввести новые продукты в оборот.

К слову говоря, всё это я делаю для Telegram бота , который создан по гайду описаном в другом посте.

Итак, написав небольшой парсер, на выходе получаем такой результат

Небольшой парсер
@fib = [1,2,3,5,8,13,21,34,55,89]    def collect_the_items      food_hash = Hash.new      (0..9).each do |iterator|        food_hash[@fib[iterator]] = FOOD.first[iterator]      end      puts food_hash.map{|key,value| "#{key} - #{value}"}    end

Следующим шагом, слегка изменим алгоритм представления Цекендорфа :

Алгоритм
def get_sequence(limit)    result = []  n, fib = limit, [1, 2]    while fib[-1] < n do    fib << fib[-2] + fib[-1]  end  fib.reverse.each do |f|    if f <= n      n -= f      result << f    end  end  resultend

Я собираюсь использовать готовую последовательность и пройдя по ней - просто вывести все продукты по ключам.

Код функции
def generate_food          food_array = Collector.collect_the_items          food = []          rarity = rand(1..143)          get_sequence(rarity).each do |key|            food << food_array[key]          end          foodend

Похоже всё готово к тесту, проведу 6 тестовых прогонов, результаты будут в виде ответа от телеграмм бота.
Немного украшу сообщение от бота. Поскольку это никак не отражается на задаче - я не буду описывать этот шаг.

Результаты теста

примечание :
Low cost : ?
Mid cost : ?
High cost : ?

Результат

Применения алгоритма представления Цекендорфа меня полностью удовлетворяет. Тоесть - выполняет поставленную перед ним задачу.

Один из первых комментов под статьей на которой основано это практическое приминение, как раз таки и ставил перед мной вопрос : "а действительно, где это применить можно?". Как видим, вот для таких задач данный алгоритм вполне можно использовать.

И я не говорю о том что это единственный правильный вариант составления списка продуктов для моего бота, но он действительно вполне потян и работает.

Подробнее..

Метапрограммирование в реальной задаче

28.01.2021 00:21:03 | Автор: admin

Всем привет! В этой статье хочу рассказать про метапрограммирование на примере реальной часто встречающейся проблемы.

Когда кто то говорит про метапрограммирование у олдскульного кодировщика случается приступ ярости.

И на это есть причины так и на большом проекте может показаться безумием использовать метапрограммирование, так как код становится очень сложным для чтения. А если в проект включится специалист со стороны, то он и подавно ничего не разберет в этом мета-коде.

Но все не так просто, как говорится - нет плохих инструментов. В этой статьей я постараюсь показать на реальном рабочем примере как метапрограммирование поможет сделать ваш код чище, избавит от рутинных повторений. И заставит порадоваться мета-магии.

Справка из википеди

Метапрограммирование вид программирования, связанный с созданием программ, которые порождают другие программы как результат своей работы (в частности, на стадии компиляции их исходного кода), либо программ, которые меняют себя во время выполнения (самомодифицирующийся код). Первое позволяет получать программы при меньших затратах времени и усилий на кодирование, чем если бы программист писал их вручную целиком, второе позволяет улучшить свойства кода (размер и быстродействие).

На этом заканчивается вступление. Теперь хочу перейти к практической части и рассказать про суть проблемы

Дальше речь пойдёт о языке ruby и фраймворке Rails в частности. В руби есть рефлексия и большие возможности для метапрограммирования. Большинство гемов, модулей и фреймворков созданы при помощи мощных инструментов рефлексии.

Photo by Joshua Fuller on Unsplash

Кто писал что то на Rails скорее всего сталкивался с таким гемом как Rails Admin, если вы в своих проектах на рельсах до сих не пробовали использовать его или аналоги категорически рекомендую это сделать, так как практически из коробки вы получите полноценную CMS для вашей системы.

Так вот там есть неприятная особенность проблема с has_one association.

Они не обрабатываются автоматически и у вас не будет возможности редактировать вашу has_one связь. Выглядит это примерно как на рисунке 1. Вместо селекта, просто ссылка на чертеж.

рисунок 1рисунок 1

Посмотрим на внутренности вот так выглядит модель нашего техпроцесса. У неё есть has_one связь с чертежом (draft) и хотелось бы иметь возможность редактировать её через CMS.

class TechProcess < ApplicationRecord  include MdcSchema  has_many :executor_programs, inverse_of: :tech_process, foreign_key: :barcode_tech_process, primary_key: :barcode  validates :barcode, presence: true  validates :barcode, uniqueness: true  has_one :draft2tech_process, dependent: :destroy  has_one :draft, through: :draft2tech_process  has_many :tech_process2tech_operations  has_many :tech_operations, through: :tech_process2tech_operationsend

Ситуация удивительная но решение этой проблемы простое как палка. Как следует из вики ассоциация has_one не будет должным образом инициализирована до тех пор, пока не будут заданы способы установки и получения идентификатора. Грубо говоря сетеры и гетеры.

Добавляем их в модель

def draft_id  self.draft.try :idenddef draft_id=(id)  self.draft = Draft.find_by_id(id)end

И в итоге получаем полноценный интерфейс редактирования техпроцесса с возможностью выбора чертежа, создания нового, или редактирования прикрепленного, в общем все прелести CMS.

Ничего сложного, но что если у нас 15 моделей с одной или даже несколькими связями has_one это что придется повторить эти гетеры сеттеры в каждом месте? Такое конечно можно сделать, но тогда нарушается очень важный принцип DRY. Ну и плюс природная лень не позволяет так расточительно писать много строк кода. Так что надо найти способ автоматизировать этот процесс и тут на помощь приходит метапрограммирование.

Meta решение

Что же приступим

self.reflect_on_all_associations(:has_one).each do |has_one_association|  define_method("#{has_one_association.name}_id") do    self.send(has_one_association.name).try :id  end  define_method("#{has_one_association.name}_id=") do |id|    self.send("#{has_one_association.name}=",has_one_association.klass.find_by_id(id))  endend

Вот так выглядит код, который подружит has_one и Rails Admin

А теперь более подробно что тут происходит. Далее детально буду останавливаться только на аспектах которые касаются рефлексии и мета программирования.

В руби всё является объектом, связь также является объектом и несет полную информацию о самой себе и всех своих отношениях. Первый интересный метод reflect_on_all_associations Который возвращает массив всех связей, но может принимать параметр "macro" в примере выше я передал туда :hasone и он вернул мне только has_one связи, прекрасно, даже не пришлось дальше селектить только нужные связи.

Отлично, теперь есть список всех has_one связей и нужно автоматизировано определить гетеры и сетеры. Дальше идет, простите за тавтологию, метод define_method который динамически прямо во время исполнения определяет метод.

В итоге этот код создает методы необходимые для корректной инициализации has_one в rails admin.

Сушим до конца

Всё это задумывалось для создания "сухого" кода, так что сейчас опишу последнюю деталь. Нужно всю эту мета-магию вынести в concern

require 'active_support/concern'module HasOneHandler  extend ActiveSupport::Concern  included do    self.reflect_on_all_associations(:has_one).each do |has_one_association|      define_method("#{has_one_association.name}_id") do        self.send(has_one_association.name).try :id      end      define_method("#{has_one_association.name}_id=") do |id|        self.send("#{has_one_association.name}=",has_one_association.klass.find_by_id(id))      end    end  endend

И в итоге класса который в который нужно добавить гетеры и сетеры, добавляется всего одна строчка со включением консерна. Таким малословным способом удалось подружить CMS и has_one связи. Если применять стандартный подход то пришлось бы писать определять гетеры и сетеры для каждой связи в каждой модели, а их может быть немало.

Итоговая версия модели
class TechProcess < ApplicationRecord  include MdcSchema  include HasOneHandler  has_many :executor_programs, inverse_of: :tech_process, foreign_key: :barcode_tech_process, primary_key: :barcode  validates :barcode, presence: true  validates :barcode, uniqueness: true  has_one :draft2tech_process, dependent: :destroy  has_one :draft, through: :draft2tech_process  has_many :tech_process2tech_operations  has_many :tech_operations, through: :tech_process2tech_operationsend

Заключение

Надеюсь мне удалось показать практическую ценность использования приемов метапрограммирования. Использовать его или нет решать конечно вам. Если применять его слишком часто и не к месту, то проект превратиться в абсолютно не читаемый и трудно отлаживаемый, но в случае правильного использования, напротив, уменьшает количество кода, улучшает читаемость и избавляет от рутиной работы. Спасибо всем кто дочитал!

Подробнее..

Бесплатные онлайн-мероприятия по разработке (1 марта 7 марта 2021)

27.02.2021 14:17:14 | Автор: admin

2 марта, Вторник

Dev-to-Consult

Мне нравится писать код, но как долго у меня получится этим заниматься?

Если посмотреть по сторонам, можно увидеть разные сценарии. Есть разработчики, кто переходит в менеджмент тим- и техлидство, управление проектами и продуктами. Другие остаются в рамках технического трека идут в архитектуру. Третьи меняют роль в разработке уходят в аналитику, маркетинг, бизнес. Некоторые подаются в консультанты или вовсе выходят из айти. А есть те, кто продолжает писать код, и у них как будто бы всё ок.

Давайте разбираться, как устроены траектории профессионального развития разработчиков.

Поговорим с инженерами, которые пошли по разным путям. Узнаем у них, как так вышло и что из их опыта может пригодится нам, всем остальным.

Эксперты
Асхат Уразбаев, управляющий партнёр, ScrumTrek
Максим Дорофеев, прокрастинатолог в mnogosdelal .ru
Георгий Могелашвили, Mentor/Coach, Lead Developer


2 марта, 15:0016:30 мск, Вторник

Регистрация на мероприятие

3 марта, Среда

Inforso Meetup #2
  1. Основной процесс работы в современных командах разработки - Глеб Шалтаев, Senior Frontend developer, банк Открытие
    Правила игры в команде; Основные инструменты и активности команды; Разбор на примере бизнес-задач; Полезные ресурсы.

  2. Roadmap для разработчика: как пройти первое собеседование - Вадим Селяков, Тим-лид, Сберлогистик
    Хард-скилы; Софт-скилы; Языки программирования; Почему стоит или не стоит работать в ИТ; Полезные ресурсы


3 марта, начало в 20:00 мск, Среда

Регистрация на мероприятие

Data Science meetup
  1. Как я перестал бояться и полюбил AutoML - Александр Кузнецов
    Что предлагают современные системы AutoML от Google, Amazon, Microsoft, IBM, зачем они нужны аналитику данных и почему не нужно бояться развития этих сервисов (пока что).

  2. All you need for RecSys is a good baseline - Павел Смирнов
    Обзор на статью от Steffen Rendle с анализом современных тенденций в RecSys к использованию нейросетей вместо классического декартового произведения. Что такое матричная факторизация, алгоритм NeuMF.

  3. Как понять ИИ (или все же ML?) - Роман Щербаков
    Обзор актуальных методов оценки влияния признаков в обучении модели, тонкости и нюансы выбора тех или иных инструментов XAI.

  4. Web Fingerprinting and ML - Илья Стариков
    Про идентификации и анти-идентификации пользователей в сети. Зачем и какую информацию собирают сайты о нас, как ML помогает им в этом.


3 марта, 18:00-20:00 мск, Среда

Регистрация на мероприятие

4 марта, Четверг

Soft Skills Hero Meetup
  1. Сон. Легко потерять, сложно найти, невозможно забыть - Алексей Заборщиков, Andersen
    О нашем самом любимом занятии - профессионально! Что такое сон? Почему нам нужно спать? Как спать лучше?

  2. Resume-Driven Development in Action - Владимир Рожков, Devlify
    Современные технологии развиваются очень быстро. Новые библиотеки и фреймворки выходят чаще, чем успеваешь изучить старые. Нужно бежать, чтобы просто оставаться на месте. Многие задаются вопросом: когда изучать новое, чтобы быть востребованным специалистом? После работы? А когда жить и заниматься своими делами?
    Ответы на эти вопросы заключаются в методологии Resume-Driven Development.

  3. Архитектура - это софт скиллы - Алексей Мигутский, Senior Sofware Engineer, Microsoft/GitHub
    Архитектура это не только код; Почему архитектура требует общения; Закон Конвея; Как взрослеет архитектурный процесс.


4 марта, начало в 19:00 мск, Четверг

Регистрация на мероприятие

Ruby Meetup Online
  1. SmartCore (smart-rb) a set of common abstractions and principles, realized in scope of Ruby, DDD and Clean Architecture - Ибрагимов Рустам, Team Lead, Umbrellio
    О наборе новых библиотек, решающих задачи архитектурного направления в мире Ruby. Инструменты, которые уже реализованы, их преимущества и идеология.

  2. MPI: композитные атрибуты моделей данных - Ильчуков Александр, Ruby разработчик в MPI
    Как реализовать с помощью композитных типов PostgreSQL иерархичные структуры, что увеличит производительность в определённых случаях и повлияет на массивное упрощение бизнес-логики.

  3. Метрики эффективности сервиса - Астхана Аникет, Project manager, Umbrellio
    Метрики Lead Time, Throughput, Flow Efficiency, Resource Efficiency: как они между собой связаны, к чему они чувствительны и как могут нам помочь. Какую информацию о положении дел сервиса можно получить из Jira.


4 марта, 17:0019:30 мск, Четверг

Регистрация на мероприятие

Быстрый веб-сервис

В начале девяностых годов мы работали на компьютерах с процессорами 80386 и 80486. Они казались сверхбыстрыми по сравнению с персоналками предыдущего поколения. На машине с мозгами модели 80386 запускался и работал Doom, что казалось невероятным.

При этом разработчикам приходилось выжимать всю производительность, на которую машины были способны.

Сейчас нам гораздо реже приходится заниматься ускорением кода, но есть задачи, где это всё ещё необходимо.

Одна из таких задач это быстрые веб-сервисы. Иногда речь идёт об очень большом количестве запросов.

Роман Ромадин расскажет о том, как он разрабатывал пиксель-трекер для анализа посещаемости сайта. Сначала трекер был написан на PHP, потом переписан на Go. Для хранения данных применялись разные базы. В конечном итоге производительность выросла на порядок.
Роман опытный бекенд-разработчик, писавший на PHP и на Go.


4 марта, 19:0020:00 мск, Четверг

Регистрация на мероприятие

5 марта, Пятница

Online-митап Hot Frontend от SimbirSoft
  1. Как успешно пройти собеседование на роль frontend-разработчика - Дмитрий, руководитель Frontend-отдела
    Что нужно знать; Как вести себя на интервью; Правильные установки.

  2. Работа с MobX: личный опыт - Евгений, frontend-разработчик
    Ушли от компонентного подхода в сторону решения бизнес-задачи; На пути к 100% декларативному программированию; Из React выброшено всё ненужное.

  3. Динамические диаграммы для Vue на основе SVG - Евгений, frontend-разработчик
    Анимация и построение графиков-диаграмм; Диаграмма без использования сторонних библиотек; GPU анимация


5 марта, начало в 18:00 мск, Пятница

Регистрация на мероприятие

Подборку подготовил автор телеграм-канала ITMeeting - анонсы бесплатных мероприятий по разработке.

Подробнее..

Запускаем скрипты Ruby из Go Lang

27.02.2021 20:23:19 | Автор: admin

Для использования Ruby как скриптового языка, то есть как языка для встраивания, вроде lua, существует легковесная реализация Ruby под названием MRuby.

Для go удалось найти только одну стабильную библиотеку с биндингом к mruby (https://github.com/mitchellh/go-mruby). По умолчанию, она может собрать mruby версии 1.2.0 (2015 год выпуска), и можно попробовать сделать сборку вплоть до версии 1.4.1 (2018 год выпуска). Но актуальная версия mruby сейчас имеет версию 2.1.2 (2020 год). Есть форк с поддержкой версии mruby 2.1.0 (https://github.com/mrbgems/go-mruby). Этот форк и будем использовать, что бы после небольших изменений получить в том числе версию 2.1.2.

В версиях старше 2.1.0 внесли как минимум следующие несовместимости на которые следует обратить внимание.

Версия 2.1.1:

  • Remove MRB_INT16 configuration option.

Версия 2.1.2

  • IO#readchar returns a UTF-8 character fragment instead of EOFError if EOF is reached in the middle of UTF-8 characters. (86271572) This behavior is different from CRuby, but it is a mruby specification that supports either ASCII or UTF-8 exclusively.

  • Remove mrb_run() from C APIs.

Для успешной сборки go-mruby критично удаление метода mrb_run. Как могут проявиться другие "Breaking Changes" пока не особо ясно. Теперь пошагово установка go-mruby:

  • Клонируем https://github.com/mrbgems/go-mruby/tree/mruby-2 в корень проекта и переключаемся на ветку mruby-2.

  • Удаляем go.mod и go.sum из директории go-mruby. Если их оставить, то go может не дать корректно импортировать модуль внутри программы. Возможно, зависит от версии go и надо покрутить настройки вендоринга, так как на протяжении нескольких версий в go поведение по умолчанию менялось. Ещё можно положить go-mruby в директорию vendor и исправить пути импортов.

  • Из файла mruby.go необходимо удалить метод Run(), а в тестах заменить все вызовы этого метода на RunWithContext().

  • В Makefile исправляем MRUBY_COMMIT на 2.1.2 - так мы получим актуальную версию mruby.

  • Запускаем make. Он сделает клон репозитория указанной версии mruby во вложенную директорию vendor и скомпилирует Си библиотеку libmruby.a.

На что ещё стоит обратить внимание. В форке в обязательном порядке занесён гем mruby-error как раз из за которого форк не был принят (https://github.com/mitchellh/go-mruby/pull/75). В mruby нет поддержки require и нельзя подключить модули в рантайме. Все необходимые модули необходимо подключить на этапе компиляции. Список доступных библиотек есть на странице http://mruby.org/libraries/, а в оригинальном файле mruby/build_config.rb есть пример подключения подключения стандартных и пользовательских библиотек. В mruby/examples/mrbgems можно подсмотреть примеры реализации собственных расширений, а в mruby/mrbgems стандартные библиотеки. Например, возможности метапрограммирования вынесены в отдельный гем mruby-metaprog.

Попробуем подключить поддержку json. Для этого необходимо в go-mruby/build_config.rb прописать библиотеку:

gem :github => 'iij/mruby-iijson'

Пример использования JSON.parse, при этом, как видим, опция symbolize_names этой библиотекой похоже не поддерживается.

func main() {mrb := mruby.NewMrb()defer mrb.Close()class := mrb.DefineClass("Example", nil)class.DefineClassMethod("json_value", func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) {return mruby.String(`{"int":1, "array":["s1", "s2", {"nil": null}]}`), nil}, mruby.ArgsReq(1))result, err := mrb.LoadString(`JSON.parse(Example.json_value, {"symbolize_names" => true})`)if err != nil {panic(err.Error())}// Result: {"int"=>1, "array"=>["s1", "s2", {"nil"=>nil}]}fmt.Printf("Result: %s\n", result.String())}
Подробнее..

Перевод Сервер в одну строку на 17 языках

16.05.2021 16:07:51 | Автор: admin
Каждая из этих команд будет запускать специальный статический http-сервер в вашем текущем (или указанном) каталоге, доступном по адресу http://localhost:8000. Используйте эту силу с умом.

Python 2.x


$ python -m SimpleHTTPServer 8000


Python 3.x


$ python -m http.server 8000


Twisted (Python)


$ twistd -n web -p 8000 --path .


Или:

$ python -c 'from twisted.web.server import Site; from twisted.web.static import File; from twisted.internet import reactor; reactor.listenTCP(8000, Site(File("."))); reactor.run()'

В зависимости от Twisted.

Ruby


$ ruby -rwebrick -e'WEBrick::HTTPServer.new(:Port => 8000, :DocumentRoot => Dir.pwd).start'


Ruby 1.9.2+


$ ruby -run -ehttpd . -p8000


adsf (Ruby)


$ gem install adsf   # install dependency$ adsf -p 8000


Sinatra (Ruby)


$ gem install sinatra   # install dependency$ ruby -rsinatra -e'set :public_folder, "."; set :port, 8000'


Perl


$ cpan HTTP::Server::Brick   # install dependency$ perl -MHTTP::Server::Brick -e '$s=HTTP::Server::Brick->new(port=>8000); $s->mount("/"=>{path=>"."}); $s->start
'

Plack (Perl)


$ cpan Plack   # install dependency$ plackup -MPlack::App::Directory -e 'Plack::App::Directory->new(root=>".");' -p 8000


Mojolicious (Perl)


$ cpan Mojolicious::Lite   # install dependency$ perl -MMojolicious::Lite -MCwd -e 'app->static->paths->[0]=getcwd; app->start' daemon -l http://*:8000


http-server (Node.js)


$ npm install -g http-server   # install dependency$ http-server -p 8000


Примечание: этот сервер делает забавные вещи с relative paths. Например, если у вас есть файл /tests/index.html, он загрузит index.html, если вы перейдете в /test, но будет обрабатывать относительные пути, как если бы они исходили из /.

node-static (Node.js)


$ npm install -g node-static   # install dependency$ static -p 8000


PHP (>= 5.4)


$ php -S 127.0.0.1:8000


Erlang


$ erl -s inets -eval 'inets:start(httpd,[{server_name,"NAME"},{document_root, "."},{server_root, "."},{port, 8000},{mime_types,[{"html","text/html"},{"htm","text/html"},{"js","text/javascript"},{"css","text/css"},{"gif","image/gif"},{"jpg","image/jpeg"},{"jpeg","image/jpeg"},{"png","image/png"}]}]).'


busybox httpd


$ busybox httpd -f -p 8000


webfs


$ webfsd -F -p 8000


В зависимости от webfs.

IIS Express


C:\> "C:\Program Files (x86)\IIS Express\iisexpress.exe" /path:C:\MyWeb /port:8000


В зависимости от IIS Express.

Подробнее..

Почему за интерпретируемыми языками будущее

31.05.2021 00:23:47 | Автор: admin

Зададимся вопросом, что такое прогресс? Википедия говорит нам следующее:

Прогресс, проградация(лат.pro движение вперёд, успех;grad шагать, ступать;gradatio постепенное повышение) направление развития от низшего к высшему, положительная динамика, поступательное движение вперед, повышение уровня организации, усложнение способа организации, характеризуется увеличением внутренних связей. Противоположность регресс,деградация

Люди, общество, социум испокон веков стремилось к познанию, открытию, изучению. Вы когда- нибудь задавались вопросом, почему? Почему мы прогрессируем? Что это, код, заложенный в нас природой на генном уровне или чей-то промысел? От каменного оружия до нанотехнологий, от лечения травами, до пересадки сердца, постепенно, со временем, но мы улучшаем и облегчаем нашу жизнь! Вот, два ключевых слова, улучшаем и облегчаем, именно эти два слова, являются нашим двигателем, двигателем развития и прогресса. О чем вы подумаете, если я вам скажу, что война, это тоже необратимая часть прогресса? История, является отражением нашего с вами развития, наших поступков и достижений, от простейшего организма, до организма со сложнейшей структурой. Мы делаем выводы, мы учимся, словно нейросеть. Допускали ли вы, что человек это и есть искусственный интеллект. Забавно, но человек, подобно Богу, стремится создать существо, по образу и подобию своему.

В начале было слово, в нашем же случае, был машинный язык. На протяжении 1950-х годов разработка программ для машин увеличивалась, код усложнялся, структура становилась сложнее. Как итог, люди пришли к созданию первого высокоуровневого языка программирования Фортран, и уже в след за ним начали появляться и другие языки, началось улучшение и развитие инструмента взаимодействия с машиной, причина, следствие. Добавим немного Википедии:

Эти языки более удобны для человека, с помощью них можно писать программы до нескольких тысяч строк длиной. Условными словами можно было, как привычно человеку, гораздо более просто выразить сложную программную операцию из битов.

Прогресс и развитие на лицо, мы улучшили и развили язык взаимодействия с машиной до необходимого для нас уровня, и мы продолжаем это делать и по сей день, ибо технологии развиваются и становятся более сложными.

В настоящее время существует, много высокоуровневых языков программирования, которые делятся в свою очередь на компилируемые и интерпретируемые. Первые, на порядок сложнее для восприятия и скорости разработки, поэтому, следуя нашей сути, были созданы последние, которые стали еще ближе к человеческой природе, к человеческому языку:

"Всё гениальное просто, и всё простое гениально. Маленькому человечку нравится скрывать свою ничтожность за сложными вещами." Йозеф Геббельс

Интерпретируемые языки программирования, это уровень нашего текущего развития в компьютерной науке, это следующий этап развития, за ними будущее, это вид, от которого пойдут новые и улучшенные языки программирования, возьмите, например компилируемый язык программирования Go, его создатели поняли, что надо взять лучшее от двух миров, простота и скорость, хотя о первом можно еще поспорить.

Да, интерпретируемые языки, такие как Python, Ruby, являются "медленными", в сравнении с такими компилируемыми языками, как Java, С++. Скорость работы программ, написанных на них, позволяет нам управлять ракетами, и мы безусловно, должны чтить и уважать их, ибо благодаря им мы имеем, что имеем. Но дело не только в скорости работы программ, но и в простоте написания и скорости разработки. Instagram, Hulu, Twich, Github, Airbnb, Shopify, Reddit, Spotify, все это написано на интерпретируемом языке и я сомневаюсь, что кто-то станет писать Instagram на C++/C, только потому что он быстрее Python, также, верно и обратное утверждение, на Python ни кто не будет писать операционную систему или драйвера. Однако, человеческая природа хочет облегчать и совершенствовать различного рода процессы. Это заложено в нас, идти в перед, развиваться и я не на миг не сомневаюсь, что лет через 100, языки программирования будут подобно человеческому, скорость которых будет сравнима скорости машинному языку. И все это, благодаря человечеству, который подобно организму, развивается, обучается и улучшается.

Так почему же я считаю, что за интерпретируемыми языками будущее? Потому что, они несут философию простоты, понимания и человечности, уже сейчас мы можем наблюдать, как они развивают наш мир и наше общество. И именно эти языки будут прародителями последующих.

"Я хочу, чтобы компьютер был моим слугой, а не господином, поэтому я должен уметь быстро и эффективно объяснить ему, что делать. " Мацумото Юкихиро

"В первую очередь вы пишете код, который будет коммуницировать с другими разработчиками, и в меньшей степени, навязывать свою волю компьютеру." Гвидо ван Россум

"Скорость работы больше не является главной проблемой. Как быстро вы зайдёте на рынок, да." Дин Хуэй

Подробнее..

Скрипт на ruby, который с помощью яндекс-сервиса Yandex SpeechKit распознает текст в видео-файле (длинные аудио)

11.01.2021 12:12:41 | Автор: admin

Начну с того, что я совсем недавно начала погружаться в IT в целом и Ruby в частности, и это задание мне выдали в качестве тестового для получения места на стажировке. Заранее скажу, что тут еще есть что приглаживать и улучшать, но в целом код работает.

Однако, возможно, мой опыт может быть для кого то полезен, так что представляю вашему вниманию подробное описание создания этого скрипта. ВАЖНО: Моя операционная система Fedora 32, так же я использую заранее установленный в систему bundler. Так что если вы тоже используете linux-подобные системы, читаем далее.

Суть задания: есть видеофайл в формате mp4 нужно написать скрипт на чистом ruby, который будет конвертировать этот файл в аудио, отправлять его в сервис яндекса Yandex SpeechKit и получив ответ, создавать текстовый файл.

Перед началом работы стоит внимательно изучить документацию Яндекса, на предмет подводных камней и таких нюансов, как читаемый яндексом формат аудио (а их, к слову, всего два: OggOpus и LPCM).

Подготовительный этап:

Теперь можно перейти к составлению плана работы:

  1. Перевод файла из формата mp4 в аудио с помощью утилиты ffmpeg

  2. Отправить получившийся файл в бакет Yandex Service Object

  3. Отправить полученный ответ с адресом файла в бакете на SpeechKit

  4. Получить ответ и преобразовать его в текстовый файл

Далее будем двигаться по пунктам с пояснениями интересных (и не всегда очевидных мест)

1. Перевод файла из формата mp4 в аудио с помощью утилиты ffmpeg

Для форматирования видео файла устанавливаем в нашу систему ffmpeg

sudo dnf install ffmpeg

И, если вы этого еще не сделали, кладем требующий форматирования видеофайл в папку нашего небольшого проекта (в моём случае это будет test_task)

В этой же папке создаем рубишный файлик (например, run.rb), в котором будем писать скрипт:

touch run.rb

В скрипте нам необходимо вызывать команды оболочки изнутри программы ruby (bash-команды, такие как: system, exec, popen, ` `) Мельком ознакомиться с этими командами можно здесь (https://www.rubyguides.com/2018/12/ruby-system/)

Я решила использовать заключение в ` `:

`ffmpeg -i test.mp4 -vn -acodec libopus audio.ogg`
Где:

test.mp4 название и формат форматируемого видеофайла.

Флажок -vn используется, чтобы указать, что нам нужен именно аудио формат (без видео).

Библиотеку libopus мы используем, так как для SpeechKit нужен именно OggOpus формат.

audio.ogg название для нового аудиофайла, который мы получим в результате форматирования и его формат (в нашем случае ogg)

Теперь, если запустить наш файл в консоли, мы должны получить на выходе аудиофайл в нужном нам формате.

2. Отправить получившийся файл в бакет Yandex Service Object

Теперь можно временно закоментировать написанную ранее строку и перейти к разработке следующей части скрипта.

Согласно документации яндекса, мы не можем загрузить в сервис непосредственно файл, нам нужно использовать ссылку на загруженный в Yandex Object Storage (яндекс хранилище) объект.

Так как Yandex Object Storage имеет HTTP API, совместимый с Amazon S3, мы можем воспользоваться инструментами, созданными для работы с объектными хранилищами Amazon S3.

Для Amazon S3 реализован руби гем aws-sdk-s3, мы можем применить его для загрузки нашего файла в Yandex Object Storage.

Устанавливаем гем aws-sdk-s3. Создаём в папке с тестовым заданием Gemfile и в нем прописываем:

 source 'https://rubygems.org' gem 'aws-sdk-s3'

gem 'aws-sdk-s3' Далее в консоли запускаем команду:

bundle install

И подключаем этот гем в наш файлик run.rb, прописывая в нём:

require 'aws-sdk-s3'

Как пример кода использовала эту статью.

На этом этапе нам понадобится создать API-ключ в яндексе и сохранить его.

Важный момент: создать нужно именно статический ключ доступа для Object Storage и Message Queue.

Так как, светить свои приватные ключи всему интернету - дурной тон, воспользуемся гемом dotenv. Этот гем нужен нам для введения переменных окружения, их мы заботливо поместим в файлик .env , который никому не покажем.

Устанавливаем гем, записываем его в Gemfile:

gem 'dotenv'

И запускаем команды в терминале:

bundle install

Теперь инклюдим его в наш файлик:

require 'dotenv/load'

И создаем в нашей папке со скриптом файлик .env с переменными, в которые заносим ключ и идентификатор:

AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXAWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXX

Далее просто меняем пример из статьи выше под наш случай. Cначала прописываем конфигурацию для aws, указывая регион и ключ с идентификатором:

 Aws.config.update(   region: 'ru-central1',   credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']) )

Добавляем эндпоинт яндекс хранилище:

region: 'ru-central1',   credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'],    ENV['AWS_SECRET_ACCESS_KEY']) s3 = Aws::S3::Client.new(endpoint: "https://storage.yandexcloud.net")

И, наконец, открываем и отправляем отформатированный ранее аудиофайл в созданный нами бакет. (Здесь, разумеется можно обойтись без вывода результата(puts pp), но так нагляднее)

 File.open('audio.ogg', 'r') do |file|   pp = s3.put_object({     bucket: 'teststask',     key: 'audio.ogg',     body: file   })     puts pp end

Теперь мы можем проверить работу кода запустив run.rb и обновив страницу бакета (в нем должен появится новый файл).

3. Отправить полученный ответ с адресом файла в бакете на SpeechKit

Для работы с http запросами в руби есть гем httparty (https://github.com/jnunemaker/httparty/blob/master/examples/basic.rb)

И примеры работы с этим гемом.

Устанавливаем гем, записываем его в Gemfile:

gem 'httparty'

И запускаем команды в терминале:

bundle install

Теперь инклюдим его в наш файлик:

require 'httparty'

Далее можно воспользоваться им в написании кода для отправки ссылки из предыдущего этапа.

Необходимо сформировать ссылку, так как реализовывать ее получение внутри скрипта не является частью задачи , то прибегнем к хитрости. Ссылка состоит из сервиса, имени бакета и имени файла, которое обычно является путем к файлу: https://storage.yandexcloud.net/<имя-бакета>/<путь-к-файлу>

Значит мы можем самостоятельно сформировать эту ссылку, так как все это у нас уже есть:

https://storage.yandexcloud.net/teststask/audio.ogg


Конечно, эту часть можно довольно легко автоматизировать, но время не ждало и задачи улучшить скрипт не стояло)) Так что здесь можете применить все свои навыки!

Далее переходим к формированию post запроса на SpeechKit.

Нам необходимо составить запрос с хедером, содержащим API-ключ для авторизации и телом, включающим нашу ссылку и язык. Подробнее можно посмотреть в документации яндекса.

Важно: здесь нам необходим другой API-ключ (API-ключ для упрощенной аутентификации вместо IAM-токена)

Положим всё это в переменную options.

 options = {   headers: {"Authorization" => "Api-Key #{ENV['API_KEY']}"},   body: {     "config" => {         "specification" => {             "languageCode" => "ru-RU"         }     },     "audio" => {         "uri" => "https://storage.yandexcloud.net/teststask/audio.ogg"     }   }.to_json }
 response = HTTParty.post('https://transcribe.api.cloud.yandex.net/speech/stt/v2/longRunningRecognize', options).to_h

Хеш нам нужен для того, что бы мы могли извлечь из него необходимый нам для отправки следующего запроса идентификатор операции распознавания.

Теперь нам нужно отправить запрос на получение информации об операции.

Создаем параметр для нового запроса в котором оставляем только авторизацию:

 option = {    headers: {"Authorization" => "Api-Key #{ENV['API_KEY']}"} }

И создаём цикл, который будет проверять, закончена ли обработка аудио и, в случае положительного результата выводить ответ в виде хеша. Здесь в качестве одного из параметров мы и передаем тот самый идентификатор операции распознавания (это #{response['id']} в ссылке "https://operation.api.cloud.yandex.net/operations/#{response['id']}").

Интервал в 2 секунды между запросами, необходим, чтобы система яндекса не посчитала нас вредоносной программой)

 done = false until done   yandex_answer = HTTParty.get("https://operation.api.cloud.yandex.net/operations/#{response['id']}", option).to_h   puts yandex_answer   done = yandex_answer['done']   sleep 2 end

4. Получить ответ и преобразовать его в текстовый файл

Так как здесь вступет в дело ruby, многим эта часть покажется менее интересной. Просто работаем с хешами и массивами:

 yandex_array = yandex_answer["response"]["chunks"] yandex_text = []  yandex_array.each do |elem|   yandex_text << elem["alternatives"].first["text"] end

pp yandex_text.uniq!

Создаем через bash-команду новый файлик:

`touch test.txt`

И кладем в него полученный массив переобразуя его в текст:

File.open("test.txt", 'w') { |file| file.write(":#{yandex_text.join(' ')}") }

Итого мы имеем три файла: .env (с переменными окружения), Gemfile (с тремя гемами: httparty, aws-sdk-s3, dotenv), run.rb (с кодом).

Вуаля, у вас есть маленький скрипт для форматирования видео в текст.

Подробнее..
Категории: Ruby , Script , Yandex.ru , Speechkit , Ffm , Aws sdk , Dotenv

Cli-IDE для Oracle СУБД. Ну. Почти IDE

25.01.2021 18:10:11 | Автор: admin

Добрый день.

Первый вопрос: зачем, есть же Toad/SQL-developer/PLSQL-developer и т.п. графические ide-среды.

Ну. Да. Есть. Однако не всем именно они нужны, как средство работы с объектами oracle-субд.

Т.е. я тут, в этой статье - не именно для oracle-разработчиков буду рассказывать что то. Скорее для dba, т.е. людей у которых работа с объектами субд - значительно более специфичная, им не нужен такой ide-комбайн как Toad/SQL-developer/PLSQL-developer;

Второй момент: сейчас: удалёнка, везде и всюду. В этой связи доступ на работу - организовывается удалённый.

Либо: через что то типа VDI/RDP. Либо: выдаётся рабочий ноутбук и от на нём, с него - работай.

В первом случае - ну, часто подразумевается графический интерфейс удалённого рабочего стола и: виндовый и с ограничением по ресурсам.

Во втором случае: ну, организации часто пытаются и тут сэкономить - выдадут что нибудь с 8Гб оперативы и - как хочешь, так на этом и работай. Т.е.: файрфокс/хром, с N>>10 кол-вом вкладок, какой нибудь Toad/SQL-developer/PLSQL-developer, скайп/зум, ещё что то.

В обоих случаях: возникает вопрос - а как то бы тут сэкономить, на ресурсах, например - на IDE для oracle-субд.

Ну и, как мне кажется, находится, под этот запрос, интересный проект: VoraX

Проект, на настоящее время, абандонед, увы.

Вот хочу, по ряду причин, обратить внимание сообщества на этот проект. С одной стороны - чтобы больше народу посмотрело, с разных сторон и что то, какие то за/против увидели/оценили.

С другой стороны - уже появились, у меня, к этому проекту хотелки, но, не понятно как к ним подступится - нет ни времени, ни ресурса этим заниматься. Может быть тоже - кто то подскажет. А то и: свой форк запилит: штука то - весьма интересная.

В русскояз-м сегменте интернета практически нет упоминаний об этом проекте, есть упоминание воракса, в хабр-комменте, одного из контрбьютеров воракс-проекта.

В функционально-пользовательском смысле про возможности/свойства воракс-а отлично рассказывает автор проекта, здесь.

В инженерно-техническом смысле это - vim-плагин. Часть кода плагина: собственно дополнительные vim-функции, обеспечивающие работу в пользовательском инт-се. Часть - это руби-скрипты, которым выполняется, например, запуск-взаимодействие с sqlplus, работа с репозиторем подключений.

Т.е. воракс - это честная скл-сессия в субд, со всеми отсюда вытекающими последствиями: возможность выполнять транзакции из нескольких команд, например. Во всяких dbext, vim-dadbod, Emacs в скл-моде: такого не получится - они, при выполнении каждой конкретной скл-команды: запускают скл-плюс, подключаются в бд, выполняют команду, получают результат, закрывают соединение в бд.

Ещё часть, релевантного к воракс-у, руби-кода распространяется в виде gem-пакета с именем "vorax", который надо доставлять в руби отдельно. В этом гем-е проверяются и, если надо, предварительно ставятся зависимости, в частности racc gem. Ему, этому gem-пакету, нужен ruby-dev ОС-пакет, его надо ставить в систему.

Соответственно: требуется, установленными на машине: ruby, vim с поддержкой ruby, oracle-клиент (можно инстант-клиент).

На LinuxMint - всё это ставится без проблем, я делал так:

apt install gawk bison libffi-dev libgdbm-dev libncurses-dev libsqlite3-dev libyaml-dev zlib1g-dev -y apt install sqlite3 libgmp-dev libreadline-dev checkinstall libxml2-dev libxslt-dev -y apt install build-essential ruby-dev pkg-config -yruby -vgem install voraxgem list | sort

Потом сборка vim-а, с поддрежкой ruby:

mkdir ~/Vim4VoraX; cd ~/Vim4VoraXgit clone https://github.com/vim/vim.gitcd ./vim/src/#make distclean./configure --enable-rubyinterp --with-features=hugemake./vim --version | grep ruby; pwdsudo apt remove vimsudo checkinstall --pkgname vim4vorax4 --pkgversion 8.2.0 --install#dpkg -r vim4vorax4#in vim:#:ruby puts RUBY_VERSION

Затем патоген-ом доставил собственно vorax-плагин в vim, использовал более современный форк, уважаемого kish4ever:

cd ~/.vim/bundle && git clone https://github.com/kakash1hatake/vorax4.git

При установке кода вим-плагина руками, так как это описывает автор, плагин полностью работоспособным не становился. Не знаю может тут что то с кривизной моих рук. Патоген-менеджер vim-плагинов - мне помог.

Ну. Пара-тройка скринов, мы все - любим скрины:

Дока есть вот тут и в файле /vorax4/doc/vorax.txt - здесь и далее: путь относительный, от директории куда ставился vim-плагин.

Из некоторых вкусностей, которые успел заметить за те два дня что разглядываю этот плагин:

  1. Профили подключения: шифруются, хранятся в виде xml-файла profiles.xml, в, по умолчанию, домашнем каталоге. Профиль-менеджер, технически, это скрипт: /vorax4/vorax/ruby/lib/vorax/profiles_manager.rb

  2. Есть некий функционал, задающий режим отображения выхлопа селект-команд, в окне (точнее: буфере, если в vim-терминах) вывода. Бегло: поддерживается аппенд-мода вывода, поддерживается лимитирование строк в выборке (дописывается WHERE ROWNUM <= :limit), подерживается задание режима в котором не усекаются имена полей, если они длинее ширины столбца, есть Топ-режим отображения. Есть ещё несколько функций. Конечно можно выполнять set-команды и прочие команды склплюс-а и задавая какое то своё форматирование. Однако, в остальном - это всё тот же склплюс, со своими плюсами и минусами, касательно вывода. Об этом - несколько ещё поговорю, ниже. В правом нижнем углу окна вывода - подсказывается текущая мода отображения.

  3. Вот про выполнение команд и всякое форматирование: есть режим <Leader>E: т.н.: sanbdox-мода. В ней можно выполнить какой то скл-скрипт, который может выполнять свои склплюс-команды настройки скл-сессии. При этом, настройки скл-сессии самого воракс-а, из которого был, в такой моде выполнения, вызван некий скл-скрипт - будут запомнены, а после выполнения скрипта - восстановлены. Also: поддерживается обработка sigint сигналов 2,9 при выполнении команд - они посылаются склплюс-у. Also: поддерживается выполнение конкретной команды, на которой стоит курcор (или то что выделено, в visual-моде ), в верхнем vim-окне.

  4. plsql-подпрограммы, их определение, можно получить из объектного вьювера, в отдельный буфер (в терминах vim-а), просто встав на элемент в тривью и жмакнув "Enter". Что тут интересно - конкретно вот этот буфер, его содержимое, при попытке изменить-скомпилировать ддл-код процедурного объекта, воракс будет отправлять в субд as is, цитирую:

The default key mapping for compiling is c. "C" stands, obviously, for "C"ompile. For convenience, you may also use @. Two things happen when a PLSQL object is compiled:

* the buffer content is sent as it is to the server.

* as soon as the buffer content is executed, Vorax will check the ALL_ERRORS view to see if any relevant errors exist. If such errors are found, they are loaded into a Vim quickfix window, along with the error line, error column and the message itself.

Note: Pay attention that during the compilation of an PLSQL object, the substitution variables feature is disabled.

Проблемки (ну. с моей т.з.):

Спулинг выборок из воракс-а в файл. Формально - работает. Однако выясняется что воракс, обменивается с склплюс-ом (читает/пишет на/с стдоут/стдин) данными особенным образом - он обрамляет пользовательский текст в свои текстовые брекеты. Взаимодействие с склплюс - делается скриптом: /vorax4/vorax/ruby/lib/vorax/sqlplus.rb, там это выглядит, например, таким образом (пара скринов):

В итоге, в спул-файл, выписывается, кроме самой выборки - ещё куча служебных строк, от обмена данными между воракс-ом и склплюс-ом. Всё это - обходится конечно, но надо сделать дополнительные усилия, для обхода.

Второй момент.

Разглядывая склплюсовое форматирование выборок, в окне вывода, в воракс-е, вспомнил про SQLcl: какой он весь из себя модный-молодёжный, как там всё здорово с форматированием выборок. Ну и: тут же пришла в голову мысль о костыле рационализаторско-конструкторском усовершенствовании - а пуркуа бы и не па вписать в /vorax4/vorax/ruby/lib/vorax/sqlplus.rb вместо вызова бинаря sqlplus, вызов sql;

Да, виновен/мне стыдно: на тот момент ещё не знал что SQLcl - вызывается шельником <SQLcl-хоум>/sqlcl/bin/sql, который запускает ява-процесс, к которым и взаимодействует пользователь.

Поэтому, конечно, тут ничего не получилось: воракс - честно запускал шельник /sqlcl/bin/sql и потом выдавал ошибку - ой что то оно не отвечает.

Плюс, не известно - как бы ещё SQLcl реагировал, на эти текстовые брекеты воракс-а, типа #set blockterm, которыми он окружает текст пользовательских команд, получаемых от пользователя.

Пробовалось вписать, в вызов явы, в функции run в шелнике <SQLcl-хоум>/sqlcl/bin/sql ехес-команду баш-а, т.е., вот так:

function run { if  [  "m$SQLCL_DEBUG" != "m" ]; then   echo "JAVA=$JAVA"   echo "JAVA_OPTS=${APP_VM_OPTS[@]}"   echo "DEBUG=$DEBUG"   echo "CPLIST=$CPLIST"   echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH"   echo "exec $JAVA  $CUSTOM_JDBC $CYGWIN "${APP_VM_OPTS[@]}" -client $SQLCL_DEBUG -cp "$CPLIST" oracle.dbtools.raptor.scriptrunner.cmdline.SqlCli " fiexec $JAVA  $CUSTOM_JDBC $CYGWIN "${APP_VM_OPTS[@]}" -client $SQLCL_DEBUG -cp "$CPLIST" oracle.dbtools.raptor.scriptrunner.cmdline.SqlCli "$@"}

Не выгорело, так же - сбоит.

Отдельно оговорюсь: в руби/яве - не понимаю ничего.

Собственно, зачем обращаю внимание собщества на этот проектик.

Интересны отзывы о этой штуке, может быть кто пользовался/пользуется, может рассказать плюсы/минусы которые с опытом работы становятся видны.

Ещё любопытно, как бы скрестить воракс-а с SQLcl, может быть есть, какие то, у кого то идеи об этом?

Так же, вопрос, какие ещё есть/знаете варианты работы с oracle-субд через cli-приложения, сопоставимые, или более продвинутые чем воракс, т.е.: коннекшен-менеджмент, обжект-браузер, и вот это вот всё.

Спасибо за внимание, ваше время.

Подробнее..
Категории: Ruby , Oracle , Vorax , Sqlplus

Cтоит ли учить Ruby

01.02.2021 06:08:37 | Автор: admin

Одним из популярных языков программирования можно назвать язык Ruby, с его не менее популярным веб-фреймворком Ruby On Rails.Однако, несмотря на его популярность, вам не стоит тратить на него время, и вот почему.

Рассвет Ruby на рельсах

В 2005 году на свет появился фреймворк Ruby On Rails, который на момент своего выхода наиболее удобно и просто реализовывал архитектурный шаблон MVC. И многие программисты осознали, что они теперь могут делать очень просто и быстро кастомные веб-приложения. Это был настоящий прорыв в сфере создания веб-приложений.

И огромное количество компаний (в основном стартапы), стали делать свои сервисы именно на языке Ruby, на фреймворке Ruby on Rails Netflix, Bloomberg, Airbnb, Groupon, Basecamp, GitHub, KickStarter и так далее.

В России и странах СНГ также огромное количество новых компаний в период 2008-2010, стали делать свои решения на ROR. Тогда были популярным в основном стартапы на туристическую тематику.


Переломный момент у Ruby

Успех Ruby On Rails не оставался незамеченным и на других языках программирования стали копировать решения из ROR. Так на Python появился Djnago, на PHP появился Laravel, а корпоративные фреймворке Spring (Java) и .Net (С#) стали брать удачные решения из ROR и внедрять в свои фреймворки.

И в какой момент на каждом языке программирования стало также удобно и просто разрабатывать кастомные веб-приложения, как в свое время на Ruby.

И тут стал появляться вопрос: Если везде также просто разрабатывать веб-приложения, то зачем выбирать именно Ruby с его рельсами. И на этот вопрос не нашелся ни один адекватный ответ.

  • Java и C# - предлагали корпоративную надежность и огромное количество разработчиков, готовых работать на этом стеке.

  • Python - подкупал своей универсальностью и элегантностью языка. Практически нет сферы, где нельзя было бы использовать python.

  • PHP предлагал безумную скорость разработки и относительную дешевизну рабочих рук.

Ruby не имел ни одного преимущества перед этими языками. У него был лишь веб-фреймворк Ruby on Rails, который все уже давно скопировали.

Как итог, новые стартапы стали выбирать другой стек для своих систем, популярность Ruby и спрос на него стали катастрофически падать. В итоге, если посмотреть сейчас какие новые стартапы запускаются на Ruby, вы вряд ли найдете громкие имена. Ruby проиграл.

Оценка рынка вакансий на Ruby

В России и странах СНГ Ruby никогда не был особо популярным стеком. Простой поиск по вакансиям дает крайне мало результатов, и большинство вакансий идет как раз в компании, что были основаны в период его популярности. Поэтому вам нужно быть готовым к тому, что основную часть своей работы вы будете тратить на поддержку существующих решений.

Однако тут есть небольшой плюс. В силу того, что специалистов на Ruby становится все меньше и меньше, а компании, которые уже завязались на Ruby, не могут с него слезть. Зарплаты на Ruby могут быть выше, чем на Python и PHP.

США и Силиконовая Долина

В США, а именно в Силиконовой Долине, ситуация с Ruby обстоит гораздо лучше. Там есть достаточно много крупных игроков, у которых код пишется на Ruby (смотрим самые известные копании на Ruby on Rails). Поэтому там есть приток свежей крови, там есть возможность переходить с одной компании на другую.

Другими словами, если вы хотите стать Ruby программистом, то вам нужно планировать переезд в Силиконовую долину, ведь только там есть стабильный спрос на Ruby программистов и какие-то перспективы на этом стеке.

Стоит ли учить Ruby в 2020 годах и выше

Если говорить объективно, то существующие программисты на Ruby могут зарабатывать неплохие деньги, так как ситуация на рынке позволяет им запрашивать более высокие зарплаты, чем у их коллег.

Если вы начинающий программист и только выбираете свой стек, то лучше выбрать что-то из PHP/Python/Java/C#. Эти языки активно развиваются, активно поддерживаются, и активно используются, как в легаси проектах, так и новых стартапах. Нет смысла изначально брать умирающий язык, каким бы крутым он не казался на первый взгляд.

УмретлиRubyиRuby on Rails

Все зависит от того, сможет ли язык предложить какие-то решение для новых проблем программирования. Мы все помним, как в свое время node.js взорвал рынок и привел к дикой популярности JavaScript. Как итог из узкоспециализированного языка JavaScript стали использовать везде: фронт, бекенд, микросервисы, десктоп-приложения, мобильные приложения.

Если на языке Ruby придумают что-то подобное, то это может привести к его возрождению и новой популярности. Однако, если этого не произойдет, он медленно умрет, как будут закрываться компании, которые держали на нем свою бизнес-логику.

Поэтому, смотрите на вакансии в своем городе, оценивайте их число и качество. Если по какой-то причине в вашем городе все вакансии именно на Ruby, то лучше выучить его, чем сидеть без работы. Однако, в большинстве случаев вакансий на Ruby будет мало.

p.s. здраво оценивайте рынок, не введитесь на рекламные лозунги продавцов курсов.

Подробнее..

PGHero дашборд для мониторинга БД PostgeSQL

23.03.2021 12:19:49 | Автор: admin

Всем привет. Сегодня я бы хотел поделиться рецептом установки утилиты PGHero с подключением нескольких баз данных. PGHero это простенькая утилита, написанная на Ruby, с минималистичным дашбордом для мониторинга производительности БД PostgreSQL.

Что может показать нам PGHero:

  • статистику по запросам: количество вызовов, среднее и суммарное время выполнения (с возможностью хранения истории);

  • активные в данный момент запросы;

  • информацию о таблицах: занимаемое на диске место, даты последних запусков VACUUM и ANALYSE;

  • информацию об индексах: занимаемое на диске место, наличие дублируемых/неиспользуемых индексов. Также может порекомендовать добавить индекс при наличии сложных запросов с Seq Scan;

  • статистику по открытым подключениям к БД;

  • вывод основных настроек БД, влияющих на производительность (shared_buffers, work_mem, maintenance_work_mem и т.д.)


Одна из очень удобных возможностей утилиты просмотр динамики среднего времени выполнения запросов (на основе статистики стандартного расширения PostgreSQL pg_stat_statements).

Выглядит это в интерфейсе PGHero вот так:


Настройка баз данных

Следующие шаги нужно проделать для каждой БД, которую мы собираемся подключать к PGHero.

Запросы нужно выполнять под суперпользователем.

  1. Устанавливаем расширение pg_stat_statements (если еще не установлено):

Откройте файл postgresql.conf в текстовом редакторе и измените строку shared_preload_libraries:

shared_preload_libraries = 'pg_stat_statements'pg_stat_statements.track_utility = false

Перезапускаем сервер PostgreSQL:

sudo service postgresql restart

Создаем расширение и сбрасываем статистику:

create extension pg_stat_statements;select pg_stat_statements_reset();
  1. Создаем в БД отдельного пользователя для PGHero (чтобы не давать утилите полные права над базой).

В следующем запросе заменяем эти значения в угловых скобках на свои:

<pghero_password> пароль для пользователя pghero;

<db_name> имя вашей БД;

<migrations_user> имя основной роли с доступом к текущей БД.

CREATE SCHEMA pghero;-- view queriesCREATE OR REPLACE FUNCTION pghero.pg_stat_activity() RETURNS SETOF pg_stat_activity AS$$  SELECT * FROM pg_catalog.pg_stat_activity;$$ LANGUAGE sql VOLATILE SECURITY DEFINER;CREATE VIEW pghero.pg_stat_activity AS SELECT * FROM pghero.pg_stat_activity();-- kill queriesCREATE OR REPLACE FUNCTION pghero.pg_terminate_backend(pid int) RETURNS boolean AS$$  SELECT * FROM pg_catalog.pg_terminate_backend(pid);$$ LANGUAGE sql VOLATILE SECURITY DEFINER;-- query statsCREATE OR REPLACE FUNCTION pghero.pg_stat_statements() RETURNS SETOF pg_stat_statements AS$$  SELECT * FROM public.pg_stat_statements;$$ LANGUAGE sql VOLATILE SECURITY DEFINER;CREATE VIEW pghero.pg_stat_statements AS SELECT * FROM pghero.pg_stat_statements();-- query stats resetCREATE OR REPLACE FUNCTION pghero.pg_stat_statements_reset() RETURNS void AS$$  SELECT public.pg_stat_statements_reset();$$ LANGUAGE sql VOLATILE SECURITY DEFINER;-- improved query stats reset for Postgres 12+ - delete for earlier versionsCREATE OR REPLACE FUNCTION pghero.pg_stat_statements_reset(userid oid, dbid oid, queryid bigint) RETURNS void AS$$  SELECT public.pg_stat_statements_reset(userid, dbid, queryid);$$ LANGUAGE sql VOLATILE SECURITY DEFINER;-- suggested indexesCREATE OR REPLACE FUNCTION pghero.pg_stats() RETURNSTABLE(schemaname name, tablename name, attname name, null_frac real, avg_width integer, n_distinct real) AS$$  SELECT schemaname, tablename, attname, null_frac, avg_width, n_distinct FROM pg_catalog.pg_stats;$$ LANGUAGE sql VOLATILE SECURITY DEFINER;CREATE VIEW pghero.pg_stats AS SELECT * FROM pghero.pg_stats();-- create userCREATE ROLE pghero WITH LOGIN ENCRYPTED PASSWORD '<pghero_password>';GRANT CONNECT ON DATABASE <db_name> TO pghero;ALTER ROLE pghero SET search_path = pghero, pg_catalog, public;GRANT USAGE ON SCHEMA pghero TO pghero;GRANT SELECT ON ALL TABLES IN SCHEMA pghero TO pghero;-- grant permissions for current sequencesGRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO pghero;-- grant permissions for future sequencesALTER DEFAULT PRIVILEGES FOR ROLE <migrations_user> IN SCHEMA public GRANT SELECT ON SEQUENCES TO pghero;

Установка и запуск PGHero

Допустим, у нас есть три таблицы: db_one, db_two и db_three. Мы хотим по всем трем отображать статистику в PGHero (вместе с историей запросов и размеров таблиц). Важный момент: для хранения истории запросов и размеров таблиц нужно завести в одной из баз данных отдельные таблицы, где будет храниться эта статистика.

CREATE TABLE "pghero_query_stats" (  "id" bigserial primary key,  "database" text,  "user" text,  "query" text,  "query_hash" bigint,  "total_time" float,  "calls" bigint,  "captured_at" timestamp);CREATE INDEX ON "pghero_query_stats" ("database", "captured_at");CREATE TABLE "pghero_space_stats" (  "id" bigserial primary key,  "database" text,  "schema" text,  "relation" text,  "size" bigint,  "captured_at" timestamp);CREATE INDEX ON "pghero_space_stats" ("database", "captured_at");

Мы будем хранить эти таблицы в БД db_one (хотя можно завести отдельную базу для этой статистики). Далее создаем на сервере файл конфигурации pghero.yml со следующим содержимым (подставляем актуальные настройки):

# Конфигурационные урлы для наших БДdatabases:  db_one:    url: postgres://pghero:secret_pass@mydomain.ru:53001/db_one  db_two:    url: postgres://pghero:secret_pass@mydomain.ru:53001/db_two    capture_query_stats: db_one  db_three:    url: postgres://pghero:secret_pass@mydomain.ru:53001/db_three    capture_query_stats: db_one# Минимальная длительность запросов (в секундах), которые будут считаться долгимиlong_running_query_sec: 60# Минимальная длительность запросов (в миллисекундах), которые будут считаться медленнымиslow_query_ms: 250# Минимальное кол-во вызовов запросов, которые будут считаться медленнымиslow_query_calls: 100# Минимальное количество соединений для показа предупрежденияtotal_connections_threshold: 100# Таймаут для explain-запросовexplain_timeout_sec: 10# Нормализация запросов (замена значений запроса нумерованными параметрами)filter_data: true# Basic авторизацияusername: pgheropassword: secret_pass# Таймзонаtime_zone: "Europe/Moscow"

Переходим к установке. Документация предлагает нам несколько способов:

  1. Docker-контейнер;

  2. отдельная служба на Linux;

  3. gem-пакет Ruby;

Мы будем использовать первый способ запуск в виде Docker-контейнера. Для этого в папке с файлом конфигурации pghero.yml нужно добавить Docker-файл с таким содержимым:

docker build -t mypghero .docker run -ti -p 12345:8080 mypghero

Теперь собираем образ на основе Docker-файла и запускаем контейнер на нужном порту:

docker build -t mypghero .docker run -ti -p 12345:8080 mypghero

Теперь дашборд должен быть доступен по адресу http://123.45.67.89/12345. Не забывайте про basic-авторизацию, логин и пароль мы указывали в pghero.yml.


Запуск cron-jobs для сохранения истории

Последний этап: нужно настроить автозапуск по крону скриптов для сохранения в БД истории по запросам (capture_query_stats) и размерам таблиц (capture_space_stats).

Документация рекомендует запускать capture_query_stats раз в 5 минут, а capture_space_stats раз в сутки (но тут нужно решать по ситуации). Запускаем в командной строке crontab -e и добавляем строки для запуска скриптов:

*/5 * * * *     /usr/bin/docker run --rm my-pghero bin/rake pghero:capture_query_stats15 2 * * *     /usr/bin/docker run --rm my-pghero bin/rake pghero:capture_space_stats

Вот и всё. Спасибо за внимание.

Демо-версию утилиты можно посмотреть здесь. Исходный код и документация.

Подробнее..

Генерация картинок в коде

08.04.2021 02:10:23 | Автор: admin

Всем привет! В этой статье я расскажу как разрабатывали решение по автоматизации генерации картинок из шаблона. Сначала будет обоснование кейса и затем техническая часть решения.

Обоснование кейса

Не секрет что соцсети являются огромной площадкой для поиска аудитории что бы позитивно выделить свой материал в ленте сделать его узнаваемым и вызывающим желание кликнуть. нужно правильно оформить на странице The Open Graph тэги.

пример с установленными og и без нихпример с установленными og и без них

страница без og тегов выглядит довольно куцо в соцсети или мессенджере, просто строчка с текстом и всю информацию о том что внутри вы узнаете только перейдя по ней. Настроив og теги вы сразу же можете презентовать свой сайт. и сделать его узнаваемым и выделяющимся ещё до того как пользователь зайдёт к вам. Важной частью этой узнаваемости конечно является изображение. Хорошо если у вас лендинг и вам достаточно одной картинки на весь сайт. Но если у вас ресурс с огромным количеством контента то нужно как то разнообразить эти изображения и сделать уникальными, вооружившись пэинтом фотошопом можно без проблем сделать такую картинку, но процесс этот довольно рутинный и скучный и всё таки требует времени, но его можно автоматизировать. В этом месте и появляется задача генерировать такие изображения автоматически. Работая в charmer мы делаем проекты на высоком визуальном уровне и изображения для расшаривания в соцсетях и мессенджерах являются важной частью. Давайте перейдём к описанию решения.

Техническая часть

Все кто пытался редактировать изображения в коде, скорее всего сталкивался с imageMagick решение уже зарекомендовавшее себя, есть обертки под все популярные языки, до сих не теряет актуальности поддерживается и развивается. Позволяет делать магию с картинками, закрывает практически все потребности в обработке картинок. Что же не так с ImageMagick? Он невероятно сложен в использовании. Если нужно сделать относительно сложный визуал

Например как тут

то в imageMagick будет довольно тяжело всё это выстроить. Учитывая то что изображение для фона, не всегда подходят по размеру и пропорциям под итоговый вариант. И наложенный текст может быть любой длинны. Так же в тексте могут быть важные типографические контролы, например неразрывный пробел.

В общем достаточно сложно сделать какой то шаблон для изображений который будет собираться в imageMagick. Но существует уже отличное готовое и всем известное решение для верстки по шаблону это - HTML.

Как можно применять HTML и Headless browser можно увидеть на схеме ниже.

Принципиальная схемаПринципиальная схема

Первым делом создается знакомый всем rails разработчикам ERB template. По нему генерируется html файл и запускается headless browser, исполнение bash команд в руби реализовано в Kernel. Дальше браузер сохраняет скриншот в файл и готово. Мы получили результирующую картинку.

Производительность

Запуск внешнего приложения в коде звучит как задача которую нужно явно запускать в фоновом режиме. Для этой цели отлично подходит sidekiq. Возможно у вас chrome тоже вызывает устойчивую ассоциацию с избыточным потреблением памяти, но как показали тесты, в headless режиме память расходуется в щадящем режиме.

На видео видно потребление ресурсов при работе приложения. Заметно как резко подскочило цпу, но это настраивается уже через sidekiq на видео в нем паралельно запускаются несколько воркеров, код для генерации картинок потокобезопасный. Если настроить на один поток то потребление ресурсов будет гораздо скромнее. За 20 секунд сгенерировалось и сохранилось 50 изображений, что достаточно быстро для такой задачи.

Итоги

Конечной целью было создание инструмента который позволит решать описанную задачу быстро и надежно. При написании кода упор был сделан на минимальное конфигурирование, потокобезопасность и простоту в использовании. Исходники доступны на гитахбе и сам гем на rubygems. Если для кого то этот гем будет полезным и найдутся идеи и желание как то улучшить\исправить с радостью приму PR или комментарии) Всем спасибо, кто дочитал до конца!

Подробнее..

Artificial Intelligence, герой нашего времени. Этюд

11.04.2021 02:13:35 | Автор: admin

Хм. Один из пунктов, регламентирующих действия модераторов на Хабре, сформулирован следующим образом: не надо пропускать статьи, слабо относящиеся к IT-тематике или не относящиеся к ней вовсе. Что сходу заставило автора призадуматься, а имеет ли прямое отношение к "IT-тематике" его пост, повествующий о некоторых этапах программирования забавного и увлекательного своего pet-проекта, несложного AI, выстраивающего нейронную сеть на основе ruby-обертки FANN для игры в крестики-нолики? Вопрос не содержит скрытого кокетства, ведь описанию логики программного кода в моем рассказе предназначено далеко не первостепенное значение. "Да это злая ирония!" скажете вы. Не знаю.

ОК. Данная разработка де-факто является иллюстрацией ряда странных наблюдений автора, некоторое число знакомых и даже друзей-приятелей которого в последние годы... заставили его припомнить уроки литературы своей когда-то в бытность очень-очень средней советской школы. Несмотря на перманентное его убеждение в том, что "проходить" всегда возможно только лишь мимо чего-то - некие персонажи русской классики вспоминаются с течением времени все чаще. Или, может статься, так и должно быть?

Итак, с места в карьер... после первого запуска программа начинает процесс самообучения, проигрывая сама с собой несколько десятков (минута - две максимум) тысяч партий (число, понятное дело, доступно для редактирования в конфиге; учитывая описываемый далее не вполне обычный алгоритм, положенный в основу логики этого AI - эксперименты такого рода также способны предоставить интересный материал для умозаключений). Здесь имитируется процесс обучения, свойственный многим другим Artificial Intelligence, с той лишь разницей, что оба "игрока" в равной степени играть не умеют, делая абсолютно рандомные ходы. Но правила игры действуют: если случайный ход не соответствует, программа обязана переходить, соответственно и выигрыш достанется той стороне, которая выиграет. Все честно: никаких подчисток и хаков, скрытых предпочтений, никаких тебе фейковых допинг-проб, зачастую в реальной жизни опрокидывающих результаты спортивных игр.

Далее начинается игра с пользователем: логированный в csv-файл протокол игр преобразуется в массив, и AI, играющий вторым номером (ноликами) решает философическую, до странности в чем-то очень российскую задачку, пытаясь выудить из абсурда и хаоса случайных ходов те, которые позволят выиграть или как минимум свести к ничьей игру с живым и вполне логично мыслящим противником.

Забавно, не правда ли? В процессе кодинга из головы не выходил диалог с одним из приятелей, чье мироощущение носит отчетливые черты героев Франца Кафки: весь мир для него состоит из случайных, заведомо неподвластных логическому анализу проявлений. Интересно, что любые попытки объяснить ему суть понятия аппроксимирующих функций встречают яростный отпор, полнейшее эмоциональное (думаю, здесь что-то навроде фрейдовского "вытеснения") неприятие: из многообразия значений любой жизненной "функции" моему приятелю свойственно выдирать сугубо одно, которое и представляет для него в дальнейшем психологическую ценность в качестве результата такой вот своеобразной интерполяции... кодинг игрушки, о котором этот рассказ, в немалой степени проходил под впечатлением нашего с ним общения.

Если есть на Хабре парочка-другая читателей, которым психология ближе (я не про эйчаров), нежели программирование - сказанное легко облечется для них в канву профессиональной терминологии. Но описываемый мной психологический сценарий - крайность... частные случаи которого, менее заметные и разительные - встречаются, на мой субъективный взгляд, очень часто.

Итак. Примем на минутку предложенную точку зрения: мир заведомо непознаваем, события случайны и призрачны. Опереться, таким образом, не на что, у нашей программки практически нет точек опоры в виде той или иной стратегии, она располагает лишь записями случайных ходов, каждая из которых снабжена, правда, еще и сопутствующей информацией: общее количество ходов и итог игры (выигрыш/проигрыш). Сумеет ли наш виртуальный игрок-нигилист, отказавшийся от несложной и эффективной логики игры на основе известных стратегий Tic Tac Toe - построить собственную стратегию, хотя бы мало-мальски успешную? Оказывается - да, вполне. Полученный результат сложно назвать инновационным и многообещающим, это, скорее, пародия на образ мыслей современного кафкианца, чем-то напоминающая историю барона Мюнхгаузена, тщащегося вытащить самого себя из болота за волосы, помните?... кстати, слово "болото" здесь удачно продолжает использованную аналогию; повторюсь, "точки опоры" у значительной части нашего с вами современного социума, как показывает житейская практика - "при наличии отстутствия", данное утверждение легко проверяется на многочисленных параноидальных мифах, от отрицания ковида и до злополучного "а вот не докажете!".

Попробуем аргументировать сказанное, хотя бы в контексте простенького нашего Artificial Intelligence. Как думаете, какой ход в любой момент игровой ситуации на поле 3x3, используемом для игры в крестики-нолики, является безусловно оптимальным? Или, иными словами, если у вас перед глазами лог игры, что именно вам необходимо, чтобы, задержав взгляд на строчке, описывающей очередной ход, и не читая далее - уверенно заявить, что в данной ситуации этот ход наилучший? Поставьте себя на место AI, вся "интеллектуальная мощь" которого заключена в нескольких коротких скриптах; здесь необходимо что-то совсем простое и безошибочное, без долгих логических рассуждений и необходимости просчитывать на несколько ходов вперед.

Хм, "и очень даже просто". (с) Если в логе случайно отыгранных игр присутствует хотя бы одна запись, где ход является последним, он в данной ситуации - наилучший. Не правда ли? Вот вам и вся логика, на основе которой начинаем формировать веса нейронной сети:

        if row[6].to_i - row[3].to_i == 1            x_data.push([row[0].to_i])            y_data.push([1]) # Присваиваем высший приоритет, т.е. максимально возможный вес, переопределяя начальный.        end

А как отыскать и исключить худший из возможных ходов? - также несложно. Если ход предпоследний, т.е. выигрывает ваш противник. Возражений нет?

Внезапно, в самый разгар работы над Tic Tac Toe AI with Neural Network пазл сложился (это я уже не о кодинге). Разгадка оказалась удручающе простой, но путь к ней длинен и непрост: суть в том, что ни малейших попыток понимания в данном случае как и в случаях иных не было у моего знакомого и в помине. Странный объект моих отнюдь непрограммистских изысков жил в собственном мире, будто в бункере, видя во внешних объектах лишь проекции, разнообразные и разрозненные частички самого себя.

Сама собой напрашивается вторая аналогия, проиллюстрировать которую техническим языком, подобно первой - не позволят, пожалуй, скромные ресурсы моего компьютерного железа. Такого рода психотип, вероятно, можно сравнить с черной дырой, за гравитационный горизонт которой способно вырваться очень и очень немногое... нет?

Поясню. Понимание"... скажите, как вы понимаете этот термин? в целях экономии времени приведу краткую, в рамках википедии, формулировку: универсальная операция мышления, связанная с усвоением нового содержания, включением его в систему устоявшихся идей и представлений. Ирония ситуации в том, что нового содержания у моего приятеля не было и быть не могло; нет для него никаких внешних объектов, которые возможно было бы постигать и далее включать в систему идей и представлений. Существует только он один или, вернее сказать, он в центре; все остальное вокруг представляется невзрачными тенями, проекциями тех или иных его аффектов. Звучит абсурдно, но, увидев на столичной улице очередную автомобильную пробку, забитую отнюдь не бюджетными авто, персонаж моих психоаналитических исследований неизменно приходил к выводу о том, что экономического кризиса в стране нет, и быть, в силу им увиденного, не может: никакой статистики или аналитики не существует, для меня есть только то, что я вижу или могу потрогать.

Возвращаемся к коду. К сожалению, дальше все несколько сложнее, чем то, с чего начали. Чтобы не увеличивать количество рандомных партий, служащих материалом для анализа в ходе игры, и не слишком увлекаться логической эквилибристикой на пустом месте - нам приходится создать парочку костылей, призванных помогать нейронной сети определять веса для ряда игровых ситуаций... в качестве оправдания, таким образом - соображение, что, вытаскивая самого себя за косичку из болота, Мюнхгаузен ведь обладал знаниями и эмпирическим опытом взрослого человека.

Немалую опасность для живущего в мире иллюзорной Матрицы виртуального игрока в крестики-нолики представляют вилки (просчитывать ситуацию на доске хотя бы на один - два хода вперед явно не наш life style). Что же, поиском вилок сейчас и попробуем заняться:

  WINNING_TRIADS = [    [0, 1, 2],    [3, 4, 5],    [6, 7, 8],    [0, 3, 6],    [1, 4, 7],    [2, 5, 8],    [6, 4, 2],    [0, 4, 8]  ].freeze

Далее, при формировании csv-лога ходов, ищем:

  def fork?    WINNING_TRIADS.select do |x|      @board[x[0]] == @board[x[1]] && @board[x[2]].class != @board[x[0]].class &&        place_x?(x[0]) ||        @board[x[1]] == @board[x[2]] && @board[x[0]].class != @board[x[2]].class &&          place_x?(x[1]) ||        @board[x[0]] == @board[x[2]] && @board[x[1]].class != @board[x[2]].class &&          place_x?(x[0])    end  end

Таким образом, если комбинация найдена два раза...

  if @game.fork?.size > 1

...вилка найдена.

Ок, работает. Хотя данный способ не учитывает следующего обстоятельства: вполне возможно, ваш ход приводит к возможности вилки лишь условно, а на практике противник вынужден сделать совсем иной ход, дабы не позволить вам следующим ходом выиграть. Что же, это решаемо.

Определим ряд потенциально опасных ситуаций:

  DANGEROUS_SITUATIONS_1 = [    [6, 4, 2],    [0, 4, 8]  ].freeze  DANGEROUS_SITUATIONS_2 = [    [0, 4, 7],    [0, 4, 5],    [2, 4, 3],    [2, 4, 7],    [3, 4, 8],    [1, 4, 8],    [1, 4, 6],    [5, 4, 6]  ].freeze
  def fork_danger_1?    DANGEROUS_SITUATIONS_1.detect do |x|      @board[x[0]] == @board[x[2]] &&        @board[x[0]] != @board[x[1]]    end  end    def fork_danger_2?    DANGEROUS_SITUATIONS_2.detect do |x|      @board[x[0]] == @board[x[2]] &&        @board[x[0]] != @board[x[1]]    end  end  def fork_danger_3?    DANGEROUS_SITUATIONS_1.detect do |x|      @board[x[0]] != @board[x[2]] &&        @board[x[1]] == @board[x[2]]    end  end

И, соответственно, создадим три массива, в которые, при анализе ситуации на доске, AI станет помещать удовлетворяющие условиям ходы: 1. однозначно неприемлемые, 2. потенциально приводящие к вилке и 3. атакующие (т.е. те, в силу которых противник вынужден, во избежание немедленного проигрыша, реализовать единственно возможный для него ход). Разумеется, массивы будут иногда пересекаться, учтем это при построении логики игры. Кроме того, последнее слово за Neural Network.

  array_of_games.each do |row|      row.each do |e|        next unless e == current_position        if row[6].to_i - row[3].to_i == 2 && row[4] == 'O' && row[2].to_f != 0.2          unacceptable_moves_array << row[0]        # Find moves that inevitably lead to a fork:        elsif fork_danger_1 && row[3].to_i == 3 && row[0].to_i.odd?          unacceptable_moves_array << row[0]        elsif (fork_danger_2 || fork_danger_3) && row[3].to_i == 3 && row[0].to_i.even?          unacceptable_moves_array << row[0]        end        next if row[5].nil?        # Find moves that may lead to a fork:        array_of_moves_to_fork << row[0] if row[3].to_i == row[5].to_i        # Find attacking moves:        attack_moves_array << row[0] if row[3].to_i == row[5].to_i && row[6].to_i < 7      end    end

Повторюсь, удалось бы обойтись без костылей, если бы массив игр, используемый AI для анализа, не формировался практически полностью рандомно. Но... я ведь оговорил с самого начала, данный программный код родился как иллюстрация рефлексий автора, родившегося в стране Онегина, Печорина, Базарова... к слову, герои "Бесов" Достоевского и несколько более симпатичный Феличе Риварес из книги Войнич тоже ведь в этом перечне. Некий исторический сарказм присутствует в том, что, судя по прочитанному и перечитанному уже много позже школы - российский нигилизм претерпел значительные изменения в своей, так сказать, результирующей... не замечали? - а вы припомните незабвенное "разговаривают, разговаривают, контрреволюция одна", сумеете проследить немало аллюзий и аналогий с нашим днем.

 array_of_games.each do |row|      row.each do |e|        next unless e == current_position        next if arrays[0].include?(row[0])        unless arrays[1].include?(row[0]) && !arrays[2].include?(row[0])          if row[6].to_i - row[3].to_i == 1            x_data.push([row[0].to_i])            y_data.push([1])          elsif row[6].to_i - row[3].to_i == 3            if arrays[2].include?(row[0])              x_data.push([row[0].to_i])              y_data.push([0.9])            elsif arrays[1].include?(row[0])              x_data.push([row[0].to_i])              y_data.push([0.3])            end          else            x_data.push([row[0].to_i])            y_data.push([row[2].to_f])          end        end      end

Сухой остаток скармливаем нейронке:

    data = nn_data(board, fork_danger_1, fork_danger_2, fork_danger_3, array_of_games)    fann_results_array = []      train = RubyFann::TrainData.new(inputs: data[0], desired_outputs: data[1])      model = RubyFann::Standard.new(        num_inputs: 1,        hidden_neurons: [4],        num_outputs: 1      )      model.train_on_data(train, 5000, 500, 0.01)      data[0].flatten.each do |i|        fann_results_array << model.run([i])      end    result = data[0][fann_results_array.index(fann_results_array.max)]

Интересная деталь: в одной и той же игровой ситуации на доске (и с одним и тем же csv-файлом) этот Neural Network способен выдавать различные варианты ходов.

В итоге - у вас максимум ничья, минимум - проигрыш, выиграть не получится. Разве что подведет рандомно сгенерированный csv-файл (такое случается, но нечасто), который в редком случае вашего выигрыша оптимально пересоздать. Впрочем, описанная ревизия кода - или не только кода - может статься, вовсе не окончательная, итоги подводить рано.

P.S. Описанный код всегда доступен полностью (а не фрагментарно, как диктует формат статьи) в моем гитхабе, разумеется, любой желающий может сделать git clone и поэкспериментировать с кодом, ну или просто поиграть. Я не сторонник запуска ruby-application под виндой, это очень не лучшая идея, но в данном случае работать будет, попробовал. Возможно, получится чуть менее эффектно, чем в консоли линукса, но логика отработает.

Подробнее..

Веб-клиент Google Cloud Text to Speech за завтраком в бастионе Сен-Жерве

13.04.2021 04:20:54 | Автор: admin

С самых юных лет, с самого первого прочтения залпом "Трех мушкетеров" автор неколебимо убежден, что оптимальный способ обучения чему-либо - практика. Теорию в дальнейшем можно подучить и подточить, друзья-мушкетеры "покажут несколько славных приемов", не проблема. Но, если верить досточтимому мэтру Дюма (а автор ему верит), оптимальный способ интеграции в любую новую для себя сущность - сходу погрузиться в нее с головой, а там как пойдет. Где наша не пропадала, да и, как известно, любой гасконец с детства академик.

Вообще, если интерес возникнет, то это всегда 90% успеха, поверьте... ну, а если не возникнет, что ж. Сэкономите время: стало быть, не ваше. Сейчас, таким образом, самонадеянный и скорый на подъем аффтор, всегда готовый выхватить шпагу при виде гнусного тролля на любом интернет-форуме - предлагает всем dbutants потратить всего лишь полчаса-час на то, чтобы заинтересоваться сразу несколькими технологиями, в числе которых язык программирования Ruby, API Google Cloud Text to Speech, облачная PaaS-платформа Heroku и git.

К слову. Предвидя сделанные на языке растреклятых англичан, исконных врагов любого истинного француза комментарии в стиле "Is ruby dead?", в том смысле, а есть ли смысл вообще этим заниматься... автор предлагает всем любителям потрепаться-ни-о-чем-в-инете временно оставить эту животрепещущую тематику, сменив ее на рекомендации по изготовлению чудодейственного бальзама, наподобие того, что дала в путь-дорогу д`Артаньяну любящая его матушка, и который помог бы, в духе дня, раз и навсегда избавиться от спама за подписью того или иного эйчара, русскоговорящего или европейца/американца, несколько раз в неделю присылающих абсолютно ненужные автору инвайты на позицию Ruby Developer. Ненужные не потому, что автор, вволю напрактиковавшись и слегка "подточив" теорию, привык получать приглашения исключительно и самолично из рук аж самого CTO Armand-Jean du Plessis, duc de Richelieu... а потому, что за все годы работы - ни одного проекта, ни одной должности от HR он не получил, так уж сложилось.

Чего и вам от души желает. Если какой-либо институт и мертв, то это почти наверняка Human Resource, по крайней мере, у нас в России (как с этим обстоят дела у Бекингема - Бог весть, не знаю). Все остальное покамест работает... это была, так сказать, литературная прелюдия, ну а теперь сходу к практике, с места в карьер. "Сударь, вы ошиблись! Нас не трое, нас четверо!"

Итак. Цель сегодняшних наших упражнений - построение приложения, работающего с API Google Cloud Text to Speech, иными словами - конвертера текста в звук, и с очень неплохим качеством. Правда, все чаще раздаются голоса, дескать, IBM Text-to-Speech API круче, но это мы оставим, с вашего позволения, для следующей статьи... "голоса" обычно не скрывают, что IBM API обходится недешево, гугловский же сервис возможно использовать практически бесплатно (находим и внимательно читаем Terms of Service). Но вам понадобится волшебный ключ, нечто в стиле "то, что сделал предъявитель сего, сделано по моему приказанию и для блага государства" в формате JSON, для получения которого, вполне возможно, придется засветить ваш MasterCard здесь. Может быть, даже заморозят на недельку кровный ваш $1, ничего? - ну всяко это не так страшно, как в военное де-факто время совершать вояж за линию фронта, будучи вдохновленным одним лишь лукавым взглядом г-жи Бонасье, согласитесь.

Также зарегистрируйте Free account Heroku, куда мы с вами намереваемся пушить ваше первое приложение на основе фреймворка Ruby on Rails, скачайте и установите git и Heroku toolbelt для своей OS.

Немного о структуре приложения. Как уже сказано выше, это rails-app, полноценный веб-интерфейс для API Google Cloud Text to Speech: аутентификация реализована посредством device (полистайте доку, там немало интересного на случай, если захотите что-то изменить в предлагаемом техническом решении), сконфигурированного таким образом, что возможен лишь один пользователь. Что нам и нужно: сразу после деплоя приложения на Heroku вы зарегистрируетесь в нем, подтвердив указанный вами email, и дальнейшие регистрации будут невозможны (изменить или сбросить пароль вы, при необходимости, сможете).

Интерфейс выполнен в духе минимализма, под которым автор понимает Bootstrap 4 и кое-какие джаваскрипты; информационные flash-сообщения панели управления - средствами ajax, благо он как никто родной для Ruby on Rails. Заказчик-киевлянин, для которого был выполнен этот rails-app, он же старый мой приятель, скупой как кардинал Мазарини - очень просил "без излишеств, можно вообще без стилей", ну и вот... впрочем, помимо ожидания, получилось вполне элегантно. Представленный далее короткий ролик не самый новый, с момента его создания приложение было рефакторено и получило новые свойства, но какое-то визуальное представление способен дать.

Да, и "о фичах". На момент публикации этого материала Google-Cloud-TTS-Rails способен работать с текстом (также поддерживается SSML) на любом из 18 языков, конвертируя в один из следующих, по желанию, форматов: MP3 (MPEG Audio Layer III), WAV (LINEAR16) and OGG (OGG_OPUS), поддерживаются оба доступных voice type: WaveNet и Basic. Также интерфейс приложения позволяет корректировать скорость произношения...

...с чего, пожалуй, и начнем этот краткий экскурс в программный код. Меню регулировки скорости реализовано как хелпер, посредством которого получаем в HTML выпадающее меню (drop-down list), диапазон значений от 0.25 до 4.0 (обусловлено API), шаг 0.25, значение по-дефолту 1.0. Привыкайте, это рельсы:

module SoundHelper  def speaking_rate    select_tag 'speaking_rate', options_for_select(      0.25.step(by: 0.25, to: 4.0), selected: '1.0'    ), { class: 'btn' }  endend

Да, к слову. Те, кто не хотят вникать в код, возможно, им это сто лет не нужно... имеют возможность пропустить вышесказанное/поименованное мимо, соответственно, ушей и глаз, равно как и еще парочку фрагментов кода, воспоследующих далее. Имеете полное право, почему нет. Я сейчас сделаю краткую паузу на то, чтобы in two words рассказать, как залить из гитхаба на Heroku полностью готовое к работе приложение, вам понадобятся на вашем рабочем компе лишь Heroku CLI и git, как уже и говорил. Ruby и postgreSQL в этом случае без надобности.

Клонируйте приложение с гитхаба и по окончании перейдите во вновь созданный каталог, набрав в консоли:

git clone https://github.com/cmirnow/Google-Cloud-TTS-Rails.gitcd Google-Cloud-TTS-Rails

Бросьте файл, содержащий ваш персональный ключ YOUR_KEY_NAME.json, в корень директории приложения (название значения не имеет). Далее:

git add .   git commit -m "my first commit"heroku creategit push heroku masterheroku run rake db:migrate

Откройте панель администрирования Heroku, YOUR_NEW_APPLICATION -> 'Settings' -> 'Reveal Config Vars', и введите следующие пары key/value:


key: GOOGLE_APPLICATION_CREDENTIALS value: YOUR_KEY_NAME.json

key: DOMAIN_NAME value: YOUR_HEROKU_DOMAIN ### i.e 'https://***************.herokuapp.com' without quotes.

key: GMAIL_USER_NAME value: YOUR_GMAIL_LOGIN

key: GMAIL_PASSWORD value: YOUR_GMAIL_PASSWORD ### (An App Password is a 16-digit passcode that gives an app or device restricted access to your Google Account without having to divulge your personal password and complete access to your Google Account).


Как видите, потребуется указать доступ к почтовому серверу, чтобы Google-Cloud-TTS-Rails смог отослать вам письмо в ходе регистрации аккаунта, также на случай необходимости сброса забытого пароля. И - на этом все, после регистрации приложение полностью готово к работе. Лимит текста "за один раз" - 5000 знаков, обусловлено Google. Надеюсь, вам понравится.

Мне же осталось показать всего только очень несложную логику работы приложения. Сейчас не буду подробно останавливаться на rails-валидациях, скажу лишь, что по дефолту проверяется только количество введенных знаков, и вы сможете добавить или исключить, при желании, самые разнообразные проверки:

class Validationinclude ActiveModel::Modelattr_accessor :requestvalidates :request, presence: true, length: {in:3..4999}end

Контроллер, как ему и предназначено судьбой, служит связующим звеном между интерфейсом пользователя и собсно сервисом, вызывая метод класса TtsConversion со следующими параметрами:

  def conversion    audio_format = TtsConversion.index(client, synthesis_input, voice, audio, params[:codec])    success_info(audio_format)  end  def client    Google::Cloud::TextToSpeech.text_to_speech  end  def synthesis_input    { params[:text_or_ssml] => params[:request] }  end  def voice    { language_code: params[:lang], name: params[:voicename] }  end  def audio    { audio_encoding: params[:codec], speaking_rate: params[:speaking_rate].to_f }  end
class TtsConversion  def self.index(*args)    response = args[0].synthesize_speech input: args[1], voice: args[2], audio_config: args[3]    File.open 'public/output/output.' + audio_format(args[4]).to_s, 'wb' do |file|      file.write response.audio_content      audio_format(args[4]).to_s    end  end  def self.audio_format(codec)  case codec  when "LINEAR16"  'wav'    when "OGG_OPUS"      'ogg'  else  'mp3'  end  endend

Всю работу свершает, по сути, gem 'google-cloud-text_to_speech', передавая конвертируемый текст и выбранные параметры в API Google Cloud Text to Speech и получая обратно звук в цифре. Вот, пожалуй, и все.

Подробнее..

Каперство в IT. В подражание Киплингу

04.05.2021 02:20:16 | Автор: admin

Статья имеет своей целью не только и не столько попытку анализа крайне субъективной сущности термина Team leader, лидер команды, проведя некую полушутливую аналогию с капитаном боевого корабля в условиях морского сражения (почему бы и нет, кстати..?), но и в очередной раз заострить внимание профессиональной аудитории на невероятно убогом (целиком принимая на себя ответственность за использование данного термина) состоянии рынка труда в IT в двух отдельно взятых странах - России и Украины. Белоруссию обойду молчанием, ситуация там иная, как мне кажется... но, что касается первых двух - автор склонен сейчас сделать крайне непопулярное, вероятно, заявление, сказав, что мы действительно один народ. Во всяком случае - когда разговор заходит о соблюдении законов и, в частности, законов о труде.

В качестве кратенького лирического отступления перескажу содержание своего диалога с неким американцем, носителем языка на English Courses, которые некогда посещал. Угораздило меня попросить почтенного препода прокомментировать суть gray salary, дескать, имеет ли подобное место быть в твоей Америке, нет? - он сперва меня попросту не понял, готов предположить, виной тому был уровень моего английского, образчика "лэт ми спик фром май хард"; а, когда наконец дошло (ну, "они тупые", как не один десяток лет восторженно декларировал с эстрадных подмостков некий наш "актер") - бесхитростно расхохотался мне в лицо.

Ох уж эти всегда непосредственные и непредсказуемые янки, все-то в мире беды от них. Коль скоро в политическом тренде в России сейчас Редьярд Киплинг, позволю себе процитировать его строчки, посвященные одному из (не по рождению) американцев, очень недурно, кстати, отличившемуся, воюяя с турками на российской стороне в чине контр-адмирала:

В нём проказа внутри, хоть снаружи он бел, и может учуять любой

Запах мускуса, что владелец рабов всегда несёт за собой.

Забегая вперед, к рабам мы еще вернемся... а вот ответ, интонацию которого я попытаюсь при переводе адаптировать на российский лад так, как ее услышал, прозвучал примерно следующим: "Дорогой мой... да, в национальных диаспорах, кое-где у нас порой, Куба, мать ее, месканы/арабы, что скрывать... но - это исключение, подтверждающее жесткое правило: в Америке нет серых зарплат, it's impossible".

Далее перехожу к эпизоду, о котором хочу рассказать. Упоминания о сталинградской битве в моем тексте, ровно так же, как и у Киплинга, не будет иметь места быть; увы, это единственное, что ставит нас на одну доску по части литературного таланта, поэтому я по большей части процитирую, без купюр и as it is, электронную переписку, сохраненную в моем архиве как воспоминание об испытательном сроке на позицию Ruby Developer украинской компании Kodo Labs, Одесса (поставил в известность своего корреспондента о намерении публикации). Предваряя разгневанные комментарии в духе неприемлемости и безграмотности подобного рода параллелей/экстраполяций на "весь рынок труда", начну со следующего утверждения: в отличие от Америки, второй родины безжалостного капера, работорговца и кавалера ордена Св. Анны, потемкинского контр-адмирала Джон Пол Джонса, у нас подобное является именно правилом, а не исключением. Go ahead, my friends.

Непонимание и разногласия возникли у нас практически сразу:

29.04.2021 17:23, Alexey пишет:

Привет. Юрий, не так. Взаимопонимание возможно при одном условии, необходимом и, вероятно, достаточном: обе стороны признают существование неких констант, трактовка которых, с обеих же сторон, одинакова. Недаром любой договор начинается с определения терминологии. Любые, как ты их называешь, "условные понятия" - в любой договоренности суть попытка манипуляции, что заведомо не способствует взаимному доверию.

В качестве такой константы / точки отсчета я всегда предлагаю принять правовое поле, что диктуется не только проявлением уважения к законам страны, в которой живешь и работаешь, но и, не в последнюю очередь, самоуважением. Ты же мне говоришь, что договоров у тебя нет, но, тем не менее, ты хочешь контролировать рабочее время тех, кто де-факто и де-юре работает на твоем предприятии. Причем речь именно о контроле, что ты и сам признаешь, цитирую: "И дело вовсе не, или не только, в контроле..."

Ты это всерьез, про гречку? - забей, не аргумент. Попытка провести аналогию между учетом рабочего времени и килограммами продаваемого товара я даже комментировать не буду, несерьезно. И крайне странно слышать подобную сентенцию от руководителя компании.

Предлагаю следующее. В основу сотрудничества ставим не "условные понятия", которые оставляют возможность многозначных трактовок и, как следствие, злоупотреблений (с обеих сторон), а четкое понимание правовой основы, это стандартная позиция нормального человека в нормальном цивилизованном мире (я, разумеется, не ************ имею в виду): договор у нас с тобой не трудовой, а партнерский, у нас это называется ГПО (гражданско-правовые отношения), и в Украине есть аналог. Такого рода договор (хотя бы и изустный, не имеет значения) предопределяет равноправие обеих сторон, что исключает тот самый контроль, к которому ты так стремишься.

Разумеется, я готов, как это и принято в большей половине украинских компаний, вручную указывать затраченное время. Это некритично; значение имеет именно взаимопонимание, о котором ты сам сказал. Аналогия человеко-часов с килограммами (или попытка представить 15 лет работы компании, перипетии которой мне неведомы, в качестве "гарантии") суть правовой и логический абсурд, тебе то же самое скажет любой юрист, и строить работу на таком шатком фундаменте я не стану: "опыт, сын ошибок трудных".

Поясню. Стойкое неприятие вызвала (как и всегда в аналогичных случаях, имя которым, повторюсь, легион на просторах вставшей с колен державы) следующая сентенция тимлида / фаундера Kodo Labs, от которого я получил предложение принять участие в проекте, цитата: "У нас в компании минимум бюрократии. У нас нет НДА или договоров, потому что все это не дороже бумаги, на которой они написаны."

Как замечательно, а. Скажите, только у меня подобный подход - "в законе одно, в бухгалтерии другое, на деле третье" - вызывает ощущение, как бы это поаккуратнее описать в приемлемых для печати фразах, эдак литературненько... некоей занудной и мучительной, как зубная боль или, лучше сказать, вялотекущая шизофрения... "состояния нестояния"? Надеюсь, Читатель, приняв во внимание, что Автор Вот В Этом живет, с матерком просыпается и с ним же засыпает, в навязчивой, вонючей как дерьмо, субстанции хронического вранья, шизоидной этой российской раздвоенности, которой за свою нелегкую трудовую жизнь нахлебался вдосталь... простит ему некоторую эмоциональность литературного стиля. Который, зато, не подразумевает ни малейшей двусмысленности, эвфемизмов и экивоков, чем не круто.

Поясню еще, и двинемся далее. Утверждаю: любой работодатель, на которого ты, уважаемый мой коллега-программист-айтишник, работаешь без трудового договора либо же работаешь "как ИП" - де-юре и де-факто работодателем не является, что бы там тебе не щебетали по этому поводу прелестные девочки-эйчары; невзирая на любые прелести, все это суть нонсенс и грубейшее нарушение трудового законодательства (на хабре несколько лет назад была опубликована весьма примечательная статья о "схемах Ходорковского" и прочих в тему вещах, велкам к прочтению те, для кого сказанное - неожиданность). Как правило, речь здесь может идти о ГПО, договоре (письменном, устном) гражданско-правового характера, который априори не является договором трудовым, заведомо предопределяя равноправие сторон. С этих позиций, на которых величественно и невозмутимо, как истуканы острова Пасхи, покоится КЗОТ (и не только российский), претензия твоего заказчика (ни в коем случае не работодателя) отслеживать твои действия за компом либо пусть даже всего только контролировать время, затраченное на работу - объяснимо лишь степенью его наглости, помноженной на элементарную твою правовую безграмотность. Устанавливая Toggl на рабочий свой ПК, или же иной тайм-трекер, ну или попросту покорно выслушивая от своего горе-эмплойера нотицию о перманентных твоих нарушениях трудовой дисциплины - помни об этом.

Сказанное относится также и к Украине. Напомню: работа как ФОП (фзична особа пдпримець) не есть трудоустройство, за подробностями, пожалуйста - к юристам, специализирующимся на трудовом праве. Здесь же - короткой строкой: априори нет и быть не может ни "испытательного срока", ни "рабочего времени" в отсутствие трудового договора, в полном соответствии с КЗоТ Украины. А если договор имеет место, то, согласно ч. 11 ст. 60 КЗоТ "при дистанционной (надомной) работе сотрудники распределяют свое рабочее время по собственному усмотрению, на них не распространяются правила внутреннего трудового распорядка, если другое не предусмотрено условиями трудового договора; при этом общая продолжительность рабочего времени не должна превышать нормы, установленных статьями 50 и 51 КЗОТ Украины."

Ось так, хлопц. Вс, хто мають ншу думку, можуть йти на Хутр ловити рдксн породи метеликв.

ОК, идем дальше, дабы попробовать проанализировать, к какому финалу вполне закономерно приводит сентенция "у нас нет НДА или договоров, потому что все это не дороже бумаги, на которой они написаны", возведенная в ранг логики работы предприятия. Получаю пару тасков в работу, клонирую с гитхаба rails-app, анализирую код и запускаю у себя. Формулирую решение технической задачи, решение, однозначно не противоречащее ни одному пункту описанных условий и безусловно работоспособное, и даже, я бы использовал именно эту терминологию - ортодоксальное, классическое решение. И вдруг... я с удивлением и очень не сразу начинаю осознавать, что уровень технических познаний моего заказчика, фаундера и тимлида в одном лице - как бы это поаккуратнее сформулировать, в определенных ситуациях оставляет желать несколько лучшего...

01.05.2021 20:13, Yura Omelchuk пишет:

Начну с того, что сам вопрос поставлен не вполне корректно

Devise invitable отвечает за приглашения пользователей в систему, не более того. Можно его использовать или обойтись без него, тут надо смотреть внимательно на его функциональность и что нам даст его использование. И кстати, надо учесть, что регистрация в системе должна быть свободной, а не только по приглашению.

Вопрос управления доступом к документу решается отдельно и независимо от приглашений. Здесь нужна структура данных, отвечающая за хранение прав доступа. Для авторизации доступа, если говорить про библиотеки, мы применяем pundit а не cancan/cancancan.

Отправлено с iPhone

Здесь снова необходима ремарка автора. Которому, признаться, никогда ранее не приходило в голову, что кто-либо способен расценить использование крайне популярного, "звездного" scambra/devise_invitable как попытку "управления доступом к документу", или что включение данного функционала отменяет основную функцию devise, заключающуюся в регистрации пользователей: "Devise is a flexible authentication solution for Rails based on Warden". Помимо прочего, мою досадную неудачу чтения мыслей на расстоянии ("мы применяем pundit, а не cancancan") постеснялись бы назвать "невполнекорректностью" даже на печально известном imho-форуме, с их вечно живым, как дело Ленина, мемом-слоганом "экстрасенсов здесь нет".

02.05.2021 01:48, Alexey пишет:

Как скажешь. Но pundit нет даже в твоем джемфайле. Предложил бы описать инструментарий, посредством которого считаешь правильным имплементацию моих двух тасков, я-то ведь не умею мысли читать. Тогда постановка задачи будет законченной, на мой взгляд. Пойми правильно, способов реализовать требуемое немало. Любой на выбор.

Продолжаю догадываться: в модель документа добавляем еще один столбец, куда станем записывать юзеров/мыло, которым автор дока предоставил доступ. Соответственно, на указанной тобой новой (фейковой) вьюхе дока - будет отображаться филд, содержащий уже введенные emails, с возможностью этот список редактировать. Посредством которого любым из десятков возможных способов будет предоставляться доступ уже существующим юзерам, несуществующим - отправляем приглашения (можно новый мэйлер сделать, но по уму предложил бы попробовать devise_invitable).

Как вариант. Что скажешь про такой сценарий?

02.05.2021 05:53, Yura Omelchuk пишет:

Мы для того и обсуждаем план, чтобы синхронизировать подходы. Что касается атрибута документа, куда записывается список адресов пользователей - это не гибко, нужна отдельная сущность.

Отправлено с iPhone

02.05.2021 13:00, Alexey пишет:

По-прежнему не понимаю, чем обусловлен выбор pundit в данном случае. Пусть он хоть 10 раз гибок, суть его подхода - политики и роли, что уже не вписывается в логику задачи. Разумеется, можно и на вордпрессе интернет-лабаз сделать, да и от руки доступ накатать, без pundit / cancan и ещё десятков аналогов, с граф. интерфейсом или без - не проблема. Это такой корпоративный стандарт на все случаи жизни, pundit, либо есть иная аргументация?

02.05.2021 13:18, Alexey пишет:

Смотри, что у нас получается. Я не располагаю ни ТЗ, ни полноценным описанием задачи, т. к. таски не содержат описания требуемого инструментария - следовательно, не могу говорить об адекватности предлагаемых мной решений. Я лишь могу утверждать, что предложенные мной решения являются 100 рабочими, но судить о степени их масштабируемости априори невозможно, не располагая деталями проекта. Обсуждение превращается в аналог детской игры холодно-горячо, ты готов платить за подобное времяпрепровождение? - даже если так, это малоинтересно для меня. Предлагаю изменить подход к делу на более технически корректный.

02.05.2021 13:25, Yura Omelchuk пишет:

Окей, думаю на этом можно остановиться. Не вижу смысла продолжать если такая простейшая задача вызывает столько полемики, что же будет на более сложных? Всего хорошего. Удачи.

Отправлено с iPhone

Комментарии по большей части излишни. Кроме, разве что, следующей констатации: как видите, господа-товарищи, "работа" на том или ином коммерческом предприятии, руководитель которого исповедует принцип "у нас нет НДА или договоров, потому что все это не дороже бумаги, на которой они написаны"... не добавляет высокого литературного штиля автору этого рассказа, разумеется, но при случае приравняет профессиональную ценность любого из вас, дорогие мои друзья и коллеги - к стоимости черного живого товара контр-адмирала флота российского Павла Джонеса, которым тот весьма неплохо зарабатывал на жизнь в бурной своей молодости. "Тимлид", владелец и руководитель одесской веб-студии Kodo Labs Юра Омельчук попрощался настолько эффектно, что даже забыл предложить оплатить время, потраченное на работу анализ, запуск и тестирование своего кода; я уж молчу про то, что ни на один из заданных ему технических вопросов он не попытался ответить, ни одного аргумента в пользу своего мнения не привел. А это уже ничем не прикрытый моветон: по окончании любого профессионального тестирования принято озвучивать правильные ответы, приходилось ведь и мне когда-то поступать таким образом. Справедливости ради добавлю, на этот знаковый момент "ты уволен, и ты мне ничего не должен, бывай здоров" я обратил внимание своего незадачливого корреспондента, добавив на прощанье "плати, если хочешь", и подразумевая тем самым, что не стал бы настаивать на оплате, выиграв в карты у местных на одесском Привозе; правила игры, знаете ли, предопределяют, да не деньги и были. Результат предсказуем: "не хочу и не плачу", и вот на сей раз, не в пример прозвучавшему ранее, все действительно логично.

Вот интересно, кстати сказать. Как думаете, что сказал бы родом из Шотландии американский капер, если бы удостоившая его высочайшей аудиенции Екатерина II предложила, вкупе с контр-адмиральским патентом, аналог "оплаты на ИП"? - или, еще того лучше, "давайте без налогов, мой очаровательный капитан, зачем вмешивать государство в наши отношения? - оставьте нас, мсье Зубов, nous vous parlerons plus tard..." (почти что такую формулировку мне доводилось слышать, и неоднократно, от российских работодателей)? Может статься, и проканало б, а?- капер, он и есть капер, какая похер дым ему разница.

Из мачты не станет он делать гик, из рубки строить вельбот,

Фок-мачту взял и рубку украл янки, дьявольский род!

Я драться не мог: надвигалась тьма, и океан бушевал,

Я дал по нем выстрел - за грубый увод, и второй - за то, что он лгал...

Была мысль выложить на гитхабе (ну, уж коль скоро NDA "не дороже бумаги") rails-app, о котором речь, дабы рубисты-рельсовики на досуге глянули, чем так приглянулся одесситам pundit в контексте данного проекта; на мой субъективный взгляд, здесь он, хотя и безусловно возможен, но был бы все равно что интернет-мем Black Lives Matter во времена войны за независимость Североамериканских штатов, в бытность которой Джон Пол Джонс получил известность как капитан шлюпа Providence... хм, тоже ведь своего рода Team Leader, не правда ли. Но, пожалуй, это частность, малоинтересная крайне широкой сегодня аудитории российских любителей поэзии и прозы Редьярда Киплинга.


Эпилог и послесловие короткой строкой. Учитывая международную аудиторию Хабра, автору очень не хотелось бы, чтобы у читателей создалось впечатление бьющего аж через край патриотизма русскоязычного автора, во всю мочь разглагольствующего о черноморских баталиях эпохи русско-турецкой войны применительно к постсоветским невеселым реалиям. :) Нет, причиной здесь скорее ностальгия по горячо любимым им, несмотря ни на что, Севастополю и Херсонесу, где он не был... уже около семи лет.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru