Repository navigation
Expand file tree
/
Copy pathgemfile.js
More file actions
182 lines (143 loc) · 4.95 KB
/
Copy pathgemfile.js
File metadata and controls
182 lines (143 loc) · 4.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
'use strict';
const fs = require('fs');
const path = require('path');
const assert = require('assert');
const GEMFILE_DEFAULT_LOCATION = path.resolve(process.cwd(), 'Gemfile.lock');
const WHITESPACE = /^(\s*)/;
const GEMFILE_KEY_VALUE = /^\s*([^:(]*)\s*\:*\s*(.*)/;
const ORIGINS = ['GEM', 'GIT', 'PATH'];
const RUBY = /^ruby\s(.*)/;
module.exports = {
interpret,
parse,
parseSync
};
function interpret(string, extractMeta) {
assert(
typeof string === 'string',
'gemfile.interpret expects a UTF-8 Gemfile.lock string source.'
);
const gemfileMeta = {};
let line;
let index = 0;
let previousWhitespace = -1;
let keyCount = {};
let gemfile = {};
let lines = string.split('\n');
let stack = [];
while((line = lines[index++]) !== undefined) {
// Handle depth stack changes
let whitespace = WHITESPACE.exec(line)[1].length;
if (whitespace <= previousWhitespace) {
let stackIndex = stack.length - 1;
while(stack[stackIndex] && (whitespace <= stack[stackIndex].depth)) {
stack.pop();
stackIndex--;
}
}
// Make note of line's whitespace depth
previousWhitespace = whitespace;
// Handle new key/value leaf
let parts = GEMFILE_KEY_VALUE.exec(line);
let key = parts[1].trim();
let value = parts[2] || '';
if (key) {
// Handle path traversal
let level = gemfile;
for (let stackIndex = 0; stackIndex < stack.length; stackIndex++) {
if (level[stack[stackIndex].key]) {
level = level[stack[stackIndex].key];
}
}
// Handle data type inference
let data = {};
if (value.indexOf('/') > -1) {
data.path = value;
} else if (value.indexOf('(') > -1) {
if (value[value.length - 1] === '!') {
value = value.substring(0, value.length - 1);
data.outsourced = true;
}
if (value[1] !== ')') {
data.version = value.substring(1, value.length - 1);
}
} else if (/\b[0-9a-f]{7,40}\b/.test(value)) {
data.sha = value;
}
// Set key at current level
// Do not throw away additional top-level key entries
// e.g. multiple GIT/GEM blocks
if (level[key]) {
if (keyCount[key] === undefined) {
keyCount[key] = 0;
} else {
keyCount[key]++;
}
level[key + keyCount[key]] = level[key];
}
level[key] = data;
// Push key on stack
stack.push({key, depth: whitespace});
}
}
let keys = Object.keys(gemfile);
let hasGemKey = keys.indexOf('GEM') > -1;
let hasDependenciesKey = keys.indexOf('DEPENDENCIES') > -1;
let hasPlatformsKey = keys.indexOf('PLATFORMS') > -1;
if (!hasGemKey || !hasDependenciesKey || !hasPlatformsKey) {
console.warn([
'Are you sure this a Gemfile.lock?',
'If it is, please file an issue on Github: https://github.com/treycordova/gemfile/issues.',
'Regardless, gemfile parsed whatever you gave it.'
].join('\n'));
}
if (gemfile['BUNDLED WITH']) {
gemfile['BUNDLED WITH'] = Object.keys(gemfile['BUNDLED WITH'])[0];
}
if (gemfile['RUBY VERSION']) {
const rawVersion= Object.keys(gemfile['RUBY VERSION'])[0];
const version = RUBY.exec(rawVersion)[1];
gemfile['RUBY VERSION'] = version;
}
if (extractMeta) {
gemfileMeta.bundledWith = gemfile['BUNDLED WITH'];
gemfileMeta.rubyVersion = gemfile['RUBY VERSION'];
gemfileMeta.platforms = gemfile['PLATFORMS'];
gemfileMeta.dependencies = gemfile['DEPENDENCIES'];
gemfileMeta.specs = Object.keys(gemfile)
.filter(key =>
ORIGINS.some(origin => key.startsWith(origin)))
.reduce((specs, key) => {
const type = key.match(/[A-Z]+/)[0];
const meta = Object.assign({ type }, gemfile[key]);
delete meta.specs;
Object.assign(specs, Object.keys(gemfile[key].specs).reduce((specs, gem) => {
specs[gem] = Object.assign({}, gemfile[key].specs[gem], meta);
return specs;
}, {}));
return specs;
}, {});
return gemfileMeta;
}
return gemfile;
}
function parse(path, extractMeta) {
path = typeof path === 'string' ?
path :
GEMFILE_DEFAULT_LOCATION;
return new Promise(function(resolve, reject) {
let file = fs.readFile(path, {encoding: 'utf8'}, function(error, gemfile) {
if (error) {
return reject(`Couldn't find a Gemfile at the specified location: ${path}.`);
} else {
return resolve(interpret(gemfile, extractMeta));
}
});
});
}
function parseSync(path, extractMeta) {
path = typeof path === 'string' ?
path :
GEMFILE_DEFAULT_LOCATION;
return interpret(fs.readFileSync(path, 'utf8'), extractMeta);
}