aboutsummaryrefslogtreecommitdiff
path: root/node_modules/csso/lib/utils/list.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/csso/lib/utils/list.js')
-rw-r--r--node_modules/csso/lib/utils/list.js389
1 files changed, 389 insertions, 0 deletions
diff --git a/node_modules/csso/lib/utils/list.js b/node_modules/csso/lib/utils/list.js
new file mode 100644
index 00000000..30d14871
--- /dev/null
+++ b/node_modules/csso/lib/utils/list.js
@@ -0,0 +1,389 @@
+//
+// item item item item
+// /------\ /------\ /------\ /------\
+// | data | | data | | data | | data |
+// null <--+-prev |<---+-prev |<---+-prev |<---+-prev |
+// | next-+--->| next-+--->| next-+--->| next-+--> null
+// \------/ \------/ \------/ \------/
+// ^ ^
+// | list |
+// | /------\ |
+// \--------------+-head | |
+// | tail-+--------------/
+// \------/
+//
+
+function createItem(data) {
+ return {
+ data: data,
+ next: null,
+ prev: null
+ };
+}
+
+var List = function(values) {
+ this.cursor = null;
+ this.head = null;
+ this.tail = null;
+
+ if (Array.isArray(values)) {
+ var cursor = null;
+
+ for (var i = 0; i < values.length; i++) {
+ var item = createItem(values[i]);
+
+ if (cursor !== null) {
+ cursor.next = item;
+ } else {
+ this.head = item;
+ }
+
+ item.prev = cursor;
+ cursor = item;
+ }
+
+ this.tail = cursor;
+ }
+};
+
+Object.defineProperty(List.prototype, 'size', {
+ get: function() {
+ var size = 0;
+ var cursor = this.head;
+
+ while (cursor) {
+ size++;
+ cursor = cursor.next;
+ }
+
+ return size;
+ }
+});
+
+List.createItem = createItem;
+List.prototype.createItem = createItem;
+
+List.prototype.toArray = function() {
+ var cursor = this.head;
+ var result = [];
+
+ while (cursor) {
+ result.push(cursor.data);
+ cursor = cursor.next;
+ }
+
+ return result;
+};
+List.prototype.toJSON = function() {
+ return this.toArray();
+};
+
+List.prototype.isEmpty = function() {
+ return this.head === null;
+};
+
+List.prototype.first = function() {
+ return this.head && this.head.data;
+};
+
+List.prototype.last = function() {
+ return this.tail && this.tail.data;
+};
+
+List.prototype.each = function(fn, context) {
+ var item;
+ var cursor = {
+ prev: null,
+ next: this.head,
+ cursor: this.cursor
+ };
+
+ if (context === undefined) {
+ context = this;
+ }
+
+ // push cursor
+ this.cursor = cursor;
+
+ while (cursor.next !== null) {
+ item = cursor.next;
+ cursor.next = item.next;
+
+ fn.call(context, item.data, item, this);
+ }
+
+ // pop cursor
+ this.cursor = this.cursor.cursor;
+};
+
+List.prototype.eachRight = function(fn, context) {
+ var item;
+ var cursor = {
+ prev: this.tail,
+ next: null,
+ cursor: this.cursor
+ };
+
+ if (context === undefined) {
+ context = this;
+ }
+
+ // push cursor
+ this.cursor = cursor;
+
+ while (cursor.prev !== null) {
+ item = cursor.prev;
+ cursor.prev = item.prev;
+
+ fn.call(context, item.data, item, this);
+ }
+
+ // pop cursor
+ this.cursor = this.cursor.cursor;
+};
+
+List.prototype.nextUntil = function(start, fn, context) {
+ if (start === null) {
+ return;
+ }
+
+ var item;
+ var cursor = {
+ prev: null,
+ next: start,
+ cursor: this.cursor
+ };
+
+ if (context === undefined) {
+ context = this;
+ }
+
+ // push cursor
+ this.cursor = cursor;
+
+ while (cursor.next !== null) {
+ item = cursor.next;
+ cursor.next = item.next;
+
+ if (fn.call(context, item.data, item, this)) {
+ break;
+ }
+ }
+
+ // pop cursor
+ this.cursor = this.cursor.cursor;
+};
+
+List.prototype.prevUntil = function(start, fn, context) {
+ if (start === null) {
+ return;
+ }
+
+ var item;
+ var cursor = {
+ prev: start,
+ next: null,
+ cursor: this.cursor
+ };
+
+ if (context === undefined) {
+ context = this;
+ }
+
+ // push cursor
+ this.cursor = cursor;
+
+ while (cursor.prev !== null) {
+ item = cursor.prev;
+ cursor.prev = item.prev;
+
+ if (fn.call(context, item.data, item, this)) {
+ break;
+ }
+ }
+
+ // pop cursor
+ this.cursor = this.cursor.cursor;
+};
+
+List.prototype.some = function(fn, context) {
+ var cursor = this.head;
+
+ if (context === undefined) {
+ context = this;
+ }
+
+ while (cursor !== null) {
+ if (fn.call(context, cursor.data, cursor, this)) {
+ return true;
+ }
+
+ cursor = cursor.next;
+ }
+
+ return false;
+};
+
+List.prototype.map = function(fn, context) {
+ var result = [];
+ var cursor = this.head;
+
+ if (context === undefined) {
+ context = this;
+ }
+
+ while (cursor !== null) {
+ result.push(fn.call(context, cursor.data, cursor, this));
+ cursor = cursor.next;
+ }
+
+ return result;
+};
+
+List.prototype.copy = function() {
+ var result = new List();
+ var cursor = this.head;
+
+ while (cursor !== null) {
+ result.insert(createItem(cursor.data));
+ cursor = cursor.next;
+ }
+
+ return result;
+};
+
+List.prototype.updateCursors = function(prevOld, prevNew, nextOld, nextNew) {
+ var cursor = this.cursor;
+
+ while (cursor !== null) {
+ if (prevNew === true || cursor.prev === prevOld) {
+ cursor.prev = prevNew;
+ }
+
+ if (nextNew === true || cursor.next === nextOld) {
+ cursor.next = nextNew;
+ }
+
+ cursor = cursor.cursor;
+ }
+};
+
+List.prototype.insert = function(item, before) {
+ if (before !== undefined && before !== null) {
+ // prev before
+ // ^
+ // item
+ this.updateCursors(before.prev, item, before, item);
+
+ if (before.prev === null) {
+ // insert to the beginning of list
+ if (this.head !== before) {
+ throw new Error('before doesn\'t below to list');
+ }
+
+ // since head points to before therefore list doesn't empty
+ // no need to check tail
+ this.head = item;
+ before.prev = item;
+ item.next = before;
+
+ this.updateCursors(null, item);
+ } else {
+
+ // insert between two items
+ before.prev.next = item;
+ item.prev = before.prev;
+
+ before.prev = item;
+ item.next = before;
+ }
+ } else {
+ // tail
+ // ^
+ // item
+ this.updateCursors(this.tail, item, null, item);
+
+ // insert to end of the list
+ if (this.tail !== null) {
+ // if list has a tail, then it also has a head, but head doesn't change
+
+ // last item -> new item
+ this.tail.next = item;
+
+ // last item <- new item
+ item.prev = this.tail;
+ } else {
+ // if list has no a tail, then it also has no a head
+ // in this case points head to new item
+ this.head = item;
+ }
+
+ // tail always start point to new item
+ this.tail = item;
+ }
+};
+
+List.prototype.remove = function(item) {
+ // item
+ // ^
+ // prev next
+ this.updateCursors(item, item.prev, item, item.next);
+
+ if (item.prev !== null) {
+ item.prev.next = item.next;
+ } else {
+ if (this.head !== item) {
+ throw new Error('item doesn\'t below to list');
+ }
+
+ this.head = item.next;
+ }
+
+ if (item.next !== null) {
+ item.next.prev = item.prev;
+ } else {
+ if (this.tail !== item) {
+ throw new Error('item doesn\'t below to list');
+ }
+
+ this.tail = item.prev;
+ }
+
+ item.prev = null;
+ item.next = null;
+
+ return item;
+};
+
+List.prototype.appendList = function(list) {
+ // ignore empty lists
+ if (list.head === null) {
+ return;
+ }
+
+ this.updateCursors(this.tail, list.tail, null, list.head);
+
+ // insert to end of the list
+ if (this.tail !== null) {
+ // if destination list has a tail, then it also has a head,
+ // but head doesn't change
+
+ // dest tail -> source head
+ this.tail.next = list.head;
+
+ // dest tail <- source head
+ list.head.prev = this.tail;
+ } else {
+ // if list has no a tail, then it also has no a head
+ // in this case points head to new item
+ this.head = list.head;
+ }
+
+ // tail always start point to new item
+ this.tail = list.tail;
+
+ list.head = null;
+ list.tail = null;
+};
+
+module.exports = List;