diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..936d024 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_size = 4 +indent_style = space +trim_trailing_whitespace = true + +[*.json] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..db814bf --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "hrundel/node" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d48db5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +/node_modules +*.log diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..f5357d5 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +save=true +save-exact=true diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4909f83 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - "6" diff --git a/README.md b/README.md index 07fbe88..44a59bf 100644 --- a/README.md +++ b/README.md @@ -1 +1,68 @@ -# javascript-task-2 \ No newline at end of file +# Задача «Телефонная книга» + +Перед выполнением задания внимательно прочитайте: + +- [О всех этапах проверки задания](https://github.com/urfu-2016/guides/blob/master/workflow/extra.md) +- [Как отправить пулл](https://github.com/urfu-2016/guides/blob/master/workflow/pull.md) +- [Как пройти тесты](https://github.com/urfu-2016/guides/blob/master/workflow/test.md) +- Правила оформления [javascript](https://github.com/urfu-2016/guides/blob/master/codestyle/js.md), [HTML](https://github.com/urfu-2016/guides/blob/master/codestyle/html.md) и [CSS](https://github.com/urfu-2016/guides/blob/master/codestyle/css.md) кода +- [Лекцию «Типы данных»](https://urfu-2016.github.io/javascript-slides/02-types/#/) + + +## Основное задание + +> Мы очень хотим, чтобы код вы написали сами, а не пользовались внешними библиотеками. + +Как известно, каждый уважающий себя разработчик должен в жизни сделать три вещи: +- [x] посадить DOM дерево +- [x] построить абстракцию +- [ ] ~~вырастить~~ написать телефонную книгу + +Предлагаем вам пройти легкий путь становления уважающего себя разработчика и реализовать для скрипта телефонной книги __phone-book.js__ ряд необходимых методов. + +Метод __add__ для добавления записей: +* На вход принимает «Телефон», «Имя» и «Электронную почту» +* Возвращает true или false в зависимости от успеха опереации +* Телефоны принимаются **только** в формате 5556667788 (без кода) +* Не добавляет **уже существующую** запись + +Метод __update__ для обновления записей: +* На вход принимает «Телефон», «Имя» и «Электронную почту» +* Обновляет «Имя» и «Электронную почту» по заданному «Телефону» +* Возвращает true или false в зависимости от успеха опереации +* «Электронную почту» можно стереть (не передав последний параметр), а «Имя» – нет + +Метод __find__ для поиска записей: +* На вход принимает запрос в виде строки +* Ищет вхождение этой строки хотя бы в одно из полей «Телефон», «Имя» и «Электронную почту» +* Возвращает отсортированный по «Имени» массив строк в формате `name, phone, email` +* «Имя» и «Электронную почту» выводит как есть, а «Телефон» в формате `+7 (555) 666-77-88` +* Пустой запрос не должен ничего находить +* Запрос «*» находит все записи + +Метод __findAndRemove__ для удаления записей: +* На вход принимает запрос в виде строки +* Находит (смотри __find__) и удаляет все найденные записи +* Возвращает число удаленных записей + +В файле _index.js_ вы можете найти примеры использования получившегося скриптика. + +## Дополнительное задание + +> Перед выполнением внимательно прочитайте [про особенности](https://github.com/urfu-2016/guides/blob/master/workflow/extra.md) + +По одной добавлять записи в книгу не очень удобно, поэтому будет здорово, если вы добавите в решение импорт данных из csv. Для этого реализуйте код метода __importFromCsv__. + +На вход метод принимает строку в формате csv. Если запись в телефонной книги уже есть – обновляет/дополняет её данными из csv строки. На выходе метод возвращает одно число добавленных/обновленных записей. + +Пример работы этого метода вы может отыскать в _index.js_ и в тестах. + +## Полезные ссылки + +- [Знакомимся с массивами](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array) +- [Пытаемся знакомиться с регулярными выражениями](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp) +- [Перебираем ключи объектов](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) +- [Метод indexOf для строк](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf) +- [Метод slice для строк](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/slice) + +Позвони мне, позвони diff --git a/index.js b/index.js new file mode 100644 index 0000000..9b259f5 --- /dev/null +++ b/index.js @@ -0,0 +1,44 @@ +'use strict'; + +var phoneBook = require('./phone-book'); + +// Эти записи добавятся, вернется true +phoneBook.add('5554440044', 'Григорий', 'grisha@example.com'); +phoneBook.add('5552220022', 'Борис', 'boris@example.com'); +phoneBook.add('5551110011', 'Алекс'); +phoneBook.add('5553330033', 'Валерий', 'valera@example.com'); + +// Эти запись не добавятся +phoneBook.add('3330033', 'Неизвестный', 'unknown@example.com'); +phoneBook.add('5551110011', 'Алексей'); +phoneBook.add('5555550055'); + +// Обновление +phoneBook.update('5551110011', 'Алексей', 'alex@example.com'); +phoneBook.update('5553330033', 'Валерий'); + +// В следующих примерах вернутся все записи +console.info(phoneBook.find('*')); +console.info(phoneBook.find('555')); +// Вывод будет следующий +// [ +// 'Алексей, +7 (555) 111-00-11, alex@example.com', +// 'Борис, +7 (555) 222-00-22, boris@example.com', +// 'Валерий, +7 (555) 333-00-33', +// 'Григорий, +7 (555) 444-00-44, grisha@example.com' +// ] + +// Удаление +phoneBook.findAndRemove('@'); // returns 3 + +if (phoneBook.isStar) { + // Импортируем из csv + var csv = [ + 'Борис;5552220022;boris@example.com', + 'Григорий;5554440044;grisha@example.com', + 'Алексей;5551110011;alex@example.com', + 'Валерий;5553330033;valera@example.com', + 'Неизвестный;3330033;unknown@example.com' + ].join('\n'); + phoneBook.importFromCsv(csv); // returns 4 +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1400772 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "private": true, + "main": "phone-book.js", + "scripts": { + "lint": "eslint .", + "test": "eslint . && mocha *.spec.js" + }, + "dependencies": { + "eslint": "3.7.0", + "eslint-config-hrundel": "latest", + "mocha": "3.1.0" + } +} diff --git a/phone-book.js b/phone-book.js new file mode 100644 index 0000000..92d38f3 --- /dev/null +++ b/phone-book.js @@ -0,0 +1,153 @@ +'use strict'; + +/** + * Сделано задание на звездочку + * Реализован метод importFromCsv + */ +exports.isStar = true; + +/** + * Телефонная книга + */ +var phoneBook = []; + +function isValidName(name) { + return name !== undefined && name.length > 0; +} + +function isValidPhone(name) { + return /^\d{10}$/g.test(phone); +} + +function formatPhone(phone) { + phone = /(\d{3})(\d{3})(\d{2})(\d{2})/.exec(phone); + + return '+7 (' + phone[1] + ') ' + phone[2] + '-' + phone[3] + '-' + phone[4]; +} + +function getRecordsByQuery(query) { + if (!query) { + return []; + } + if (query === '*') { + return phoneBook; + } + + return phoneBook.queryFilter(function (item) { + return item.phone.indexOf(query) !== -1 || + item.name.indexOf(query) !== -1 || + item.email.indexOf(query) !== -1; + }); +} + +function getRecordByPhone(phone) { + return phoneBook.phoneFilter(function (item) { + if (item.phone.indexOf(phone) !== -1) { + return item; + } + + return false; + }); +} + +/** + * Добавление записи в телефонную книгу + * @param {String} phone + * @param {String} name + * @param {String} email + * @returns {Bool} + */ +exports.add = function (phone, name, email) { + if ( !(isValidPhone(phone) && isValidName(name)) ) { + return false; + } + + if (getRecordByPhone(phone)) { + return false; + } + + phoneBook.push({ + 'phone': phone, + 'name': name, + 'email': email + }); + + return true; +}; + +/** + * Обновление записи в телефонной книге + * @param {String} phone + * @param {String} name + * @param {String} email + * @returns {Bool} + */ +exports.update = function (phone, name, email) { + if ( !(isValidPhone(phone) && isValidName(name)) ) { + return false; + } + + if (phoneBook.indexOf(getRecordByPhone(phone)) !== -1) { + phoneBook[index].name = name; + phoneBook[index].email = email; + + return true; + } + + return false; +}; + +/** + * Удаление записей по запросу из телефонной книги + * @param {String} query + * @returns {Int} + */ +exports.findAndRemove = function (query) { + return getRecordsByQuery(query).forEach(function (item) { + phoneBook.splice(phoneBook.indexOf(item), 1); + }).length; +}; + +/** + * Поиск записей по запросу в телефонной книге + * @param {String} query + * @returns {Array} + */ +exports.find = function (query) { + var records = getRecordsByQuery(query); + + if (records.length === 0) { + return []; + } + + return records.map(function (item) { + var emailPart = item.email !== undefined ? ', ' + item.email : ''; + + return item.name + ', ' + formatPhone(item.phone) + emailPart; + }).sort(); +}; + +/** + * Импорт записей из csv-формата + * @star + * @param {String} csv + * @returns {Number} – количество добавленных и обновленных записей + */ +exports.importFromCsv = function (csv) { + // Парсим csv + // Добавляем в телефонную книгу + // Либо обновляем, если запись с таким телефоном уже существует + var csvSplited = csv ? csv.split('\n') : ''; + var numberOfRecordsChanged = 0; + for (var i = 0; i < csvSplited.length; ++i) { + var record = csvSplited[i].split(';'); + + if (record.length <= 3 && + this.add(record[1], record[0], record[2]) || + this.update(record[1], record[0], record[2])) { + ++numberOfRecordsChanged; + } + } + + return numberOfRecordsChanged; +}; diff --git a/phone-book.spec.js b/phone-book.spec.js new file mode 100644 index 0000000..1b62a1a --- /dev/null +++ b/phone-book.spec.js @@ -0,0 +1,61 @@ +/* eslint-env mocha */ +'use strict'; + +var assert = require('assert'); + +var phoneBook = require('./phone-book'); + +describe('phone-book', function () { + it('должен добавлять записи', function () { + assert.ok(phoneBook.add('5554440044', 'Григорий', 'grisha@example.com')); + assert.ok(phoneBook.add('5552220022', 'Борис', 'boris@example.com')); + assert.ok(phoneBook.add('5551110011', 'Алекс')); + assert.ok(phoneBook.add('5553330033', 'Валерий', 'valera@example.com')); + }); + + it('не должен добавлять неправильные записи', function () { + assert.ok(!phoneBook.add('3330033', 'Неизвестный', 'unknown@example.com')); + assert.ok(!phoneBook.add('5551110011', 'Алексей')); + assert.ok(!phoneBook.add('5555550055')); + }); + + it('должен обновлять существующие записи', function () { + assert.ok(phoneBook.update('5551110011', 'Алексей', 'alex@example.com')); + assert.ok(phoneBook.update('5553330033', 'Валерий')); + }); + + it('должен искать все записи по запросу "*"', function () { + assert.deepStrictEqual(phoneBook.find('*'), [ + 'Алексей, +7 (555) 111-00-11, alex@example.com', + 'Борис, +7 (555) 222-00-22, boris@example.com', + 'Валерий, +7 (555) 333-00-33', + 'Григорий, +7 (555) 444-00-44, grisha@example.com' + ]); + }); + + it('должен искать все записи по запросу "555"', function () { + assert.deepStrictEqual(phoneBook.find('555'), [ + 'Алексей, +7 (555) 111-00-11, alex@example.com', + 'Борис, +7 (555) 222-00-22, boris@example.com', + 'Валерий, +7 (555) 333-00-33', + 'Григорий, +7 (555) 444-00-44, grisha@example.com' + ]); + }); + + it('должен удалять элементы из телефонной книги', function () { + assert.strictEqual(phoneBook.findAndRemove('@'), 3); + }); + + if (phoneBook.isStar) { + it('должен экспортировать из cvs', function () { + var csv = [ + 'Борис;5552220022;boris@example.com', + 'Григорий;5554440044;grisha@example.com', + 'Алексей;5551110011;alex@example.com', + 'Валерий;5553330033;valera@example.com', + 'Неизвестный;3330033;unknown@example.com' + ].join('\n'); + assert.strictEqual(phoneBook.importFromCsv(csv), 4); + }); + } +});