@tim-greller/xgettext-regex 0.6.0 → 0.8.1

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,30 @@
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
+
23
+ ## Install
24
+
25
+ ```sh
26
+ npm i @tim-greller/xgettext-regex
27
+ ```
28
+
5
29
  ## Examples
6
30
 
7
31
  ```sh
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 || ''
@@ -83,13 +81,14 @@ function createDuplexStream (filename, opts) {
83
81
  text = '"' + text.replace(/"/g, '\\"') + '"'
84
82
  }
85
83
 
86
- // handle triple-quoted string (java code blocks)
84
+ // handle triple-quoted string (java text blocks)
87
85
  if (text.slice(0, 3) == '"""') {
88
86
  text = text
89
87
  .slice(3, -3)
90
- .replace(/^\r?\n\s*/, '') // remove initial new line
91
- .replace(/\\\r?\n\s*/g, '') // escaped linebreak + indent
92
- .replace(/\r?\n\s*/g, '\\n') // normal linebreak + indent
88
+ // The following regexes use the double-negation [^\S\r\n] which corresponds to \s but excludes \r and \n.
89
+ .replace(/^\r?\n[^\S\r\n]*/, '') // remove initial new line
90
+ .replace(/\\\r?\n[^\S\r\n]*/g, '') // escaped linebreak + indent
91
+ .replace(/\r?\n[^\S\r\n]*/g, '\\n') // normal linebreak + indent
93
92
  text = `"${text}"`
94
93
  }
95
94
 
@@ -133,11 +132,68 @@ function getText (filename, opts) {
133
132
  return fs.createReadStream(filename).pipe(createDuplexStream(filename, opts))
134
133
  }
135
134
 
136
- var READDIRP_OPTS = {
135
+ var DEFAULT_WALK_OPTS = {
137
136
  fileFilter: ['!.*', '!*.png', '!*.jpg', '!*.gif', '!*.zip', '!*.gz'],
138
137
  directoryFilter: ['!.*', '!node_modules', '!coverage']
139
138
  }
140
139
 
140
+ function createFilter (patterns) {
141
+ var compiled = patterns.map(function (pattern) {
142
+ var isExclude = pattern[0] === '!'
143
+ var glob = isExclude ? pattern.slice(1) : pattern
144
+ var re = new RegExp('^' + glob.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^/]*') + '$')
145
+ return { re: re, isExclude: isExclude }
146
+ })
147
+ return function (name) {
148
+ for (var i = 0; i < compiled.length; i++) {
149
+ if (compiled[i].re.test(name)) return !compiled[i].isExclude
150
+ }
151
+ return true // include by default when no pattern matches
152
+ }
153
+ }
154
+
155
+ function walkDirectorySorted (dir, fileFilter, dirFilter, onFile, cb) {
156
+ fs.readdir(dir, function (er, entries) {
157
+ if (er) return cb(er)
158
+ entries.sort(function (a, b) {
159
+ return a.localeCompare(b, undefined, {sensitivity: 'base'})
160
+ })
161
+
162
+ var index = 0
163
+
164
+ function nextEntry () {
165
+ if (index >= entries.length) return cb()
166
+
167
+ var name = entries[index++]
168
+ var fullPath = path.join(dir, name)
169
+
170
+ fs.stat(fullPath, function (er, stats) {
171
+ if (er) {
172
+ console.error('walkDirectorySorted stat error', fullPath, er)
173
+ return nextEntry()
174
+ }
175
+ if (stats.isFile()) {
176
+ if (fileFilter(name)) {
177
+ onFile(fullPath, nextEntry)
178
+ } else {
179
+ nextEntry()
180
+ }
181
+ } else if (stats.isDirectory()) {
182
+ if (dirFilter(name)) {
183
+ walkDirectorySorted(fullPath, fileFilter, dirFilter, onFile, nextEntry)
184
+ } else {
185
+ nextEntry()
186
+ }
187
+ } else {
188
+ nextEntry()
189
+ }
190
+ })
191
+ }
192
+
193
+ nextEntry()
194
+ })
195
+ }
196
+
141
197
  function createDuplexFileStream (opts) {
142
198
  opts = opts || {}
143
199
  opts.readdirp = opts.readdirp || {}
@@ -166,32 +222,19 @@ function createDuplexFileStream (opts) {
166
222
  cb()
167
223
  })
168
224
  } else if (stats.isDirectory()) {
169
- var total = 0
170
- var complete = 0
171
- var readdirpComplete = false
172
-
173
- readdirp(filename, xtend(READDIRP_OPTS, opts.readdirp))
174
- .on('data', function (entry) {
175
- total++
176
-
177
- getText(entry.fullPath, opts)
178
- .on('data', function (entry) {self.push(entry)})
179
- .on('error', function (er) {
180
- console.error('Directory getText error', entry.fullPath, er)
181
- cb(er)
182
- })
183
- .on('end', function () {
184
- complete++
185
- if (total == complete && readdirpComplete) cb()
186
- })
187
- })
188
- .on('error', function (er) {
189
- console.error('Directory error', filename, er)
190
- cb(er)
191
- })
192
- .on('end', function () {
193
- readdirpComplete = true
194
- })
225
+ var walkOpts = Object.assign({}, DEFAULT_WALK_OPTS, opts.readdirp)
226
+ var fileFilter = createFilter(walkOpts.fileFilter)
227
+ var dirFilter = createFilter(walkOpts.directoryFilter)
228
+
229
+ walkDirectorySorted(filename, fileFilter, dirFilter, function (filePath, done) {
230
+ getText(filePath, opts)
231
+ .on('data', function (entry) { self.push(entry) })
232
+ .on('error', function (er) {
233
+ console.error('Directory getText error', filePath, er)
234
+ cb(er)
235
+ })
236
+ .on('end', done)
237
+ }, cb)
195
238
  }
196
239
  })
197
240
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tim-greller/xgettext-regex",
3
- "version": "0.6.0",
3
+ "version": "0.8.1",
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
+ })