@tim-greller/xgettext-regex 0.5.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  Minimum viable xgettext .pot file generator. Uses a configurable regex to get translation keys.
4
4
 
5
+ ___________________
6
+ *Note: This repository was forked from [alanshaw/xgettext-regex](https://github.com/alanshaw/xgettext-regex), which is no longer maintained.*
7
+ *It provides the following additional features:*
8
+
9
+ **✨ Improved command line interface (CLI)**
10
+ - **Configurable regex via CLI:** made the regex (and regexTextCaptureIndex) configurable.
11
+
12
+ **✨ Better support for complex syntax & other languages**
13
+ - Added an **improved default regex** and documentation for it.
14
+ - **Multiline extraction support:** rewrote matching to work across newlines, added regex dotAll support.
15
+ - Added support for **Java text blocks** using """.
16
+ - Fix bug of missing location information if two strings found in same line.
17
+
18
+ **✨ Better .pot file diffs to use it in version management like GIT**
19
+ - Deterministic order of the keys by file path
20
+ - Switched to **relative paths** for location comments to prohibit commiting personal workspace directory paths.
21
+ ___________________
22
+
5
23
  ## Examples
6
24
 
7
25
  ```sh
@@ -16,7 +16,7 @@ var outFile = argv.o || argv.outfile
16
16
  var opts = {
17
17
  fn: (argv.f || argv.fn)
18
18
  }
19
- if (argv.r || argv.regex) opts.regex = new RegExp(argv.r || argv.regex, 'g')
19
+ if (argv.r || argv.regex) opts.regex = new RegExp(argv.r || argv.regex, 'gs')
20
20
  if (argv.i || argv.index) opts.regexTextCaptureIndex = argv.i || argv.index
21
21
 
22
22
  if (argv._.length) {
package/index.js CHANGED
@@ -2,9 +2,7 @@ var fs = require('fs')
2
2
  var path = require('path')
3
3
  var Readable = require('stream').Readable
4
4
  var through = require('through2')
5
- var readdirp = require('readdirp')
6
5
  var once = require('once')
7
- var xtend = require('xtend')
8
6
 
9
7
  function createDuplexStream (filename, opts) {
10
8
  filename = filename || ''
@@ -50,7 +48,7 @@ function createDuplexStream (filename, opts) {
50
48
  * \) closing paranthesis
51
49
  * ) end of positive lookahead
52
50
  */
53
- opts.regex = opts.regex || new RegExp("(?<=" + opts.fn + "\\(\\s*(?:(?:\"(?:[^\"]|\\\\.)*\"|'(?:[^']|\\\\.)*')\\s*,\\s*)*)(([\"'])(?:(?=(\\\\?))\\3.)*?\\2)(?=(?:\\s*,\\s*(?:\"(?:[^\"]|\\\\.)*\"|'(?:[^']|\\\\.)*'|[^\"')]+))*\\s*\\))", 'g')
51
+ opts.regex = opts.regex || new RegExp("(?<=" + opts.fn + "\\(\\s*(?:(?:\"(?:[^\"]|\\\\.)*\"|'(?:[^']|\\\\.)*')\\s*,\\s*)*)(([\"'])(?:(?=(\\\\?))\\3.)*?\\2)(?=(?:\\s*,\\s*(?:\"(?:[^\"]|\\\\.)*\"|'(?:[^']|\\\\.)*'|[^\"')]+))*\\s*\\))", 'gs')
54
52
  opts.regexTextCaptureIndex = opts.regexTextCaptureIndex || 1
55
53
 
56
54
  var chunks = []
@@ -83,6 +81,16 @@ function createDuplexStream (filename, opts) {
83
81
  text = '"' + text.replace(/"/g, '\\"') + '"'
84
82
  }
85
83
 
84
+ // handle triple-quoted string (java code blocks)
85
+ if (text.slice(0, 3) == '"""') {
86
+ text = text
87
+ .slice(3, -3)
88
+ .replace(/^\r?\n\s*/, '') // remove initial new line
89
+ .replace(/\\\r?\n\s*/g, '') // escaped linebreak + indent
90
+ .replace(/\r?\n\s*/g, '\\n') // normal linebreak + indent
91
+ text = `"${text}"`
92
+ }
93
+
86
94
  this.push([
87
95
  ``,
88
96
  `#: ${relativeFilename}:${lineNum}`,
@@ -123,11 +131,68 @@ function getText (filename, opts) {
123
131
  return fs.createReadStream(filename).pipe(createDuplexStream(filename, opts))
124
132
  }
125
133
 
126
- var READDIRP_OPTS = {
134
+ var DEFAULT_WALK_OPTS = {
127
135
  fileFilter: ['!.*', '!*.png', '!*.jpg', '!*.gif', '!*.zip', '!*.gz'],
128
136
  directoryFilter: ['!.*', '!node_modules', '!coverage']
129
137
  }
130
138
 
139
+ function createFilter (patterns) {
140
+ var compiled = patterns.map(function (pattern) {
141
+ var isExclude = pattern[0] === '!'
142
+ var glob = isExclude ? pattern.slice(1) : pattern
143
+ var re = new RegExp('^' + glob.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^/]*') + '$')
144
+ return { re: re, isExclude: isExclude }
145
+ })
146
+ return function (name) {
147
+ for (var i = 0; i < compiled.length; i++) {
148
+ if (compiled[i].re.test(name)) return !compiled[i].isExclude
149
+ }
150
+ return true // include by default when no pattern matches
151
+ }
152
+ }
153
+
154
+ function walkDirectorySorted (dir, fileFilter, dirFilter, onFile, cb) {
155
+ fs.readdir(dir, function (er, entries) {
156
+ if (er) return cb(er)
157
+ entries.sort(function (a, b) {
158
+ return a.localeCompare(b, undefined, {sensitivity: 'base'})
159
+ })
160
+
161
+ var index = 0
162
+
163
+ function nextEntry () {
164
+ if (index >= entries.length) return cb()
165
+
166
+ var name = entries[index++]
167
+ var fullPath = path.join(dir, name)
168
+
169
+ fs.stat(fullPath, function (er, stats) {
170
+ if (er) {
171
+ console.error('walkDirectorySorted stat error', fullPath, er)
172
+ return nextEntry()
173
+ }
174
+ if (stats.isFile()) {
175
+ if (fileFilter(name)) {
176
+ onFile(fullPath, nextEntry)
177
+ } else {
178
+ nextEntry()
179
+ }
180
+ } else if (stats.isDirectory()) {
181
+ if (dirFilter(name)) {
182
+ walkDirectorySorted(fullPath, fileFilter, dirFilter, onFile, nextEntry)
183
+ } else {
184
+ nextEntry()
185
+ }
186
+ } else {
187
+ nextEntry()
188
+ }
189
+ })
190
+ }
191
+
192
+ nextEntry()
193
+ })
194
+ }
195
+
131
196
  function createDuplexFileStream (opts) {
132
197
  opts = opts || {}
133
198
  opts.readdirp = opts.readdirp || {}
@@ -156,32 +221,19 @@ function createDuplexFileStream (opts) {
156
221
  cb()
157
222
  })
158
223
  } else if (stats.isDirectory()) {
159
- var total = 0
160
- var complete = 0
161
- var readdirpComplete = false
162
-
163
- readdirp(filename, xtend(READDIRP_OPTS, opts.readdirp))
164
- .on('data', function (entry) {
165
- total++
166
-
167
- getText(entry.fullPath, opts)
168
- .on('data', function (entry) {self.push(entry)})
169
- .on('error', function (er) {
170
- console.error('Directory getText error', entry.fullPath, er)
171
- cb(er)
172
- })
173
- .on('end', function () {
174
- complete++
175
- if (total == complete && readdirpComplete) cb()
176
- })
177
- })
178
- .on('error', function (er) {
179
- console.error('Directory error', filename, er)
180
- cb(er)
181
- })
182
- .on('end', function () {
183
- readdirpComplete = true
184
- })
224
+ var walkOpts = Object.assign({}, DEFAULT_WALK_OPTS, opts.readdirp)
225
+ var fileFilter = createFilter(walkOpts.fileFilter)
226
+ var dirFilter = createFilter(walkOpts.directoryFilter)
227
+
228
+ walkDirectorySorted(filename, fileFilter, dirFilter, function (filePath, done) {
229
+ getText(filePath, opts)
230
+ .on('data', function (entry) { self.push(entry) })
231
+ .on('error', function (er) {
232
+ console.error('Directory getText error', filePath, er)
233
+ cb(er)
234
+ })
235
+ .on('end', done)
236
+ }, cb)
185
237
  }
186
238
  })
187
239
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tim-greller/xgettext-regex",
3
- "version": "0.5.2",
3
+ "version": "0.8.0",
4
4
  "description": "Minimum viable xgettext .po file generator. Uses a configurable regex to get translation keys.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -11,11 +11,9 @@
11
11
  "dependencies": {
12
12
  "minimist": "^1.1.0",
13
13
  "once": "^1.3.1",
14
- "readdirp": "^3.1.3",
15
14
  "split": "^1.0.1",
16
15
  "stream-combiner": "^0.2.1",
17
- "through2": "^3.0.1",
18
- "xtend": "^4.0.0"
16
+ "through2": "^3.0.1"
19
17
  },
20
18
  "bin": {
21
19
  "xgettext-regex": "bin/xgettext-regex.js"
package/test/test.js CHANGED
@@ -164,4 +164,20 @@ test('Can create .pot from multiple files/directories', function (t) {
164
164
  t.end()
165
165
  }))
166
166
  })
167
- })
167
+ })
168
+
169
+ test('Emits directory entries in deterministic path order', function (t) {
170
+ t.plan(3)
171
+
172
+ xgettext.createReadStream(__dirname + '/fixtures')
173
+ .pipe(concat({encoding: 'string'}, function (pot) {
174
+ var indexJade = pot.indexOf('msgid "index.js"')
175
+ var indexPhp = pot.indexOf('msgid "php hypertext preprocessor"')
176
+ var jsApp = pot.indexOf('msgid "app.js"')
177
+
178
+ t.ok(indexJade > -1, 'index.jade translation exists')
179
+ t.ok(indexPhp > indexJade, 'index.php content appears after index.jade content')
180
+ t.ok(jsApp > indexPhp, 'js/app.js content appears after index.php content')
181
+ t.end()
182
+ }))
183
+ })