Debian, AngularJS, Django, Python

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

LESS для Google Material Icon Font

У Google для Web-разработки с использованием Angular Material есть даже специальный набор иконок, а так же репозиторий на GitHub с возможностью установки через Bower. Там всё хорошо, но вот CSS для иконок приходится по кускам собирать из официальной документации. Тут я и публикую такой LESS/CSS, собранный собственноручно по результатам чтения официальных доков.

Установка через Bower

bower install material-design-icons

Помимо шрифта в архиве куча иконок в разных форматах, так что будьте осторожны - bower скачает около 30 Мб, а потом будет его некоторое время распаковывать.

У меня все сторонние библиотеки хранятся в каталоге static/libs/, вам же следует изменить пути к шрифтам (переменная @BASE_PATH) на подходящие.

material-icons.less

@BASE_PATH: '/static/libs/material-design-icons/iconfont/MaterialIcons-Regular.';
@font-face {
    font-family: 'Material Icons';
    font-style: normal;
    font-weight: 400;
    src: url("@{BASE_PATH}eot");
    /* For IE6-8 */
    src: local('Material Icons'),
         local('MaterialIcons-Regular'),
         url("@{BASE_PATH}woff2") format('woff2'),
         url("@{BASE_PATH}woff") format('woff'),
         url("@{BASE_PATH}ttf") format('truetype');
}

.material-icons {
    font-family: 'Material Icons';
    font-weight: normal;
    font-style: normal;
    font-size: 24px;
    /* Preferred icon size */
    display: inline-block;
    width: 1em;
    height: 1em;
    line-height: 1;
    text-transform: none;
    letter-spacing: normal;
    word-wrap: normal;
    -webkit-font-smoothing: antialiased; /* Support for all WebKit browsers. */
    text-rendering: optimizeLegibility;  /* Support for Safari and Chrome. */
    -moz-osx-font-smoothing: grayscale;  /* Support for Firefox. */
    font-feature-settings: 'liga';       /* Support for IE. */
}

.material-icons.md-18 { font-size: 18px; }
.material-icons.md-24 { font-size: 24px; }
.material-icons.md-36 { font-size: 36px; }
.material-icons.md-48 { font-size: 48px; }

// Rules for using icons as black on a light background.
.material-icons.md-dark { color: rgba(0, 0, 0, 0.54); }
.material-icons.md-dark.md-inactive { color: rgba(0, 0, 0, 0.26); }

// Rules for using icons as white on a dark background.
.material-icons.md-light { color: rgba(255, 255, 255, 1); }
.material-icons.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); }

Работает данный шрифт через лигатуры. В отличие от FontAwesome, который оперирует классами для тегов <span> и <i>, здесь нужно использовать и класс, и лигатуру:

Пример использования

<md-icon>
    <i class="material-icons md-24">menu</i>
</md-icon>

Полный список лигатур находится в каталоге material-design-icons/iconfont/codepoints. Так же есть отдельный ресурс с описанием и показом всех иконок.

Django Rest Framework - обновление поля типа ImageField

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

Исходные данные

Дано:

  • Модель, имеющая поле типа ImageField
  • Django REST Framework
  • ngFileUpload на фронте

Задача: сделать возможным загрузку изображений в указанное поле на основе Class-Based View в DRF.

Решение

Фронт-энд:

Вёрстка

<img ng-src="{$ item.logo200x200 $}" ng-model="logo" ngf-select ngf-change="uploadLogo(files)" accept="image/*" />

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

Что делает этот код:

Параметр Описание
ng-src="{$ item.logo200x200 $}" Связываем свойство модели и источник для нашего изображения. Делается через директиву Angular ng-src, как того советует официальная документация. На скобки в виде '{$' и '$}' не обращайте внимания. Т.к. на сервере используется стандартный шаблонизатор Django, приходится для Angular использовать другие скобки.
ng-model="logo" Для выбора файлов будет использоваться отдельная модель - logo
ngf-select Указываем, что данное изображение (можно использовать вообще-то что угодно) является полем ввода для плагина ngFileUpload
ngf-change="uploadLogo(files)" При изменении значения поля выполняем указанную функцию. Загрузка без нажатия кнопки "Загрузить", в общем, достаточно лишь выбрать файл.
accept="image/*" Разрешаем выбирать любые изображения. Фильтр для окна выбора файла.

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

LogoController.js

$scope.uploadLogo = function() {
    if ($scope.logo.length < 1) {
        return;
    }
    Upload.upload({
        url: logoUrl, // /api/item/3/logo/
        file: $scope.logo,
        method: 'PATCH'
    }).success(function(data) {
        $scope.item.logo = data.logo;
    });
};

Я описал лишь одну функцию контроллера. Надеюсь, догадаться, что нужно инжектировать $scope и Upload, не сложно.

Обратите внимание, для загрузки логотипа используется метод PATCH, а файл логотипа помещяется в объект file - потом именно его будем обрабатывать на сервере.

Бэк-энд

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

core.helpers.py

from PIL import Image

MAX_THUMBNAIL_SIZE = 200

def resize_logo(instance):
    """
    Resize model logo to needed sizes.
    """
    width = instance.logo.width
    height = instance.logo.height

    filename = instance.logo.path

    max_size = max(width, height)

    if max_size > MAX_THUMBNAIL_SIZE:  # Да, надо изменять размер
        image = Image.open(filename)
        image = image.resize(
            (round(width / max_size * MAX_THUMBNAIL_SIZE),
             round(height / max_size * MAX_THUMBNAIL_SIZE)),
            Image.ANTIALIAS
        )
        image.save(filename)

Пришло время описать саму модель, переопределив её метод save() таким образом, чтобы при сохранении размеры изображения для логотипа нормализовались, как нам нужно:

core.items.models.py

from os import path

from django.db import models

from core.helpers import resize_logo

class ItemModel(models.Model):

    name = models.CharField(
        "Название",
        max_length=255,
        help_text='Максимум 255 знаков',
        null=False,
        blank=False
    )
    logo = models.ImageField(
        "Логотип",
        upload_to=path.join('item', 'logo'), # Отдельный каталог для аватаров
        null=True,
        blank=True,
    )

    def save(self, *args, **kwargs):
        # Сначала модель нужно сохранить, иначе изменять/обновлять будет нечего
        super(ItemModel, self).save(*args, **kwargs)

        # Приводит размеры лого к одному виду - 200px по наибольшей стороне
        if self.logo:
            resize_logo(self)

    class Meta:
        app_label = 'core'
        db_table = 'item'
        verbose_name = 'элемент'
        verbose_name_plural = 'элементы'

Теперь можно описать части, относящиеся к API - сериализатор, представление и часть конфигурации URL.

api.items.serializers.py

from rest_framework import serializers

from core.items.models import ItemModel

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


class ItemLogoSerializer(serializers.ModelSerializer):

    class Meta:
        model = ItemModel

Как видно, сериализатор крайне прост. Опишем наше представление.

api.items.api.py

from rest_framework import permissions
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView

from core.items.models import ItemModel

from .serializers import ItemLogoSerializer


class ItemLogoAPIView(APIView):

    permission_classes = [
        permissions.IsAdminUser,
    ]

    serializer_class = ItemLogoSerializer

    # Обновление модели - методом PATCH, как я уже писал выше
    def patch(self, *args, **kwargs):

        # Находим нужную модель (по-хорошему надо обернуть в try ... except, но
        # сейчас я этого делать не буду, чтобы не загромождать код)
        instance = ItemModel.objects.get(pk=kwargs.get('pk'))

        # Получаем из запроса наш файл (как указали выше, в JS)
        instance.logo = self.request.FILES['file']

        # Сохраняем запись (тут должна быть проверка значений встроенными в DRF
        # методами, но сейчас я этого делать не буду)
        instance.save()

        # Возвращаем ответ - нашу сериализованную модель и статус 200
        return Response(
            ItemLogoSerializer(instance).data,
            status=status.HTTP_200_OK
        )
Обязательно проверяйте, что именно приходит от клиента, иначе будут проблемы. Так же добавьте нужные права в permission_classes.

Теперь - самое простое - конфигурация URL:

api.items.urls.py

from django.conf.urls import url

# Тут должен быть импорт остальных сериализаторов
from .api import ItemLogoAPIView

urlpatterns = [
    # А здесь должны быть остальные URL (создание/получение/обнавление)
    url(r'^(?P\d+)/logo/$', ServiceLogoAPIView.as_view()),
]

Ну что ж, всё выглядит не таким уж сложным. Пришло время закрыть вопросы на Toster'е и StackOverflow.

Django - изменить размер изображения перед сохранением

Как изменить размер изображения перед сохранением? Никак. Но далее я опишу путь, на который указал пользователь StackOwerflow в своём ответе вот на этот вопрос.

Вкратце:

  • Сохраняем объект
  • Проверяем наличие данных в нужном поле типа ImageField
  • Открываем сохранённое изображение и меняем его свойства, как нам нужно

Пример

from PIL import Image
from django import models

from os import path

# Максимальный размер изображения по большей стороне
_MAX_SIZE = 300

class CarManufacter(models.Model):
    """Производитель автомобилей, два поля
       name - название, строка
       logo - логотип, изображение
    """
    name = models.CharField(
        'Наименование',
        max_length=100,
        unique=True
    )
    logo = models.ImageField(
        upload_to=path.join('car', 'manufacter', 'logo'),
        null=True,
        blank=True
    )

    def save(self, *args, **kwargs):
        # Сначала - обычное сохранение
        super(CarManufacter, self).save(*args, **kwargs)

        # Проверяем, указан ли логотип
        if self.logo:
            filepath = self.logo.path
            width = self.logo.width
            height = self.logo.height

            max_size = max(width, height)

            # Может, и не надо ничего менять?
            if max_size > _MAX_SIZE:
                # Надо, Федя, надо
                image = Image.open(filename)
                # resize - безопасная функция, она создаёт новый объект, а не
                # вносит изменения в исходный, поэтому так
                image = image.resize(
                    (round(width / max_size * _MAX_SIZE),  # Сохраняем пропорции
                    round(height / max_size * _MAX_SIZE)),
                    Image.ANTIALIAS
                )
                # И не забыть сохраниться
                image.save(filename)

Зависимости в Angular

Внеднение зависимостей

Все знают, что Angular построен на постоянном и вездесущем внедрении зависимостей, т.н. dependency injection. Существуют как минимум три способа сделать это.

1. Implicit annotation

Зависимости просто перечисляются как аргументы нашего модуля.

Пример

(function (A){
    "use strict";
    A.module('app', []).controller('MyCtrl', function($scope, $sce){
        //Тут должен быть код контроллера
    });
}(this.angular));
Не используйте этот способ!

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

2. $inject

Хороший способ внедрения зависимостей - создать статическое свойство функции под названием $inject. В нужный момент Angular возьмёт список зависимостей оттуда.

Пример

(function (A){
    "use strict";

    function MyCtrl($scope, $sce){
        //Код контроллера
    }

    MyCtrl.$inject = [ '$scope', '$sce' ]; //Строки не сжимаются никогда

    A.module('app', []).controller('MyCtrl', MyCtrl);
}(this.angular));

Уже лучше. Такой код успешно переживёт минификацию.

3. Inline array annotation

Весьма хороший метод, не хуже предыдущего, но многие статические анализаторы кода, например, в Eclipse, считают такое поведение ошибкой (Они не правы!):

Пример

(function (A){
    "use strict";

    function MyCtrl($scope, $sce){
        //Код контроллера
    }

    A.module('app', []).controller('MyCtrl', [ '$scope', '$sce', MyCtrl ]);
}(this.angular));

Такой код тоже хорошо переживёт минификацию.

Дополнительно

Недавно узнал, что в Angular даже есть специальная директива для запрета внедрения зависимостей первым способом - ng-strict-di. Указывать её надо рядом с ng-app:

Пример

<!doctype html>
<html ng-app="app" ng-strict-di>
    <head>
        <!-- Код -->
    </head>
    <body>
        <!-- И тут тоже код -->
    </body>
</html>

После этого попытка внедрения зависимостей через Implicit Annotation будет вызывать ошибку.

DRF, часть 1 - Проектирование структуры API

Введение

Автор не является экспертом с мировым именем в данном вопросе и всего лишь публикует здесь свои мысли, которые кажутся ему правильными на момент написания. Для более полной информации о работе с Django REST Framework обращайтесь к официальной документации.

Раз уж у меня не получается написать полноценную огромную статью про Django REST Framework, следует публиковать хотя бы небольшие заметки. Начать следует с прописных истин.

  1. 1. Дуб - дерево.
  2. 2. Олень - животное.
  3. 3. Смерь - неизбежна.
  4. 4. api - отдельное приложение в нашем проекте
  5. 5. Следует придерживаться общепринятых правил именования частей API
  6. 6. API должен быть версионным
  7. 7. API должен быть простым

С первыми тремя пунктами всё ясно, поэтому перейду сразу к четвёртому и нижеследующим.

Структура каталогов и версионность

Сначала я пытался запихнуть API в разные части основного проекта, создавал в каталоге с приложениями файлы api.py, serializers.py и permissions.py.

О том, насколько это плохая идея, я узнал почти сразу же, когда начал путаться с тем, что и где лежит. Стоило только переименовать один из каталогов, как сразу же всё начинало сыпаться. В итоге я пришёл к тому, что API - не просто отдельное приложение, содержащее только файлы __init__.py и urls.py (со ссылками на нужные файлы в приложениях Django), а полноценный модуль со множеством вложенных модулей. В общем, почувствуйте разницу:

Было
/api/
    __init__.py
    urls.py
/news/
    __init__.py
    admin.py
    api.py
    models.py
    serializers.py
    tests.py
    urls.py
    views.py
/comments/
    __init__.py
    admin.py
    api.py
    models.py
    serializers.py
    tests.py
    urls.py
    views.py
Стало:
/api/
    /v1/
        /news/
            /comments/
                __init__.pt
                api.py
                permissions.py
                serializers.py
                urls.py
            __init__.py
            api.py
            permissions.py
            serializers.py
            urls.py
    __init__.py
    urls.py
/core/
    /comment/
        __init__.py
        api.py
        permissions.py
        serializers.py
        urls.py    
    /news/
        /comments/
             __init__.py
            admin.py
            models.py
            tests.py
            views.py
         __init__.py
        admin.py
        models.py
        tests.py
        views.py

Надеюсь, структура понятна. Во-первых, всё, что связано с API, переехало в одноимённое приложение. Во-вторых, API теперь поддерживает версионность. Для этого нужно всего ничего - создать соответствующие каталоги и правильно описать файл urls.py в самом начале. Я сделал так:

api/urls.py
from django.conf.urls import include
from django.conf.urls import url

urlpatterns = [
    url(r'', include('api.v2.urls')),
    url(r'^v1/', include('api.v1.urls')),
    url(r'^v2/', include('api.v2.urls')),
]

Естественно, где-то в главном файле urls.py есть строка, в которой конфигурация URL для API присоединяется к общей конфигурации URL через всё тот же include().

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

Именование API

Сначала хочу сказать, как делать НЕ НАДО:

Вешать всё на один URI и в зависимости от содержимого полученных пакетов выполнять то или иное действие либо возвращать нужный набор данных.
Пример:
http://example.org/api/

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

Использовать глаголы в частях URL или для выполнения определённых действий.
Пример
GET /api/news/get/?id=1     - получить новость с id=1
GET /api/news/get/all/      - получить все новости
POST /api/news/add/         - добавить новость
POST /api/news/update/?id=1 - обновить новость с id=1
POST /api/news/delete/?id=1 - удалить новость с id=1
Ещё примерчик
GET  /api/news/?id=1               Получение записи с id=1
POST /api/news/?id=1?action=update Обновление записи с id=1
POST /api/news/?id=1?action=delete Удаление записи с id=1

Это API? Конечно же, нет! Суть REST-сервисов в том, что требуемое действие определяется HTTP-заголовком!

Пример
URL - /api/news/:id

GET    /api/news/ - получить список всех новостей
POST   /api/news/ - создать новость
GET    /api/news/12/ - вернёт новость с id=12
PATCH  /api/news/12/ - обновить новость с id=12
DELETE /api/news/12/ - удалить новость с id=12

Разница, как говорится, налицо.

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

Названия сущностей во множественном числе. Каждой сущности - отдельный URL.
Пример

/api/articles/:id/ - статьи
/api/friends/:id/  - друзья
/api/news/:id/     - новости
/api/users/:id/    - пользователи
/api/videos/:id/   - видео
Пример:
GET    /api/acticles/:id/comments/ - получить комментарии к статье
GET    /api/news/:id/comments/     - получить комментарии к новости
POST   /api/articles/:id/comments/ - добавить комментарий к статье
POST   /api/news/:id/comments/     - добавить комментарий к новости
DELETE /api/comments/articles/:id/ - удалить комментарий к статье
DELETE /api/comments/news/:id/     - удалить комментарий к новости

Сериализаторы и всё остальное.

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

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

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

Итоги

  • Разработчики хотят работать с сущностями, а не с URL. Сделайте интуитивно понятными все URL вашего API.
  • Разработчики ленивы и не хотят изучать 34 параметра, влияющие на результат вызова одного единственного метода (из 56, имеющихся в наличии). Старайтесь избегать реализации поведения API через параметры. limit, offset, filter и sorting - необходимое зло, от них никуда не деться.
  • Действие, выполняемое методом API, должно зависеть от HTTP-заголовка. GET для чтения, POST для создания, PUT/PATCH для обновления и DELETE для удаления записей.
  • Пишите документацию к вашему API. Даже самая лучшая структура URL для API неспособна описать различные тонкости и нюансы его работы.

CSRFToken в Angular 1.4

В новом Angular 1.4 одно из приятных изменений - возможность задавать куку и заголовок для CSRF-токена. Раньше делали так:

angular.module('app', ['ngCookies']).run(['$http', '$cookies', function($http, $cookies){
    var token = $cookies.csrftoken;
    $http.defaults.headers.common['X-CSRFToken'] = token;
    $http.defaults.headers.post['X-CSRFToken'] = token;
}]);

Сейчас на этапе конфигурирования приложения можно сделать такой финт:

angular.module('app', []).config(['$httpProvider', function($httpProvider){
    $httpProvider.defaults.xsrfCookieName = 'csrftoken';
    $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
}]);

Оригинальная документация - здесь. Проверял работу на Django 1.8.

EMACS - начало

Введение

Я не буду здесь рассказывать в деталях, чем плоха та или иная IDE или чем мне не угодили VIM и Sublime Text. Начнём лучше с того, чем EMACS хорош.

EMACS хорош в первую очередь тем, что это не просто текстовый редактор, а настоящий конструктор, из которого можно сделать как полноценную IDE для разработки практически на любом языке, так и издательскую систему или текстовый процессор. Основная фишка - возможность расширять возможности редактора путём написания сценариев на языке EMACS Lisp. К счастью, всё уже написано до нас, и лично мне ни разу не приходилось писать что-то своё, однако при этом я смог превратить EMACS в IDE для WEB- и Python-разработки. Да, эту статью я тоже пишу в нём.

Во-вторых, он быстр. Он не требует JRE для своей работы, написан на C и EMACS Lisp, запускается и работает очень быстро.

Это свободное ПО, что для обычного российского пользователя означает как раз "Free beer".

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

Огромная база готовых пакетов. Там есть практически всё, что нужно, от поддержки редких языков до веб-браузера и игр. Серьёзно!

Установка

Установка в Windows предельно проста - нужно зайти на этот сайт и скачать архив с последней версией. Распаковываем её в любое нужное место, находим в каталоге bin файл runemacs.exe и создаём для него ярлык на Рабочем столе.

Установка в Linux может быть чуть сложнее. Как правило, в Debian и Ubuntu LTS идёт устаревшая версия дистрибутива. Я рекомендую использовать не ниже 24.0, в противном случае часть пакетов (можно считать их плагинами) не будет работать. Если в официальном репозитории слишком старая версия, придётся собирать EMACS из исходных кодов, что заслуживает отдельной статьи. Здесь на этом останавливаться не буду.

Помимо самого EMACS необходимо иметь в системе установленную систему контроля версий Git. Пользователи Windows могут скачать здесь.

Комбинации клавиш

Оригинальные комбинации клавиш EMACS устарели. Нажимать [Ctrl] (в любом руководстве по EMACS эта клавиша обозначается как [C]) мизинцем неудобно. Когда-то [Ctrl] располагался на месте сегодняшнего [Alt], и пользоваться им было удобно, но времена мэйнфреймов давно прошли... Подробный разбор того, почему комбинации клавиш в EMACS такие, какие есть, и почему это плохо, производится здесь [EN]. Нет смысла изучать дефолтные настройки комбинаций клавиш, всё равно придётся потом переучитваться. Однако, в экстренных ситуациях может пригодиться как минимум две команды:

Комбинация Действие
[M-X] Вход в минибуфер для ввода команд
[C-X, C-C] Завершение работы редатора

После установки и активации пакета ergoemacs-mode комбинации клавиш будут переопределены. Рекомендую сделать это как можно быстрее, т.к. отвыкнуть от Ctrl+X, Ctrl+C и Ctrl+V очень тяжело, да и нет смысла этого делать.

Терминология

Буфер - аналог вкладки с документом в других тектовых редакторах и IDE. Так же это область, куда выводятся сообщения плагинов или самого редактора.

Минибуфер - самый маленький буфер, расположенный в нижней части окна EMACS. В него вводятся различные команды. Для входа в него следует использовать комбинацию клавиш [M-X] без установленного и включенного пакета ergoemacs-mode, либо [M-A]. После ввода команда запускается на выполнение нажатием [Enter] (в документации EMACS эта клавиша называется [RET])

Пакет - аналог плагина в других редаторах и IDE. Представляет собой сценарий или набор сценариев на языке EMACS Lisp. Часть пакетов является встроенной в редактор и поставляется вместе с ним, другие нужно скачивать из репозитриев. Отметим, что сейчас практически не используется способ установки пакета ручным клонированием репозитория с GitHub, далее под репозиториями будут пониматься специализированные хранилища пакетов.

Основной режим - определяет поведение EMACS для буфера. От этого режима зависит поведение различных клавиатурных комбинаций и набор доступных дейстий. Например, при написании этой статьи я использую web-mode, что даёт подсветку синтаксиса, автоматическое форматирование и применение Emmet для создания разметки. Основной режим может быть лишь один (имеется пакет, позволяющий включить сразу несколько основных режимов для одного буфера, но я им ни разу не пользовался).

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

Первоначальная настройка

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

Настройка через правку файла конфигурации .emacs

Основной файл настроек EMACS называется .emacs и располагается в домашнем каталоге пользователя. В случае с Windows это как правило каталог вроде этого:

C:\Users\xPhoenix\AppData\Roaming\.emacs

Однако, можно переопределить домашний каталог для хранения настроек (важно понимать, что файл .emacs станет вашей прелессссстью, которую вы будете оберегать и лелеять), создав для текущего пользователя системную переменную под именем HOME, и указав в ней путь к каталогу, который следует считать домашним для EMACS (возможно, работает и для других программ из мира Linux, не проверял). У меня эта переменная выглядит так:

D:\xphoenix\

Кроме этого файла EMACS при работе создаёт так же создаёт в домашнем каталоге папку .emacs.d. В ней будут храниться скачанные пакеты, сниппеты, файлы .desktop и т.д. Не надо его удалять!

В любом случае, в наш .emacs следует поместить как минимум вот эти строки:

(defalias 'yes-or-no-p 'y-or-n-p)

(setq package-archives '(
                         ("elpy" . "http://jorgenschaefer.github.io/packages/")
                         ("gnu" . "http://elpa.gnu.org/packages/")
                         ;; ("melpa" . "http://melpa.milkbox.net/packages/")
                         ("melpa-stable" . "http://melpa-stable.milkbox.net/packages/")
                         ("org" . "http://orgmode.org/elpa/")
                         ))

Первая строка указыает на то, что вместо ввода строк yes и no в ответы на запросы системы можно просто нажать [y] или [n] соответственно.

Чуть ниже идёт указание репозиториев для поиска пакетов. Репозиторий MELPA очень популярен и в Интернете, вы будете часто встречать отсылки именно к нему, но использовать его опасно, т.к. пакеты туда попадают прямо из ветки master с GitHub. Поэтому я предпочитаю MELPA-STABLE. Пусть пакеты старые и их гораздо меньше, зато ничего не сломается при очередном обновлении. Впрочем, в редких случаях я пользуюсь MELPA, например, если пакета вообще нет в STABLE или тот, что есть, слишком старый. Так же существует репозиторий MARMELADE, но я не вижу смысла его использовать, т.к. некоторые пакеты в нём даже более старые, чем в MELPA-STABLE.

Настройка через customize

В одной из версий EMACS в стандартную поставку стал входить пакет sustomize. Просто выполните одноименную команду, и попадете в буфер, где будет поле для поиска и множество гиперссылок для перехода к нужным настройкам. Отмечу лишь, что настройка некоторых параметров через customize конфликтует с ручной правкой .emacs, однако, в ряде случаев является более удобной. Некоторые параметры EMACS можно настроить только ручным редактированием .emacs, в customize вы просто не найдёте нужного раздела.

Установка пакетов

Указав в вашем .emacs репозитории так, как указано выше, перезапустите редактор (можно сделать и без перезапуска, но не буду усложнять) и выполните команду list-packages. Откроется буфер, содержащий список пакетов в доступных репозиториях. Выбор пакета для установки осуществляется нажатием клавиши [I], отмена выбора - [U], пометка на удаление - [D]. После того, как будет выбрано, что делать с пакетами, следует нажать клавишу X для запуска операций. EMACS установит и при необходимости выполнит компиляцию нужных пакетов.

Есть несколько пакетов, которые я настоятельно рекомендую к установке.

  • auto-complete - предназначение ясно из названия. Через него работают многие другие пакеты, например, jedi.
  • autopair - автоматически закрывает скобки
  • company - ещё одно средство для автокомплита, через него работают некоторые пакеты, которые не работают с auto-complete
  • emmet-mode - незаменимое средство для верстальщиков. Рекомендую ставить из MELPA, т.к. в STABLE очень старая версия, которая работает хуже и многого не умеет.
  • ergoemacs-mode - пакет, устанавливающий комбинации клавиш, удобные для использования людьми, а не Ричардом Столлманом. Подробное описание клавиш здесь.
  • flycheck - модуль проверки чего угодно на лету. На самом деле по-тихому вызывает имеющиеся в системе средства проверки и статического анализа кода. Имеет возможность конфигурирования того, какие средства и с какими параметрами следует использовать. Является более новым и прогрессивным в сравнении с загнивающим flymake.
  • jedi - автокомплит для Python-разработчиков. Настройка данного пакета достойна отдельной статьи.
  • js2-mode - расширенный по сравнению с js-mode режим правки JavaScript-кода. Ставить js3-mode не рекомендую, т.к. проект давно загнулся.
  • less-css-mode - не вижу смысла объяснять назначение данного пакета.
  • monokai - тема из Sublime Text 2, пожалуй, лучшая, что мне приходилось видеть.
  • neotree - порт плагина NerdTree из VIM, отображает слева (или справа, настраивается) дерево каталогов и файлов. Лучше ставить из MELPA.
  • rainbow-mode, rainbow-delimiters - в паре подсвечивают скобки и другие элементы разными цветами, позволяет легко находить ошибки типа "Забыл закрыть скобку".
  • web-beautify - доступен только из MELPA, позволяет автоматически форматировать JS, CSS и HTML-файлы. Для работы требует установленный в системе NodeJS и его пакет web-beautify.
  • web-mode - добавляет в EMACS возможность редактировать файлы XML, HTML, XHTML и др. Обеспечивает подсветку синтаксиса и т.д.