24 июня 2019 г.

Подборка статей прочитанных в июне

Frontend

Делаем таблицу с бесконечной прокруткой без event listener
Концепт реализации бесконечного скрола при помощи IntersectionObserver API.

Настройка окружения unit тестирования javascript
Advanced Patterns in React

Backend

Введение в Spring, или что делать, если по всему проекту @Autowired и @Component, а вы не понимаете, что это

Python

Python потребляет много памяти или как уменьшить размер объектов?
Обзор структур данных python по объему требуемой оперативной памяти

Operation Systems

Redox
Redox is a Unix-like Operating System written in Rust, aiming to bring the innovations of Rust to a modern microkernel and full set of applications.

IOT

Вывод информации на дисплей покупателя
Статья про вывод произвольного теста на внешний дисплей Raspberry Pi.

Office trends in 2k2*

Текстовый процессор для подготовки документов в различных форматах.

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

  1. Современный формат презентаций
  2. Презентация как код, или Почему я больше не пользуюсь Powerpoint-ом
  3. Basic presentation-on ascii doc
  4. A simple markdown-like script language for generating charts from text via javascript. (+ online editor)
  5. A Markdown Presentation Writer
  6. A simple, in-browser, markdown-driven slideshow tool







4 декабря 2015 г.

Параметризация установки расширения из Chrome Web Store

С благими намерениями Google закрыла для разработчиков возможность установки плагинов за пределами магазина (с небольшим списком исключений).
Поэтому единственный вариант дистрибуции плагина со своего ресурса это публикация ссылки на страницу Chrome Web Store.
Если надо добавить какой нибудь уникальный параметр плагину при установке, то можно добавить параметр в url ссылки на плагин (например в хэш), а затем найти его при помощи history api.
Публикуем подобную ссылку:
https://chrome.google.com/webstore/detail/link-checker-web-console/picifgoheidimhdnaghmacegokgglgeh?hl=ru#hashmark=one_two_three

В расширении при установки ищем в истории ссылку с заданным хешем:
var URL_MARK = '#hashmark=';

chrome.history.search({
 text: URL_MARK,
 maxResults: 10,
}, function(results){
 var  url = results[0] ? results[0].url : '', 
   pos = url.indexOf(URL_MARK), 
   options = pos != -1 && url.substr(pos + URL_MARK.length);
 
 if(options){
  // Do something
 }
});

22 октября 2015 г.

Статус загрузки страницы в iframe

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

Если в iframe подгружается с того же домена, что и страница то мы можем просто скачать страницу по ajax, получить статус операции.

В примере использую обертку jQuery для загрузку через ajax.

var $frame = $('iframe')

$.get(url).done(function(content, status, x){
var _doc = $frame[0].contentWindow.document; 
_doc.open('text/html', 'replace');
_doc.write(content);
_doc.close();
$frame.css('height', _doc.documentElement.scrollHeight);
}).fail(function(x, status, message){
// Do another magic
});

П.С.
В одном списке вопросов для фронтендеров в качестве ответа на вопрос, когда бы вы использовали метод document.write(), был предложен ответ - никогда (или в 1999-м). Вообщем, это первый случай, когда я нашел применения этому методу.

П. П. С.
Обнаружил один существенный недостаток - невозможно определить когда завершится отрисовка содержимого, вставленного через document.write(). Поэтому полученный размеры контента могут не соответствовать реальным.

5 сентября 2015 г.

Лучшее из прочитанного в августе

Короткий конспект собранных ссылок понравившихся статей.

Браузер - это развитие ОС
http://siliconrus.com/2015/08/browsers-future/

Касперский попал под раздачу
http://geektimes.ru/post/259750/
Особенно полезно, что возможное решение сейф браузинга Dr.Web будет иметь похожее воплощение.

Занимательная вероятностная статистика
http://geektimes.ru/post/259090/

Как победить проституцию
http://w-o-s.ru/article/16030

Откуда берется ксенофобия
http://w-o-s.ru/article/14704

"Традиции износилования"
http://w-o-s.ru/article/14823

Чем бороться с локерами/вымогателями
http://geektimes.ru/company/icover/blog/259968/

Исповедь фронтендера в поисках работы. Стоит иногда перечитывать, чтобы приходить в чувства.
http://siliconrus.com/2015/08/kozulya-job/

Mac app store free apps. Листинг скидок в app store
http://appshopper.com/mac/prices/free

В чём истинное предназначение «Косынки» и «Сапёра»
http://geektimes.ru/post/260292/

Небольшой обзор Алисы в стране чудес с точки зрения анализа работы мозга.
Мне всегда нравился сюрриализм в этом произведении, и по всей видимости он не случаен.
http://www.factroom.ru/books/secrets-of-alice-in-wonderland

Мара Сальватруча из Центральной америки
http://lenta.ru/articles/2015/08/18/ms13/?pasa

Реабилитируем селфи
http://www.lookatme.ru/mag/how-to/inspiration-howitworks/216209-psycho-selfie

Признаю, что выработал у себя такую привычку - держать по множеству открытых вкладок.
И две вещи которые вынуждают их закрывать - не читаемость заглавий вкладок и нехватка памяти. Из за физических ограничений монитора и компьютера.
http://www.lookatme.ru/mag/how-to/better-life/216309-lifehack-singletasking

Небольшая история как появился ноль и отрицательные числа
http://www.lookatme.ru/mag/how-to/books/216125-excerpt-negative

Хороший рассказ о выборе фотоаппарата или чем снимать
http://geektimes.ru/company/icover/blog/260740/

Познавательная статья о прослушке (с красочными примерами)
http://habrahabr.ru/company/neuronspace/blog/265457/

20 июля 2015 г.

VirtualBox: как объединить две виртуальные машину в одну сеть

Было необходимо поправить багу в верстке под IE9, который был доступен из одной ВМ, а сервер был на другой ВМ. Поэтому потребовалось настроить работу двух ВМ в VirtualBox в одной сети.
Составил небольшой конспект, основных действий. Может пригодится в будущем.

Создание виртуальной сети
Переходим по пунктам меню "Файл > Настройки"
Открываем вкладку "Сеть".
Создаем/выбираем сеть
Задаем настройки сети в попапе:
  • Имя сети: "NatNetwork"
  • CIDR сети: 10.0.2.0/24
IP адресс хоста потом надо будет искать в диапазоне указанного CIDR сети.

Настройка виртуальной машины
Выбираем пункт меню "Настройка" контекстного меню ВМ.
Во вкладке "Сеть" в настройках Адаптера (добавляем новый если требуется).
Выбираем "Тип подключения" - "Сеть NAT", в выпадающем списке "Имя" выбираем созданную сеть.
Задаем такие настройки для всех ВМ, которые надо подключить

Проверка
В ВМ в которой запущен сервер узнаем ip адрес машины
Debian/Ubuntu к примеру
#: ifconfig
В ВМ в которой открыт браузер в адресной строке указываем хост и порт сервера.

26 января 2015 г.

Двоеточие в идентификаторе css селектора

Опытным путем нащупал отличия в работе методов document.getElementById() и document.querySelector()
Был в DOM-е элемент с идентификатором layout:rightcol
<div id="layout:rightcol"></div>

Так для него document.getElementById('layout:rightcol') вернет правильный элемент, а для
document.querySelector('#layout:rightcol') и $('#layout:rightcol') будет получено исключение

chrome 40: DOMException: Failed to execute 'querySelectorAll' on 'Document': '#layout:rightcol' is not a valid selector.
firefox 35: SyntaxError: An invalid or illegal string was specified

2 октября 2014 г.

Находки в сети за сентябрь

Заметки и все интересное, что мне попадалось в сети.

Js и веб разработка
Советы по хорошему оформлению angular кода
http://habrahabr.ru/post/235873/
https://github.com/johnpapa/angularjs-styleguide
V8 JavaScript stack trace API
https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
коллекция шаблонов сайтов
http://templated.co/
Сервис отдающий ip адрес по запросу строкой
https://secure.informaction.com/ipecho/
Проект на github-е ruby api for google drive
https://github.com/gimite/google-drive-ruby

Веб разработка под мобильные устройства
Список физических размеров экранов мобильных устройств
mydevice.io
подборка мобильных меню
http://www.dejurka.ru/web-design/menu-list-mobile-design/

Служебные разделы доступные только в браузере
Некоторые служебные разделы браузеров, которые могут пригодится при разработке расширений (аддонов)
Кэш в chromium-based браузерах - chrome://cache/
Превью страниц в chromium-based браузерах - chrome-search://thumb/http://mail.ru
Список сторонннего ПО, которое используется в Yandex браузере - browser://credits/

Приватность
Подробное описание заголовка запроса Content-Security-Policy
http://content-security-policy.com/
Статья про XSS
http://habrahabr.ru/post/149081/
Деаномизация пользователей
http://habrahabr.ru/post/238565/

Интересные статьи с Adme.ru
25 пророческих цитат Джорджа Оруэлла
Как обманывают наивных талантов
Эволюция мозга

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

Linux Дистрибутив
Сайт проекта занимающегося разработкой одноименного минималистичного Linux дистрибутива.
Damn Small Linux Remake

Уроки биологии
http://science.compulenta.ru/688541/

Android
Engineer Mode на Android смартфоне
UCSD comand: *#*#3646633#*#* К примеру на смартфоне c Android 4.2 нашел сообщение о состоянии батареи
Log and Debugging > BatteryLog > battery healthy


16 сентября 2014 г.

Макет резиновой таблицы

Придумал решение, которое позволяет вписать таблицу с несколькими колонками в контейнер фиксированного размера.  При выходе за пределы размеров контейнера, скролл не портит верстку. Решение представлено на github-е.

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


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

Основное ограничение, это не учитывается высота "резиновой" колонки, потому что он находится в блоке с абсолютным позиционированием. Этот эффект в примере прикрыт обрезанием длинных строк. Чтобы преодолеть это ограничение нужно добавить немного кода на js.

6 сентября 2014 г.

Погружение в Angular

Сухим текстом представляю мои впечатления от знакомства с Angular.
До этого работал с Backbone и обратить на него внимания меня вынудили разговоры с командами которые его используют.
Чтобы лучше понять на каких принципах построен Angular реализовал небольшое приложение, которое разместил на github-е.

Что сразу бросилось в глаза.
Динамика интерфейса приложения скорее всего будет в верстке. Если же, разработчик не испытывает отвращение к "декларативному подходу".
Благодаря использованию директив обычная верстка перейдет в разряд шаблонов. Встроенная логика - дополнительный синтаксис, на подобии  SQL, LINQ. В таком примере
ng-options="key as value for (key, value) in categories"
часть после for относится к источнику, выражение до for определяет, какое значение использовать в качестве атрибута value опции, а какое в качестве текста опции.
Параметризация шаблонов с возможностью двунаправленного биндинга поддерживается изначально.
<h3>{{[«Добавление»,«Изменение»][(item.id>0)+0]}} записи</h3>
По слухам, в шаблоне не работает тернарный оператор, поэтому в этом примере он был заменен такой конструкцией.

Достоинства:
  • Не требует зависимостей. В Backbone, для примера, потребуется jquery и underscore;
  • Встроенная реализация привязки данных. В Backbone для аналогичного функционала придется создать удобный подсобный инструментарий или использовать стороннее решение (epoxy.js, ribs);
  • Приемлемая архитектура построенная на патернах.
Недостатки :
  • Не  имеет 100% эквивалентной замены функционала jquery для работы с DOM. К примеру, метод $element.find() работает только с названиями тегов и не работает с селекторами атрибутов (elem.find('[type=submit]'));
  • Из личного, не так просто взять и вставить в нужное место вьюшку как в Backbone. Надо все обставить в специально "приготовленные" для этого директивы.
Сравнение с Backbone
Backbone по содержанию напоминает фреймворк кастомных MV* фреймворков. Необходимо самому реализовать вывод представления (метод render()). Можно использовать любой другой шаблонизатор по мимо underscore.template и т. п. Как связать атрибуты модели к вьюшке кодер решает сам. Т.е. то что будет использовано под "вывеской бекбона" будет удовлетворять вкусу и взглядам на правильный подход конечного разработчика. Поэтому необходимо, что бы у разработчика были представления о best practise и способах их достижений:
  • Декларативное описания логики в шаблоне или перенос их в js код представления;
  • Отдельное представление для каждой коллекции или коллекции будет вставлятся в DOM представления;
  • Одна модель - одно представление или у одной модели может быть много представлений.
Разные слои абстракции. 
Backbone: Backbone.Events, Backbone.Model, Backbone.Collection, Backbone.View
под девизом "расширяй и наследуй".
Angular: service, fabric, module, controller, directive, filters Внедрение зависимостей.
В Angular роль бэкбоновской модели выполняют scope и сервисы, коллекции так же размыты.

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

В последнее время у меня появилась мысль, что коллекции в Backbone (Backbone.Collection) получили ход, благодаря тому что в модели поддерживаются только plain объекты (без доступа к дочерним под объектам). Но пока эта мысль у меня развивается.


1 сентября 2014 г.

Находки в сети за август

Самые интересные проекты и статьи которые я видел и читал за месяц. В основном по веб разработке и всему такому.

Сжималки и переcжималки графики
https://tinypng.com/
http://www.jpegmini.com/
http://pnggauntlet.com/

Фотосток со свободными изображениями
www.raumrot.com/10/

Неистовый sublime
Что делать с sublime-ом если вы не кодер
http://xakep.ru/howtos/sublime-text-not-for-coding/
Полезное описание, как прикрутить Package Control
Описание хоткеев
http://nosensus.com/sublime-text-3-hot-keys/

Язык программирования
LENS - встраиваемый язык программирования для .Net
https://github.com/impworks/lens/

API virustotal.com
https://www.virustotal.com/ru/documentation/public-api/
https://www.virustotal.com/ru/documentation/browser-extensions/

Node.js
Электронные таблицы
https://ethercalc.org/
sublime подобный редактор
https://github.com/slap-editor/slap
Изменения в Express 4.0
http://scotch.io/bar-talk/expressjs-4-0-new-features-and-upgrading-from-3-0

Css3 animation library
http://anijs.github.io/

Подробнее о strict mode в js
http://stepansuvorov.com/blog/2014/07/strict-mode/#more-1730
Unit tests
http://xakep.ru/howtos/js-unit-testing/

.gitconfig
http://stepansuvorov.com/blog/2014/02/gitconfig/#more-1131

Верстка почтовых рассылок
Теги поддерживаемые gmail
http://xakep.ru/news/gmail-tags/

На карандаш:
AndroidManifest.xml разрешения которые используют зловреды:
"android.permission.INTERNET"
"android.permission.ACCESS_NETWORK_STATE"
"android.permission.READ_PHONE_STATE"
"android.permission.RECEIVE_BOOT_COMPLETED"
"android.permission.WAKE_LOCK"
"android.permission.WRITE_EXTERNAL_STORAGE"
"android.permission.READ_EXTERNAL_STORAGE"

Ссылки на страницы с историей использования сервисов:
https://www.youtube.com/my_search_history
https://www.youtube.com/feed/history
https://history.google.com/history&hl=ru

Список спецификаций моделей mac mini
http://support.apple.com/kb/HT3476

24 августа 2014 г.

HTMLayout от Terra Informatica

Небольшое описание процесса верстки для встраиваемого браузера, с которым мне пришлось познакомится.

HTMLayout - встраиваемый браузер разработанный компанией Terra Informatica, GUI библиотека которая использует html/css для построения интерфейса с api для различных языков программирования. Используется для создания GUI "коробочного" ПО.
Документацию немного, в основном на основном сайте.
По общему впечатлению, процесс верстки напомнил мне работу с Internet Explorer 8.

Поддерживает css 2.1, частично css3 и ряд своих css конструкций (CSSS!).
Из "+" в пику основным браузерам, поддерживает свойства, параметры связанные с настройками GUI Windows. Такие как стандартные настройки и параметры темы Windows. Ширина скрола, основной шрифт системы, цвет окон и т.п.


Отдельные особенности:
  • Поддержка flow и flex-units. (Реализация их в последних версиях chrome и FFox все еще имеют различия);
  • Поддержка переменных css, include html файлов;
  • Встроенный в css язык сценариев! Поддерживаются обработка событий, выборка dom элементов. В целом, это оправдано тем, что тогда не придется на c++ писать логику  переключения блоков верстки.
Недостатки:
  • Слабая документированность;
  • Различия в обработке с основными браузерами;
  • Отсутствие средств отладки.
Основной способ изменения состояния интерфейса это описание логики в css. Используемый синтаксис расширен дополнительными конструкциями. Называется он CSSS! или Active CSS. Приведу некоторые примеры кода, которые проиллюстрируют его работу.

Компенсация не работающего атрибута for
<label for="elem"><input type="checkbox" id="elem"/> awesome checkbox</label>
Чтобы он заработал, необходим небольшой css код
/* Some magic: [for] not supported by default */
[for]{
hover-on!: $1(#< self.for >):hover = true;
hover-off!: $1(#< self.for >):hover = false;
active-on!: item = $1(#< self.for >), item:checked = !item:checked;
cursor: pointer;
}

Поддержка псевдокласса :checked
#elem:checked // работает
#elem + span // работает
#elem:checked + span // уже не работает

По умолчанию click event поддерживает только тег button. Выявлено опытным путем, в документации ни слова.
button{
click!: // Do something
}

Переключение блоков на странице
Требуется примитивная логика, которая позволит переключать блоки:
<button switch-view="view1">turn on View1</button>
<div view="view1" style="display: none;">...</div>
CSS:
/* View switcher */
[switch-view]{
click!:
relView = self.switch-view,
$([view]:not([view=<relView>]))::display = "none",
$([view=<relView>])::display = "block",
;
}

Вариант реализации переключения табов
button[tab]{
click!:
relTab = self.tab,
$([tab]:not([tab=<relTab>])).class = "inst_tab-item",
$1([tab=<relTab>]).class = "inst_tab-item inst_tab-active",
$([rel-tab=<relTab>])::display = "block",
$([rel-tab]:not([rel-tab=<relTab>]))::display = "none",
;
}
где
$() - функция выборки элементов по селектору
$1() - выбирает первый элемент по указанному селектору
::display - свойство display атрибута style элемента
.class - атрибут class элемента

Ссылки
http://habrahabr.ru/post/154697/
http://www.terrainformatica.com/2006/08/h-smile-core-popup-and-context-menus-htmlayout-and-sciter-engines/
http://www.terrainformatica.com/2008/02/built-in-drag-and-drop-support-in-h-smile-core/

3 августа 2014 г.

Настройка отступов в Komodo Edit

Все настройки задаются в разделе Edit > Preferences, но для файла они могут быть переопределены в CurrentFileSettings.

Настройки находятся в группе Editor/Indetation.
Чтобы использовать табы в место пробелов ставим галочку для пункта Prefer Tab characters over spaces. Там же необходимо задать длину отступа табуляции.


1 августа 2014 г.

Интернет банк Банка Авангарда

Небольшие детали, беглым взглядом, которые там нашел.

1) Сделан на Bitrix.
https://www.avangard.ru/bitrix/admin/index.php#authorize
2) В js коде jquery партянка. Не всегда ставят точки с запятой.
3) Таким запросом https://www.avangard.ru/ibAvn/ping/session.json можно фиксировать активную сессию юзера.
Если юзер покинул сессию, то его редиректнет на https://www.avangard.ru/ibAvn/site.jsp, а следом https://www.avangard.ru/login/www/ibank_enter.php

Ответ при активной сессии в скриншоте.



29 июля 2014 г.

Data url изображения в Firefox-е

Обнаружил для себя интересные тулзы для фронтендера в FireFox-е. Возможность получить изображение из data url и на оборот.

Подробности в скриншотах:


15 июня 2014 г.

Наследование в javascript

Выкладываю на всеобщее обозрение свой вариант реализации наследования в js.
Выкладки и демонстрационный пример уже доступны на https://github.com/matraska23/es5_inheritance_implementation

Реализация использует возможности Object.create() и ряд других новых методов появившихся в ES5. Поэтому в отличие от аналогичного механизма применяемого в Backbone отличается:
  • объект потомка проходит проверку на instanceof для всех объектов предков (т. е. поддерживается реализация цепочки из объектов предков);
  • имеется возможность для переопределения методов объекта и в вызова метода предков;
  • IE8 не поддерживается (а он вам нужен?).
Код не отличается особой оригинальностью, поэтому вероятно что-то подобное вы сможете увидеть еще где-нибудь:
function inherit(Constructor, Base, extendProperties){
    var buf = Constructor.prototype;
    
    Constructor.prototype = Object.create(Base.prototype, extendProperties);
    Constructor.prototype._base = Base;
    _.extend(Constructor.prototype, buf);
    
    return Constructor;
}

Метод _.extend() осталось заменить на свою реализацию для 100% независимости от сторонних библиотек

2 февраля 2014 г.

Скользкие моменты при работе c data атрибутами в Jquery

Когда вы задаете или получаете какое-нибудь значение хранящееся в data-атрибуте DOM элемента JQuery  делает часть грязной рутинной работы за вас.
Что бы было понятно о чем речь, пример Data атрибута data-text-value
<div data-text-value="abc" id="example">...</div>
Вы можете получить значение или задать:
$("#example").data("text-value")
$("#example").data("text-value","123")

Метод $.data является оберткой над объектом dataset DOM элемента. При этом когда вы пытаетесь считать хранимое значение (строку), Jquery конвертирует значение в ближайший по смыслу тип:
"true" => true,
"123" => 123
и т.п. При этом еще кэширет значение чтобы не парится с распарсиванием в будущем.

Вот здесь и можно подскользнутся на банановой кожуре. Если через дата data атрибут задать такое число выходящее за 32 бита 83735748785296303. То можно получить что нибудь отличное, к примеру 83735748785296300. По всей видимости для приведения строки в число используется функция parseInt:
parseInt("83735748785296303")
#: 83735748785296300

Код для доказательства:
var d = document.createElement('div')
d.innerHTML = '<div data-value="83735748785296303"></div>';
$(d).data('value') == 83735748785296300

13 октября 2013 г.

исполняемые комментарии в Internet Explorer

Обнаружил для себя новую приблуду в IE. Когда делал комит в SVN получил предупреждение, что с поставленным в js коде комментарием что-то не так. Нагуглил инфы и обнаружил,что  Interner Explorer может исполнять js код оставленный в комментариях. Код должен:
  1. обернут  символами собачками `@` ;
  2. между собачками и символами открывающими/закрывающими комментарии не должны быть пробелы.
Такой код выполнится в IE8, IE9, но в других будет проигнорирован:
/*@ document.body.innerHTML="123" @*/

Ивроде и не баг. Но почему то я раньше с ним негде не встречался. Осталось, теперь придумать, как это можно использовать.

Подробнее можно почитать
http://support.microsoft.com/kb/323487/en
http://stackoverflow.com/questions/3894079/multiple-js-comment-bug-in-ie

29 сентября 2013 г.

Тестирование почтовой рассылки с помощью blat

Для автоматизации процесса верстки - тестирования почтовых рассылок под Windows я использую консольный почтовый сервер blat.
Загрузить blat можно с сайта проекта http://www.blat.net.

После распаковки в архиве. Перед тем как начать использовать утилиту, необходимо настроить параметры учетной записи smtp сервера.
Все указанные параметры будут сохранены в реестре.
#: blat.exe -install -server smtp.server.ru -port 25 -f your@mail.box -u your@mail.box -pw __password__
где smtp.server.ru адрес smtp сервера твоего любимого почтового сервиса. smtp.server.ru - учетная запись и  имя отправителя. __password__ пароль.

Как послать письмо с вложением
#: blat.exe -charset windows-1251 -to mail@yandex.ru -subject "Тема" -body "Тело письма" -attach "attach.txt"
Письмо по умолчанию будет отправлено в utf-8 (о чем следует помнить).  Ключ -charset windows-1251 у меня почему то не срабатывает.

Как отправить html файл (сверстанное письмо)
#: blat.exe body.html -s "Test mail" -i "Cybergavin Tester" -to mail@yandex.ru,mail@mail.ru,mail@gmail.com -html
Описание ключей:
-s => Тема письма ( тема письма по умолчанию "Contents of file: stdin.txt" )
-i => Имя отправителя (адрес отправителя указывается при настройке параметров smtp сервера)
-to => Адреса назначения через запятую
body.html => файл с версткой письма
-html => ключ для указания mime типа документа - HTML (Content-Type : text/html)

Если письмо содержит графику с сервера. То на время тестирования ее можно брать из расшаренной папки google drive. Google drive позволяет делать статические сайты из расшаренных папок (как и DropBox), поэтому в них можно сложить необходимую графику.

Полезные gem-ы ruby. chunky_png

Ruby удобный язык для создания подсобного инструментария. И основную ценность придает ему доступная система пакетов gem-ов. Одним из таких пакетов является gem chunky_png. [ <) https://github.com/wvanbergen/chunky_png ] chunky_png добавляет легкий api для работы с png. И в основном я его использую для генерации спрайтов - склейки png с небольшими трансформациями. Собственно, для этих целей он используется в compass.

Ставим gem
#: gem install chunky_png

Пример склейки png #1
require 'chunky_png'

files = ['btn1.png','btn2.png','btn3.png','btn4.png','close.png']
# Создадим png файл в который все поместим все картинки. 1-й параметр width, 2-й - height
png = ChunkyPNG::Image.new(70,70*4+32, ChunkyPNG::Color::TRANSPARENT)
yPos=0
files.each{|fname| # Перебираем файлы
    img = ChunkyPNG::Image.from_file(fname)
    png.compose!(img,0,yPos) # размещаем картинку в нужном месте
    yPos=yPos+img.dimension.height
}
png.save("btnSprites.png")

Пример склейки посложнее #2
require 'chunky_png'

# в спрайт нужно вставить оригинальное изображение, но иногда и отцентрировать в заданном периметре
files = [{
            :file => "attention.png" 
        },{
            :file => "check.png"
        },{
            :file => "coin.png"
        },{
            :file => "desctop.png",
            :width => 50, # зададим ширину и высоту квадрата в центр которого нужно вставить изображение
            :height => 50
        },{
            :file => "mobile.png",
            :width => 50,
            :height => 50
        },{
            :file => "question.png"
        },{
            :file => "letter.png",
            :width => 50,
            :height => 50
        }]

file_buf = []
max_width = 0
max_height = 0
css = "" # попутно сгенерируем css, раз мы и так знаем все координаты
css_x_pos = 0
css_y_pos = 0
save_file_name = "sprite" # название спрайта
css_class_name_prefix = ".home-room-free-fm-" # а в css будем вставлять классы с таким префиксом

files.each{|image|
    # вставляем оригинальное изображение
    if image[:width].nil? || image[:height].nil? 
        img = ChunkyPNG::Image.from_file(image[:file])
    # или  помещаем изображение в заданный периметр
    else
        img = ChunkyPNG::Image.new(image[:width],image[:height], ChunkyPNG::Color::TRANSPARENT)
        buf = ChunkyPNG::Image.from_file(image[:file])
        x_pos = ((image[:width] - buf.dimension.width)/2).round
        y_pos = ((image[:height] - buf.dimension.height)/2).round
        #puts "image[:width] `#{image[:width]}`, img.dimension.width `#{img.dimension.width}`, x_pos: `#{x_pos}`"
        img.compose!(buf,x_pos,y_pos)
    end
    
    if img.dimension.width > max_width
        max_width = img.dimension.width
    end
    max_height = max_height + img.dimension.height
    file_buf << img
}
png = ChunkyPNG::Image.new(max_width,max_height, ChunkyPNG::Color::TRANSPARENT)

# помещаем получившиеся фрагменты в спрайт
file_buf.each_with_index{|file,index|
    png.compose!(file,css_x_pos,css_y_pos)
    # а данные о координатах сохраняем в CSS:
    css = css +"#{css_class_name_prefix}#{files[index][:file]}{\n\tbackground:url(#{save_file_name}.png) #{css_x_pos > 0 ? (-css_x_pos).to_s+"px":"0"} #{css_y_pos > 0 ? (-css_y_pos).to_s+"px":"0"} no-repeat;\n\twidth:#{file.dimension.width}px;\n\theight:#{file.dimension.height}px;\n}\n"
    css_y_pos = css_y_pos+file.dimension.height
}
#css можно сохранить в отдельный файл

Рисование окружностей:
require 'chunky_png'

png = ChunkyPNG::Image.new(57, 57, ChunkyPNG::Color::TRANSPARENT)

png.circle(28, 28, 27, ChunkyPNG::Color(0x7b0f34FF), ChunkyPNG::Color::TRANSPARENT)
png.circle(28, 28, 25, ChunkyPNG::Color::TRANSPARENT,ChunkyPNG::Color(0x7b0f344D))
png.save("out3.png",:fast_rgba)
Гем поддерживает возможность рисования. Но по пиксельно, поэтому получившиеся окружности смотрятся хуже в отличие от прямых линий, чем если это делать в photoshop или gimp.

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