Установка coffescript
npm install -g coffee-script
Вывод подсказки по интерпретатору coffeescript
coffee -h
Вывод версии coffeescript
coffee -v
Запуск интерпретатора coffescript
coffee
Выход из интерпретатора coffescript
Ctrl + C
Ctrl + C
Запуск файла coffeescript в интерпретаторе
coffee my/folder/myfile.coffee
Компиляция файла coffescript
coffee -c myfile.coffee
Компиляция файла в целевую папку (Компиляция всегда создает файл с тем же именем, что и оригинальный файл)
coffee -o /to/my/javascript/folder -c /from/my/folder/myfile.coffee
Убрать при компиляции подстановку кода в анонимную функцию обертку (function(){...}).call(this)
coffee -b -c myfile.coffee
Вывод результата компиляции на экран
coffee -p myfile.coffee
Автоматическая компиляция файлов coffeescript при их обновлении в локальной папке
coffee -w -c /to/my/javascript/folder
Для написания coffeescript-кода из javascript-кода:
1) Удаляем все фигурные скобки ({) (}) и точки с запятыми (;).
2) Расставляем правильно пробелы для обеспечения отступов в коде.
3) Заменяем везде слово function на символ (->) после круглых скобок.
4) Хотя круглые скобки удалять необязательно. При наличии сомнений всегда пишите круглые скобки.
Круглые скобки можно использовать для группировки кода.
Удаляем круглые скобки (), но не все.
Оставляем круглые скобки в местах, где при определении функции перечислены подставляемые переменные.
При вызове функции и передаче ей аргументов скобки можно не писать.
5) при объявлении переменных не используем слово var.
Экспортирование функции в глобальную зону видимости глобального объекта: window, this или @
window.sayHi -> console.log('Hello, World!')
this.sayHi -> console.log('Hello, World!')
@sayHi -> console.log('Hello, World!')
Результат
(function(){this.sayHi() = function(){return console.log('Hello, World!');}}).call(this);
Значение this можно обозначать как @.
Вставка значения внутрь строки #{...}, расположенной в двойных кавычках " ". Строки в одинарных кавычках ' ' вставку значений не поддерживают.
someName = "Boris"
files = "<My name is #{someName}"
Внутри #{} иожет быть любой coffeescript-код
text = "Add numbers: #{1+1}"
console.log "It's beautiful #{if day is 'Sunday' then day else 'Day'}"
Для задания отформатированного многострочного текста используются тройные кавычки """ ... """ или ''' ... '''
text = """
This
is
my
text
"""
console.log text
Комментарии
# это однострочный комментарий coffeescript. Он не попадет в код javascript.
###
Это
многострочный
комментарий
coffeescript.
Он попадет в код javascript.
###
В JavaScript-коде появится такой комментарий.
/*
Это
многострочный
комментарий
coffeescript.
Он попадет в код javascript.
*/
Регулярные выражения в coffeescript пишутся так же как и в javascript.
Расширенная запись регулярного выражения в coffeescript. Выражение внутри /// ... /// можно записывать в несколько строк и комментировать.
pattern = /// ^ # слово начинается с
(a|b) # символа a или b
{3} # повторенного 3 раза подряд
$ # и окончивается им
///
Компилятор coffeescript автоматически преобразует == в ===
Поэтому используйте в coffeescript только == и !=
Проверка существования переменной ?
console.log x?
console.log someObject?.someFunction()
Результат
(function(){
console.log(typeof x !== "undefined" && x !== null);
console.log(typeof someObject !== "undefined" && someObject !== null ? someObject.someFunction() : void 0);
}).call(this)
Псевдонимы операторов
Внимание! Лучше использовать псевдонимы CoffeScript!
CoffeeScript | JavaScript
|
is | ===
isnt | !==
not | !
and | &&
or | ||
true, yes, on | true
false, no, off | false
@, this | this
of | in
in | не определен
Пример
name = "Mark"
console.log name is "Mark"
console.log name isnt "Bob"
Внимание! Нельзя использовать конструцию is not. is not не равно isnt! Это может привести к логической ошибке!
Условные выражения
today = "Monday"
if today is "Sunday"
console.log "Today is Sunday"
else
console.log "Today is #{today}"
Однострочное условное выржение
Однострочные вырожения сложнее для восприятия
today = "Monday"
console.log if today is "Sunday" then "Today is Sunday" else "Today is #{today}"
CoffeeScript не поддерживает выражения вида a === b ? c = 1 : c = 2
If - Else If - Else
today = "Monday"
if today is "Sunday"
console.log "Today is Sunday"
else if today is "Saturday"
console.log "Today is Saturday"
else
console.log "Today is #{today}"
Unless - противоположность If. Если выражение равно false (взамен true у if()), то выполнить блок кода.
today = "Monday"
unless today is "Sunday"
console.log "No football today!"
Результат
(function(){
var today = "Monday";
if (today !== "Sunday") {console.log("No footbal today!");}
}).call(this);
Условные инструкции
today = "Sunday"
console.log "Today is Sunday" if today is "Sunday"
Конструкция Switch - Case - Break - Default в coffeescript заменяется на Switch - When - , - Else
today = "Monday"
switch today
when "Saturday"
console.log "It is Saturday"
when "Sunday", "Monday"
console.log "It is Sunday"
else
console.log "Let's go to work!"
Создание функций в coffeescript
myFunction = () ->
console.log "Hello!"
или можно записать так
myFunction = ->
console.log "Hello!"
или так
myFunction = -> console.log "Hello!" (напоминает EcmaScript 6: var a = x => x * x)
myFunction()
Но мне кажется, что скобки лучше писать!
CoffeScript при компиляции автоматически добавляет в каждую функцию return, возвращая последнее значение.
Если вам нажо возвращать другое значение, то добавьте return в вашу функции в coffescript.
myFunction = () ->
x = 1
y = 2
return x
Если не нужно, чтобы функция возвращала какое-либо значение, то доабавьте в концее её return null или return undefined
myFunction = () ->
x = 1
y = 2
return undefined
Определение функции с аргументами
myFunction = (name, surname) ->
console.log "My nam is #{name} #{surname}"
myFunction("Boris", "Viktorov")
или можно записать вызов функции так
myFunction "Boris", "Viktorov"
Но лучше использовать скобки!
При определении функции можно задавать значения по умолчанию
myFunction = (name = "Stanislav", surname = "Nikolaev") ->
console.log "My nam is #{name} #{surname}"
myFunction()
В качестве значений по умолчанию можно использовать и функции
defaultRate = () ->
return 0.05
calculateTotal = (num, rate = defaultrate()) ->
return num * rate
Важно! Значения по умолчанию должны идти последними в списке аргументов функции, так как они являются по смыслу необязательными.
Переменное число рагусментов.
Групповые аргументы обозначаются троеточием (...) после названия группового аргумента.
Троеточие (...) переводится как "и другие..."
Групповые аргументы args[] и kwargs{} используются когда функция может по смыслу принимать переменное число аргументов.
Групповые аргументы, в отличии от аргументов по умолчанию, можно помещать в любое место перечня аргументов функции. Но одна функция может иметь только один список групповых аргументов!
myFunction = (etc...) ->
console.log "Length: #{etc.length}, Values: #{etc.join(', ')}"
myFunction("a", "b", "c")
Результат
(function(){
var myFunction = function(){
var etc;
etc = 1 <= arguments.length ? Array.protorype.slice.call(arguments, 0) : [];
console.log("Length: " + etc.length + ", Values: " + etc.join(', '));
};
myFunction("a", "b", "c");
}).call(this);
Перечисление групповых аргументов в любом списке аргументов функции
myFunction = (first, middles..., last) ->
parts = []
if first?
parts.push(first.toUpperCase())
for middle in middles
parts.push(middle.toLowerCase())
if last?
parts.push(last.toUpperCase())
parts.join('/')
console.log(myFunction("a", "b", "c"))
Аргументы можно подставлять в функции и виде массива аргументов (arr...)
myFunction = (etc...) ->
console.log "Length: #{etc.length}, Values: #{etc.join(', ')}"
a = ["a", "b", "c"]
myFunction(a...)
Результат "Length: 3, Values: a, b, c"
Массивы
При объявлении массива, если элементы располагаются на отдельных строках, то можно не ставить запятые!
myArray = [
"a"
"b"
"c"
]
console.log myArray
Допускается использование одновременно запятых и новых строк без запятых.
myArray = [
"a", "b", "c"
"d", "e", "f"
"g", "h", "i"
]
Проверка нахождения значения в массиве
nyArray = ["a", "b", "c"]
if "b" in myArray
console.log "I found b"
unless "d" in myArray
console.log "d was nowhere to be found"
Обмен значениями переменных
x = "X"
y = "Y"
[x, y] = [y, x]
console.log "x is #{x}" # x = "Y"
console.log "y is #{y}" # y = "X"
Присвоение значений множеству переменных сразу через массив
rack = () ->
[200, {"Content-Type": "text/html"}, "Hello, World!"]
[status, headers, body] = rack()
console.log "status is #{status}"
console.log "headers is #{JSON.stringify(headers)}"
console.log "body is #{body}"
Внимание! При присваивании с перестановкой и множественном присваивании не нада предварительно объявлять переменные, участвующие в этой операции. Компилятор сам позаботится об этом.
Для присваивания множества значений можно использовать синтаксис групповых аргументов.
myValues = [1, 2, 3, 4, 5]
[start, middle..., end] = myValues
console.log start
console.log middle
console.log end
Если число значений в массиве будет меньше, чем число переменных, то у лишних переменных будет значение "undefined".
Если число переменных будет меньше, чем значений в массиве, то лишние значения останутся никому не присвоенными.
Диапазоны.
Создание массивов с диапазонами чисел [..] включая последнее число.
myArray = [1..10]
console.log myArray # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Создание массивов с диапазонами чисел [...] исключая последнее число.
myArray = [1...10]
console.log myArray # [1, 2, 3, 4, 5, 6, 7, 8, 9]
Создание массивов со значениями чисел в порядке убывания.
myArray = [10..1]
myArray = [10...1]
Вырезание элементов из массива
myArray = [1..10]
partOfArray = myArray[0..2] # var partOfArray = myArray.slice(0, 3);
partOfArray = myArray[0...3] # var partOfArray = myArray.slice(0, 3);
partOfArray = myArray[4...8] # var partOfArray = myArray.slice(4, 8);
Замена сразу целой группы значений внутри массива
myArray = [0..10]
myArray[4..7] = ["a", "b", "c", "d"]
console.log myArray # [1, 2, 3, 4, "a", "b", "c", "d", 9, 10]
Вставка значений в середину массива, используя -1
myArray = [0..10]
myArray[4..-1] = ["a", "b", "c", "d"]
console.log myArray # [1, 2, 3, 4, "a", "b", "c", "d", 5, 6, 7, 8, 9, 10]
Создание пустого объекта
obj = {}
Создание объекта с данными
obj =
firstName: "Mark"
lastName: "Bates"
console.log obj
Внимание! Если свойства объекта располагаются на отдельных строках, то из не надо отделять запятыми (,), также можно в этом случае не писать фигурные скобки.
Хотя использование фигурных скобок мне кажется удобным.
obj = {
firstName: "Mark"
lastName: "Bates"
}
console.log obj
Добавление в объект функции
obj = {
firstName: "Mark"
lastName: "Bates"
fullName: () ->
return "#{@firstName} #{@lastName}"
}
console.log obj
Иногда объект строится так
foo = "FOO"
bar = "BAR"
obj = {
foo: foo
bar: bar
}
Эту процедуру можно упростить, просто записав
foo = "FOO"
bar = "BAR"
obj = {
foo
bar
}
Внимание! При таком способе записи использование фигурных скобок обязательно!
При использовании объекта в качестве аргумента функции использование фигурных скобок необязательно.
myFunction = (obj) ->
console.log obj
myFunction(foo: "FOO", bar: "BAR")
это аналогично
myFunction({foo: "FOO", bar: "BAR"})
Хотя я бы скобки писал. Так понятнее.
Присвоение группы значений из объекта переменным (подобно тому как мы делали с массивами)
book = {
title: "Programming"
author: "Mark Bates"
chapter1: {
name: "Part 1"
pageCount: 33
}
chapter2: {
name: "Part 2"
pageCount: 40
}
}
{author, chapter1: {name, pageCount}} = book
console.log "Author #{author}"
console.log "Chapter 1 #{name}"
console.log "Page Count #{pageCount}"
Итерации по элементам массива.
for <переменная_цикла> in <массив>
<ваш_код>
myLetters = ["a", "b", "c"]
for letter in myLetters
console.log letter
for x in [1..5]
console.log x
Увеличение итерационного значения в цикле через шаг приращения by
myLetters = ["a", "b", "c"]
for letter in myLetters by 2
console.log letter
После ключевого слова by можно указать любое число (например -1) или функцию (например by func()), а цикл for будет выполнять итерпции по массиву с указанным шагом.
С помощью ключевого слова when можно добавить в цикл условие.
a = [1..10]
for num in a when num < 5
console.log num
это аналогично такому коду
for num in a
if num < 5
console.log num
Итерации по атрибутам объектов.
for <переменная_для_ключа>, <переменная_для_значения> of <объект>
<ваш_код>
person = {
firstName: "Mark"
lastName: "Bates"
}
for key, value of person
console.log "#{key} is #{value}"
Важно! Отличия итераций по элементам объекта от итераций по элементам массива.
В итерации по атрибутам объекта необходимо объявить переменную для ключа и переменную для значения. И вместо слова in используется слово of.
Ключевое слово by в итерации по элементам объектов не применяется.
Но ключевое слово whene в итерации по элементам объектов может использоваться.
person = {
firstName: "Mark"
lastName: "Bates"
}
for key, value of person when value.lenth < 5
console.log "#{key} is #{value}"
это аналогично такому коду
for key, value of person
if value.length < 5
console.log "#{key} is #{value}"
Перечисление свойств принадлежащих только данному объекту, а не его прототипу, через for own (аналог .hasOwnProperty в JavaScript).
person = {
firstName: "Mark"
lastName: "Bates"
}
Object.prototype.dob = new Date();
for own key, value of person
console.log "Only this object #{key} is #{value}. Not prototype!"
Такой цикл обнаруживает только атрибуты явно объявденные в данном объекте.
В coffeescript помимо стандартного цикла while можно использовать его противоположность - цикл until
Цикл while продолжает выполнять операции, пока условное выражение остается истинным - true.
Цикл until продолжает выполнять операции, пока условное выражение остается ложным - false.
until index++ >= numberOfTimes
console.log "Not now"
Генераторы
Генераторы - это обычные циклы, в которых блок кода находится в той же строке, что и конструкция цикла.
Простой пример генератора
myLetters = ["a", "b", "c"]
console.log letter.toUpperCase() for letter in letters
Генераторы можно использовать для сохранения значений, получаемых в цикле.
Заключая инструкцию генератора в круглые скобки, можно сохранить результаты в другой переменной.
Сохраним преобразованные значения в новом массиве upLetters
myLetters = ["a", "b", "c"]
upLetters = (letter.toUpperCase() for letter in myLetters)
console.log upLetters
Можно также сохранять значения в переменную в многострочной версии цикла for
myLetters = ["a", "b", "c"]
upLetters = for letter in myLetters
letter.toUpperCase()
console.log upLetters
Однако код генераторов трудно читать и понимать.
Циклы с ключевым словом do().
Ключевое слово do() удобно использовать вместе с таймерами, чтобы не потерять значение переменных, получаемых в цикле.
fox x in [1..5]
do(x) ->
setTimeout() ->
console.log x
Ключевое с лово do() - сделать - создает функцию-обертку вокруг программного кода, реализующего тело цикла, которая принамает занчение переменной, имеющееся на момет её вызова.
Что эквивалентно следующему коду в JavaScript.
var func = function(x){
return setTimeout(function(){
console.log(x);
}, 1);
};
for (var x = 1; x <= 5, x++) {
func(x);
}
Классы
Создание пустого класса
class Employee
Результат
(function(){
var Employee = (function (){
function Employee(){}
return Employee;
})();
}).call(this);
Создание экземпляров класса
class Employee
emp1 = new Employee()
emp2 = new Employee()
Внимание! При использовании ключевого слова new круглые скобки после названия класса ставить необязательно. Хотя так понятнее.
Создание функциий внутри класса
class Employee
myFunc: (year: 1976, month = 7) ->
return new Date()
emp1 = new Employee()
console.log emp1.myFunc()
Создание класса с функцией constructor
В coffeescript при создании класса можно определить функцию конструктор constructor, которая автоматически вызывается при создании нового экземпляра объект.
Функция constructor - это обычная функция, которая просто автоматически вызывается в момент создания объекта.
class Employee
constructor: () ->
console.log "Object created"
myFunc: (x) ->
console.log x
emp1 = new Emloeyee() # "Object created"
emp2 = new Emloeyee() # "Object created"
Функция constructor позволяет быстро заполнить объект его индивидуальными данными сразу при его создании.
Создание класса с атрибутами
class Employee
constructor: (name) ->
@name = name
myFunc: () ->
console.log @name
emp1 = new Employee("Boris")
emp1.myFunc()
Определени функции constructor можно сократить
class Employee
constructor: (@name) ->
myFunc: () -> console.log "My name: #{@name}"
myFunc2: () ->
@myFunc() # Вызвать функцию this.myFunc()
Можно подставлять несколько значений
class Employee
constructor: (@name, @surname) ->
myFunc: () -> console.log "My name: #{@name} and surname: #{@surname}"
myFunc2: () ->
@myFunc() # Вызвать функцию this.myFunc()
Можно подставлять массив или объект
class Employee
constructor: (@attributes) ->
myFunc: () -> console.log "My name: #{@attributes.name} and surname: #{@attributes.surname}"
myFunc2: () ->
@myFunc() # Вызвать функцию this.myFunc()
emp1 = new Empoyee({name: "Boris", surname: "Viktorov"})
Наследование классов
class Employee
constructor: (name) ->
@name = name
myFunc: () ->
console.log @name
class Manager extends Employee
emp1 = new Employee({name: "Mark", date: new Date(), salary: 5000})
emp1.myFunc()
manager = new Manager({name: "Bob", date: new Date(), salary: 70000})
manager.myFunc()
Наследовать классы также можно с использование пространства имен.
class Responder.Update extends MyClass.Show
Переопределение атрибутов в наследуемых классах
class Employee
constructor: (name) ->
@name = name
myFunc: () ->
console.log @name
class Manager extends Employee
myFunc: () ->
console.log "This is a manager"
Как видно при наследовании атрибуты могут просто перезаписываться. Так же могут добавляться новые атрибуты.
Вызов родительской функции в наследуемом классе через ключевое слово super
class Employee
constructor: (name) ->
@name = name
myFunc: () ->
console.log @name
class Manager extends Employee
myFunc: () ->
super # тут вызывается оригинальная функция myFunc() из класса Employee
console.log "This is a manager"
Вызов super можно выполнять в любой точке функции, переопределяющей её!
Вызывая функцию super нет необходимости передавать ей аргументы явно. По умолчанию, все аргументы, полученные переопределяющей функцией, автоматически будут переданы функции super.
Однако функции super можно передавать аргументы и явно.
Функции класса
Функции класса не требуют для их вызова наличия (предварительного создания) экземпляра класса.
Они могут использоваться для подсчета числа созданных объектов или для организации пространства имен для своих функций.
Примером такой функции в JavaScript служит Math.random(). Чтобы получить случайное число не нужно создавать новый объект Math.
Math задает лишь простарнство имен для функции random().
Функции класса создаются при помощи символа @ (this) перед названием функции.
Ссылка this будет ссылаться на класс Employye, а не на конкретный экземпляр класса.
Внутри функции класса допускает использовать только другие функции и атрибуты класса.
При работе с функциями и атрибутами класса ключевое слово super имеет ограниченное примение. super можно использовать только для вызова оригинальной функции, переопределенной в дочернем классе.
Но, если оригинальная функция, вызываемая с помощью ключевого слова super, ссылается на какие-либо атрибуты класса, то это приведет к появлению большой ошибки во время выполнения кода.
Поскольку атрибуты класса Employee будут скрыты от класса Manager, то он не сможет напрямую обратиться к родительским атрибутам и произойдет ошибка.
class Employee
constructor: () ->
Employee.hire(@)
@hire: (employee) ->
@allEmployees ||= []
@allEmployees.push(employee)
@total: () ->
console.log "There are #{@allEmployees.length} employees."
new Employee()
new Employee()
new Employee()
Employee.total() # There are 3 employees.
Важно! Лучше избегать использование ключевого слова super в функциях класса.
Лучше делать все функции самодостаточными, автономными и независимыми от других функций!
Функции прототипа
Добавление функций и атрибутов во все экземпляры объектов класса через прототип prototype.
В coffeescript добавление через прототип делается с помощью символов (::).
myArray = [1..10]
try
console.log myArray.size()
catch (error)
console.log error
Array::size = () ->
@length
console.log myArray.size()
Результат
(function(){
var myArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
try {
console.log(myArray.size());
} catch (error) {
console.log(error);
}
Array.prototype.size = function(){
return this.length;
};
myArray.size();
}).call(this);
Важно! Оператор :: был введен исключительно для удобства записи. В coffescript можно писать по-старинке непосредственно атрибут prototype.
Отличие -> от =>
=> привязывает функцию (bind) к контексту её выполнения. То есть позволяет не терять данные при вызове функции, созданной внутри класса и если происходя асинхронные операции.
Подробнее об этом можно прочитать в статье Understanding JavaScript Function Invocation and this. Автор Yehuda Katz.
Функциии setTimeout и setInterval выполняются асинхронно. То есть функциия, запущенная ранее, может выполниться позже, чем функция, которая была запущена позже. Из-за этого могут потеряться данные.
Сборка coffeescript через Cake
Cake доступен сразу после установки coffeescript.
Все задания по сборке должны находится в файле Cakefile, который должен располагаться в папке, где должны выполняться задания. Обычно это корневой каталог проекта.
Код файла Cakefile должен быть написан на coffeescript.
Создание задания для Cake в Cakefile.
Чтобы определить задание для Cake следует вызвать функцию task(), автоматически добавляемую во все файлы Cakefile.
В первом аргумента функции task() передается имя задания, которое будет указываться в команде выполнения этого задания.
Во втором аргументе передается текстовое описание задания. Оно будет отображаться при выводе списка доступных заданий.
В последнем аргументе передается ссылка на функцию, которая будет вызвана для выполнения задания. Эта функция функция как раз будет выполнять всю работу, связанную с данным заданием.
task ("greet", "Say Hello!", () -> console.log "Hello World!")
Результат
(function(){
task("greet", "Say Hello!", function () {console.log "Hello World!";});
}).call(this);
Чтобы вывести список доступных заданий можно воспользоваться утилитой командной строки
> cake
Если теперь запустить Cakefile, то в консоли будет выведено имя задания и описание задания:
Cakefile defines the folowing tasks:
cake greet #Say Hello!
Для запуска задания в консоли нужно набрать cake и имя задания
> cake greet
В результате в консоли будет выведено:
Hello World!
Передача аргументов в задание через функцию option.
option принимает 3 аргумента:
Первый аргумент - краткая форма параметра -n.
Второй аргумент - длинная форма параметра --name.
Третий аргумент - описание значения параметра.
Изменим код Cakefile
option ('-n', '--name [NAME]', 'name you want to greet')
task ("greet", "Say hi to someone", (options) ->
message = "Hello "
if options.name?
message += options.name
else
message += "World"
console.log message
)
Результат
(function(){
options('-n', '--name [NAME]', 'name you want to greet');
task ("greet", "Say hi to someone", function (options) {
var message = "Hello ";
if (options.name !== null) {
message += options.name;
} else {
message += "World";
}
console.log (message);
});
}).call(this);
В этой реализации задания вызывается функция option, которой передается 3 аргумента.
-n - краткая форма
--name [NAME] - длинная форма ([NAME] сообщает, что здесь ожидается некоторое значение. Без него будет сгенерирована ошибка.)
'name you want to greet' - описание
Важной! Значение перрвого аргумента не является обязательным, его можно просто заменить на null в первом аргументе.
Выполним команду cake
> cake
Уидим в консоли
Cakefile defines the folowing tasks:
cake greet #Say hi to someone
-n, --name name you want to greet
Важно! Параметры задания не связаны с определенными значениями - они определяются для всех заданий сразу!
Если в дополнение к заданию greet определить еще одно задание, то оба они смогут принимать параметр --name.
Хотя это и не большая проблема, необходимо с осторожностью подходить к выбору имен параметров и сопровождать их правильными описаниями.
Выполнить задание greet с параметром --name можно следующим образом.
> cake -n Mark greet
Важно! Все параметры помещаются в командной строке преред именем задания! Иначе вы получите сообщение об ошибке!
Вывод
Hello, Mark
Если нужно сделать передачу параметра обязательным, то для этого необходимо вручную организовать проверку параметра в определении задания и возбудить ошибку в случае его отсутствия.
option ('-n', '--name [NAME]', 'name you want to greet')
task ("greet", "Say hi to someone", (options) ->
throw new Error("[NAME] is required") unless options.name?
console.log "Hello, #{options.name}"
)
Вызов заданий из других заданий c помощью функции invoke()
task ("clean", "Clean up build directories", () -> console.log "cleaning up...")
task ("build", "Build the project files", () -> console.log "building...")
task ("package", "Clean, build and package the project", () ->
invoke("clean")
invoke("build")
console.log "packaging..."
)
Вывод cake
> cake
Cakefile defines the following tasks:
cake clean # Clean up build directories
cake build # Build the project files
cake package # Clean, build and package the project
Выполнить 2 задания
> cake clean build
Выполнить 1 задание с вызовом двух предыдущих
> cake package
Внимание! Когда внутри одного задания вызваются другие задания, то они выполняются асинхронно. Такое поведение может приводить к ошибкам!
Тоже происходит, когда одной командой cak выполняетс цепочка заданий. Они все будут выполняться асинхронно, а не в порядке их вызова.
Разработка через тестирование
Сначала создаются контрольные примеры или тесты, а затем уже пишется сам код.
Хорошая статья на эту тему How to Become a Test-driven Developer
Установка Jasmine для тестирования кода
Установить jasmine-headless-webkit. Но он требует Ruby.
Работа с Jasmine
Создайте новую папку проекта.
mkdir project
Перейдите в нее.
cd project
Выполните команду
jasmine init
В папке project появится следующая структура
public/
javascripts/
Player.js
Song.js
Rakefile
spec/
javascripts/
helpers/
SpecHandler.js
PalyerSpec.js
support/
jasmine.yml
jasmine_config.rb
jasmine_runner.rb
Проверяем настройки командой
jasmine-headless-webkit -c
На экране должно появиться
Running Jasmine specs...
.....
PASS: 5 tests, 0 failures, 0.009 secs.
Создадим файл Cakefile
exec = require('child_process').exec
tesk ("test", (options) =>
exec ("jasmine-headless-webkit -c", (error, stdout, stdrr) ->
console.log stdout
)
)
Первая строка задания импортирует модуль child_process Node.js
Функци exec() позволяет выполнять команды в консоли и захватывать вывод этих команд.
Ниже мы создали задаине "test", которое выполняет команду "jasmine-headless-webkit -c", запускающую тесты.
По окончании тестирования вызывается функция обратного вызова и результаты тестов выводятся в консоль.
Теперь выполним наши тесты.
> cake test
Почистит проект
Удалим папки и файлы, чтобы получить следующую структуру проекта
src/
Rakefile
spec/
javascripts/
helpers/
support/
jasmine.yml
jasmine_config.rb
jasmine_runner.rb
Изменим настройки в файле spec/javascripts/helpers/support/jasmine.yml
src_files:
- "**/*.coffee"
helpers:
- "helpers/**/*.coffee"
spec_files:
- "/**/*_spec.coffee"
src_dir: "src"
spec_dir: spec/javascripts
Теперь все готово к использованию Jasmine
Создадим в папке spec файл calculator_spec.coffee
Запишем в файле наши тесты
Прежде всего необходимо создать блок описания describe. Этот блок определяет объект испытаний.
Обычно в этом блоке описываются классы и функции.
Первым аргументом describe является строка, представляющая объект испытаний.
Во втором аргументе передается функция, содержащая все тесты, связанные с данным объектом испытаний.
Тесты определяются внутри функции it.
Блок it принимает 2 аргумента.
Первый аргумент - строка, которая описывает, что планируется сделать в ходе теста.
Второй аргумент - функция, которая содержит утверждения, истинность которых требуется проверить.
Тестирование выполняется с помощью методов сопоставления.
Если метод возвращает true, то считается, что тест пройден, в противном случае считается, что тест не пройден.
describe ("Calculator", () ->
it ("does something", () ->
expect(1+1).toEqual(2)
expect(1+1).not.toEqual(3)
)
)
Модульное тестирование
Каждый внутренний блок describe тестирует свою отдельную функцию.
В блоках it определяется реализация тестов данной функции
describe ("Calculator", () ->
describe("#add", () ->
it ("add two numbers", () ->
calculator = new Calculator()
expect(claculator.add(1, 1)).toEqual(2)
)
)
describe("#substract", () ->
it ("substracts two numbers", () ->
calculator = new Calculator()
expect(claculator.substract(10, 1)).toEqual(9)
)
)
describe("#multiply", () ->
it ("multiplies two numbers", () ->
calculator = new Calculator()
expect(claculator.multiply(5, 4)).toEqual(20)
)
)
describe("#divide", () ->
it ("divides two numbers", () ->
calculator = new Calculator()
expect(claculator.devide(20, 5)).toEqual(4)
)
)
)
Внимание! Символом (#) мы условно обозначаем, что тестируем функцию экземпляра класса.
Для описания функций класса мы будем использовать символ точки (.)
Создадим в папке src файл calculator.coffee
class @Calculator
add: (a, b) -> a+b
substract: (a, b) -> a-b
multiply: (a, b) -> a*b
devide: (a, b) -> a/b
Запуск тестов
> cake test
Использование повторяющегося кода в тестах через beforeEach (доступно также afterEach)
describe ("Calculator", () ->
beforeEach (() ->
@calculator = new Calculator()
)
describe("#add", () ->
it ("add two numbers", () ->
expect(@claculator.add(1, 1)).toEqual(2)
)
)
describe("#substract", () ->
it ("substracts two numbers", () ->
expect(@claculator.substract(10, 1)).toEqual(9)
)
)
describe("#multiply", () ->
it ("multiplies two numbers", () ->
expect(@claculator.multiply(5, 4)).toEqual(20)
)
)
describe("#divide", () ->
it ("divides two numbers", () ->
expect(@claculator.devide(20, 5)).toEqual(4)
)
)
)
Внимание! Область действия функций beforeEach и afterEach простирается вниз до конца текущего блока describe, распространяясь на все вложенные блоки describe, независимо от глубины их вложенности!
Функций beforeEach может быть сколько угодно и они могут помещаться на любой уровень вложенности.
Создание собственных методов сопоставления
Чтобы определеить собственный метод сопоставления прежде необходимо определить функцию beforeEach.
Внутри beforeEach следует вызваеть встроенную функцию addMatches. Она принимает объект, содержащий имена добавляесых методов сопоставления и функции, реализующие их.
Собственный метод сопоставления должени возвращать либо true, либо false.
В папке spec/javascripts/helpers/directory файл с именем to_be_scientific.coffee
beforeEach () ->
@addMatchers
toBeScientific: () ->
@actual.scientific is true
Результат
(function(){
beforeEach(function(){
return this.addMatchers({
toBeScientific: function(){
return this.actual.scientific === true;
}
});
});
}).call(this);
Внимание! Выбор имени файла не имеет большого значения, но если имя совпадает с названием метода, то проще определить, что внутри файла.
Методы сопоставления необязательно помещать в отдельные файлы. Их можно определить в одном общем файле. Но разделение делает код проще.
Одноразовые методы сопоставления можно определять в тестах внутри блоков describe.
Собственный метод сопоставления можно задействовать в тестах.
describe ("Calculator", () ->
describe("scientific mode", () ->
it ("is in scientific mode", () ->
calculator = new Calculator)
expect(@claculator).toBeScientific()
)
)
)
Node JS
Команда coffee может выполнять файлы с кодом для Node JS.
> coffee file.coffee
If - Else If - Else
ОтветитьУдалитьtoday = "Monday"
if today is "Sunday"
console.log "Today is Sunday"
else if if today is "Saturday"
//опечатка в последней строчке - второй if не нужен
Спасибо. Исправил.
ОтветитьУдалить