txgh 5.5.0 → 6.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +334 -0
- data/lib/ext/zipline/output_stream.rb +62 -0
- data/lib/txgh/github_api.rb +8 -3
- data/lib/txgh/resource_contents.rb +2 -11
- data/lib/txgh/version.rb +1 -1
- data/spec/diff_calculator_spec.rb +0 -56
- data/spec/github_api_spec.rb +9 -20
- data/spec/merge_calculator_spec.rb +0 -46
- data/spec/resource_contents_spec.rb +11 -30
- data/txgh.gemspec +1 -1
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a63c65881f6f00e2479a19d8ff925581cc8b2c10
|
4
|
+
data.tar.gz: e309c3690f95a66a36f5d9b3e743e160dc8a78f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c41e8fc0e628f7c00078046a2cc467fbd62777ba7ff848eafcce1503bdc37c05dc5ac7a5aaa342c01636ca7feae4ee04e9848bb10203862db9f5c4b440819df6
|
7
|
+
data.tar.gz: 254e2b871687f9023df759b2df134976a93270eb179c9a1471c11bfc4d6f34933ff9771678868f668a2ef9914bc5144330544d718ab17798e8fde8bcc15c2ba0
|
data/README.md
ADDED
@@ -0,0 +1,334 @@
|
|
1
|
+
Transifex Txgh (Lumos Labs fork)
|
2
|
+
====
|
3
|
+
|
4
|
+
[![Build Status](https://travis-ci.org/lumoslabs/txgh.svg?branch=master)](https://travis-ci.org/lumoslabs/txgh)
|
5
|
+
|
6
|
+
Txgh, a mashup of "Transifex" and "Github", is a lightweight server that connects Transifex and Github via webhooks. It enables automatic translation of new content pushed to your Github repository, and supports single-resource as well as branch-based git workflows.
|
7
|
+
|
8
|
+
|
9
|
+
How Does it Work?
|
10
|
+
---
|
11
|
+
|
12
|
+
1. When a source file is pushed to Github, the Txgh service will update the corresponding Transifex resource with the contents of the new file. Configuration options exist to process only certain branches and tags.
|
13
|
+
|
14
|
+
2. When a resource in Transifex reaches 100% translated, the Txgh service will download the translations and commit them to the target repository. Configuration options exist to protect certain branches or tags from automatic commits.
|
15
|
+
|
16
|
+
<br>
|
17
|
+
For the more visually inclined:
|
18
|
+
![Txgh Use Cases](https://www.gliffy.com/go/publish/image/9483799/L.png)
|
19
|
+
<br>
|
20
|
+
<br>
|
21
|
+
|
22
|
+
Supported Workflows
|
23
|
+
---
|
24
|
+
|
25
|
+
Use the following table to determine if Txgh will work for your git and translation workflow:
|
26
|
+
|
27
|
+
|Workflow|Comments|
|
28
|
+
|:--------|:----------|
|
29
|
+
|**Basic**<br>* You maintain one master version of your translations<br>* Translations may not be under source control<br>* New content is translated before each release and does not change until the next release|This is the default. Txgh <br> can also be configured to only<br> listen for changes that happen<br> on a certain branch or tag.|
|
30
|
+
|**Multi-branch**<br>* Your team is small or everyone works from the same branch<br>* Translations should change when code changes|You might want to consider <br>multi-branch with diffs (below)<br> since your translators may see<br> a number of duplicate strings<br> in Transifex using this workflow.|
|
31
|
+
|**Multi-branch with Diffs**<br>* Your team uses git branches for feature development<br>* Translations should change when code changes|This is the recommended workflow<br> if you'd like to manage translations<br> in an agile way, i.e. "continuous<br> translation." Only new and changed<br> phrases are uploaded to Transifex.|
|
32
|
+
|
33
|
+
Getting Started
|
34
|
+
---
|
35
|
+
|
36
|
+
Txgh supports a significant number of configuration options, and you'll need to familiarize yourself with them to ensure you're setting Txgh up to serve your particular needs. That said, we've whipped up a couple of templates with sensible defaults to get you started more quickly. If you're thinking about deploying with Docker, take a look at our [Docker template](https://github.com/lumoslabs/txgh-docker-template). If you want more flexibility (i.e. the ability to add custom middleware, etc), take a look at our [ruby template](https://github.com/lumoslabs/txgh-ruby-template). The rest of the setup steps below assume you're using one of these templates.
|
37
|
+
|
38
|
+
1. Open a terminal window and run `./bin/configure`. Follow the instructions to add a new project to `config.yml`. You'll need to have some version of Ruby installed on your system to run the configuration script. Refer to the section marked "Configuring Txgh" below for a detailed explanation of each option.
|
39
|
+
|
40
|
+
2. Create a file in your repository named `.tx/config` and add appropriate tx config. Txgh uses this information to know which files should be watched for changes. Refer to the section marked "Tx Config" below for more information.
|
41
|
+
|
42
|
+
3. If you've configured Txgh to upload diffs, visit your project's page in Transifex and upload each of the files described in your tx config. Use Transifex's categories feature to add a category of `branch:heads/master` to each resource. These full resources will provide the base set of translations for your project.
|
43
|
+
|
44
|
+
3. At this point, you're ready to deploy Txgh. There are a number of ways to do this, including using a host like AWS or Heroku. It's important your Txgh instance is publicly accessible over the Internet, because the next few steps involve setting up webhooks, which rely on being able to reach it.
|
45
|
+
|
46
|
+
4. Visit the settings page for your Github repository, click on "Webhooks and Services," then click the "Add webhook" button. Under payload URL, fill in the URL of your publicly accessible Txgh instance and the path to the Github hook. For example, `http://mytxgh.herokuapp.com/hooks/github`. Fill in the "Secret" field with the Github `webhook_secret` generated for you in `config.yml`. Make sure to enable the "push" event, and also the "delete" event if you have configured Txgh to automatically delete resources. When the webhook is first created, Github will send your Txgh instance a "ping" test event. Your Txgh instance should respond with a 200 OK.
|
47
|
+
|
48
|
+
5. Visit the Manage -> Edit Project page for your Transifex project. Scroll down to the "Features" header and look for the "Web Hook URL" field. Fill it in with the URL of your publicly accessible Txgh instance and the path to the Transifex hook. For example, `http://mytxgh.herokuapp.com/hooks/transifex`. Fill in the "Secret Key" field with the Transifex `webhook_secret` generated for you in `config.yml`.
|
49
|
+
|
50
|
+
6. Congratulations, you now have a running, fully configured Txgh instance! Note that the configuration script you ran in step 1 automatically configured Txgh to process all branches, so you should be able to create a test branch, modify some translations, and push your changes. Txgh is configured correctly if a new resource appears in Transifex with the new branch name attached.
|
51
|
+
|
52
|
+
Available Endpoints
|
53
|
+
---
|
54
|
+
|
55
|
+
Txgh exposes the following endpoints:
|
56
|
+
|
57
|
+
* **`POST /hooks/github`**: Receives and processes Github webhook requests. Request body is expected to be a Github webhook payload in JSON format. Uploads any modified translatable content to Transifex. This endpoint is protected by shared secret signature authorization.
|
58
|
+
|
59
|
+
* **`POST /hooks/transifex`**: Receives and processes Transifex webhook requests. Request body is expected to be a Transifex webhook payload in JSON format. Commits translations back to the Github repository. This endpoint is protected by shared secret signature authorization.
|
60
|
+
|
61
|
+
* **`PATCH /push?project_slug=[slug]&branch=[branch]`**: Causes translatable content from Github to be pushed to Transifex. This endpoint is designed to emulate receiving a Github webhook, but doesn't require the usual massive Github webhook payload. Currently this endpoint is not protected (but it should be).
|
62
|
+
|
63
|
+
* **`PATCH /pull?project_slug=[slug]&branch=[branch]`**: Causes translations from Transifex to be committed to Github. This endpoint is designed to emulate receiving a Transifex webhook, but doesn't require the usual Transifex webhook payload. Currently this endpoint is not protected (but it should be).
|
64
|
+
|
65
|
+
* **`GET /config?project_slug=[slug]&branch=[branch]`**: Returns the tx config in JSON format for the given project and branch.
|
66
|
+
|
67
|
+
* **`GET /health_check`**: Simply returns an HTTP 200 OK.
|
68
|
+
|
69
|
+
Configuring Txgh
|
70
|
+
---
|
71
|
+
|
72
|
+
Config is written in the YAML markup language and is comprised of two sections, one for Github options and one for Transifex options:
|
73
|
+
|
74
|
+
```yaml
|
75
|
+
github:
|
76
|
+
repos:
|
77
|
+
organization/repo:
|
78
|
+
api_username: github username
|
79
|
+
api_token: abcdefghijklmnopqrstuvwxyz github api token
|
80
|
+
push_source_to: transifex project slug
|
81
|
+
branch: branch to watch for changes, or "all" to watch all of them
|
82
|
+
tag: tag to watch for changes, or "all" to watch all of them
|
83
|
+
webhook_secret: 123abcdef456ghi github webhook secret
|
84
|
+
diff_point: branch to diff against (usually master)
|
85
|
+
transifex:
|
86
|
+
projects:
|
87
|
+
project-slug:
|
88
|
+
tx_config: map of transifex resources to file paths
|
89
|
+
api_username: transifex username
|
90
|
+
api_password: transifex password (transifex doesn't support token-based auth)
|
91
|
+
push_translations_to: organization/repo
|
92
|
+
protected_branches: branches that should not receive automatic commits
|
93
|
+
webhook_secret: 123abcdef456ghi transifex webhook secret
|
94
|
+
auto_delete_resources: 'true' to delete resource when branch is deleted
|
95
|
+
```
|
96
|
+
|
97
|
+
### Github Configuration
|
98
|
+
|
99
|
+
* **`api_username`**: Your Github account username. You might want to create a new dev Github account for Txgh to use instead of providing someone's actual credentials here. Keep in mind that the username you specify must have access to the repository in question, or Github will reject all Txgh's API requests.
|
100
|
+
|
101
|
+
* **`api_token`**: A valid Github access token. The token should be generated by the username specified by `api_username`. In Github, visit your account settings and generate a personal access token. Give the token all the "repo" permissions.
|
102
|
+
|
103
|
+
* **`push_source_to`**: The slug of the Transifex project you want to push new translatable content to. The slug is basically the name of the project, but with URL-unfriendly characters removed. You can find the slug of your project by inspecting the URL on any Transifex project page.
|
104
|
+
|
105
|
+
* **`branch`**: The Github branch to watch for new translatable content. If you want Txgh to watch all branches, use the special value "all". By default, branch is `master`.
|
106
|
+
|
107
|
+
* **`tag`**: The Github tag to watch for new translatable content. If you want Txgh to watch all tags, use the special value "all".
|
108
|
+
|
109
|
+
* **`webhook_secret`**: A user-defined string that Github will use to sign webhook requests. If present, Txgh will use this value to verify the authenticity of these webhook requests and reject those that are improperly signed. Make sure you use this same value when configuring the webhook in the Github UI.
|
110
|
+
|
111
|
+
* **`diff_point`**: The branch to compare against when submitting new translatable content to Transifex, usually `master`. Set this option to enable diffing. If not set, Txgh will upload changed files in their entirety.
|
112
|
+
|
113
|
+
### Transifex Configuration
|
114
|
+
|
115
|
+
* **`tx_config`**: Configuration specifying which files to watch for changes. See the section labeled "Tx Config" below for more information.
|
116
|
+
|
117
|
+
* **`api_username`**: Your Transifex account username. You might want to create a new dev Transifex account for Txgh to use instead of providing someone's actual credentials here. Keep in mind that the username you specify must have access to the project in question, or Transifex will reject all Txgh's API requests.
|
118
|
+
|
119
|
+
* **`api_password`**: The Transifex account password associated with `api_username`. Transifex does not support token-based authentication or OAuth.
|
120
|
+
|
121
|
+
* **`push_translations_to`**: The Github repository to commit completed translations to. If configured to process a branch or branches, Txgh will commit translations back to the branch they came from.
|
122
|
+
|
123
|
+
* **`protected_branches`**: A comma-separated list of branches Txgh should never make automatic commits on. It can be useful to blacklist certain branches so as not to disrupt a release cycle or surprise your QA team.
|
124
|
+
|
125
|
+
* **`webhook_secret`**: A user-defined string that Transifex will use to sign webhook requests. If present, Txgh will use this value to verify the authenticity of these webhook requests and reject those that are improperly signed. Make sure to use this same value when configuring the webhook in the Transifex UI.
|
126
|
+
|
127
|
+
* **`auto_delete_resources`**: If set to "true", Txgh will automatically delete Transifex resources when the corresponding branch is deleted from git.
|
128
|
+
|
129
|
+
### Loading Config
|
130
|
+
|
131
|
+
Txgh supports two different ways of accessing configuration, raw text and a file path. In both cases, config is passed via the `TXGH_CONFIG` environment variable. Prefix the raw text or file path with the appropriate scheme, `raw://` or `file://`, to indicate which strategy Txgh should use.
|
132
|
+
|
133
|
+
#### Raw Config
|
134
|
+
|
135
|
+
Passing raw config to Txgh can be done like this:
|
136
|
+
|
137
|
+
```bash
|
138
|
+
export TXGH_CONFIG="raw://big_yaml_string_here"
|
139
|
+
```
|
140
|
+
|
141
|
+
When Txgh starts up, it will use the YAML payload that starts after `raw://`.
|
142
|
+
|
143
|
+
#### File Config
|
144
|
+
|
145
|
+
It might make more sense to store all your config in a file. Pass the path to Txgh like this:
|
146
|
+
|
147
|
+
```bash
|
148
|
+
export TXGH_CONFIG="file://path/to/config.yml"
|
149
|
+
```
|
150
|
+
|
151
|
+
When Txgh runs, it will read and parse the file at the path that comes after `file://`.
|
152
|
+
|
153
|
+
Of course, in both the file and the raw cases, environment variables can be specified via `export` or inline when starting Txgh. See the "Running Txgh" section below for more information.
|
154
|
+
|
155
|
+
Tx Config
|
156
|
+
---
|
157
|
+
|
158
|
+
In addition to the YAML configuration described above, Txgh needs to know which files to watch for changes. Txgh uses the same [ini-style config format](http://docs.transifex.com/client/config/#txconfig) as the Transifex CLI client, meaning you can simply point Txgh at this existing config and things will Just Work™. If you don't already have the CLI client configured, then keep reading.
|
159
|
+
|
160
|
+
You'll probably have to do some research to find out which formats Transifex supports in order to put together your tx config. You'll also need to know which files contain translatable content. Once you have all this information, constructing your tx config should be fairly straightforward.
|
161
|
+
|
162
|
+
### Format
|
163
|
+
|
164
|
+
By way of example, the tx config for a basic Rails app might look like this:
|
165
|
+
|
166
|
+
```ini
|
167
|
+
[main]
|
168
|
+
host = https://www.transifex.com
|
169
|
+
lang_map =
|
170
|
+
|
171
|
+
# Create one such section per file/resource
|
172
|
+
[myproject.enyml]
|
173
|
+
file_filter = config/locales/<lang>.yml
|
174
|
+
source_file = config/locales/en.yml
|
175
|
+
source_lang = en
|
176
|
+
type = YML
|
177
|
+
```
|
178
|
+
|
179
|
+
For every file you'd like Txgh to watch for changes, add another section to the config. The section header enclosed in square brackets is comprised of the project slug, a period, and the resource slug. The project slug can be found by inspecting the URL on Transifex project pages, while the resource slug is something you can make up. Keep in mind that resource slugs in the same project must be unique. Try to choose a name that makes it easy to identify the resource at-a-glance later.
|
180
|
+
|
181
|
+
Here's a description of each of the fields:
|
182
|
+
|
183
|
+
* **`file_filter`**: I'm not sure why this field is called `file_filter` since a more appropriate name would be "translation\_file" or "translation\_path\_template". Basically this field is a template that indicates where translations should be saved. The `<lang>` part functions as a placeholder and gets swapped out for a language code. The Spanish translation file for example would be written to config/locales/es.yml.
|
184
|
+
|
185
|
+
* **`source_file`**: The file to watch for changes.
|
186
|
+
|
187
|
+
* **`source_lang`**: The language the strings inside the `source_file` are written in.
|
188
|
+
|
189
|
+
* **`type`**: The format the strings in both the `source_file` and `file_filter` are stored in. This format must be supported by Transifex. For a full list of supported i18n types, see the [Transifex documentation](http://docs.transifex.com/formats/). Rails stores translations in the YAML file format. For a JavaScript project you might store translations in JSON and choose the KEYVALUEJSON i18n type. For Android XML, the ANDROID i18n type, etc.
|
190
|
+
|
191
|
+
### Loading Tx Config
|
192
|
+
|
193
|
+
Tx config is loaded in a similar fashion to Txgh config. There are three supported schemes, `raw`, `file`, and `git`. Both `raw` and `file` behave the same as their Txgh config counterparts, but the `git` scheme is different. It allows tx config to be downloaded dynamically from a git repository instead of read from a static string or file. This means you can store tx config in your git repository itself, where it can be versioned and changed on a per-branch basis. There are several benefits to this. First, any time you add a file to the tx config in a feature branch, that file will be identified and uploaded for that branch only without any additional configuration changes. Second, it places the responsibility of maintaining translated resources with the engineers working in the repo itself rather than on the engineers maintaining your translation infrastructure.
|
194
|
+
|
195
|
+
Loading tx config from git is straightforward. Use the `git://` scheme followed by the path to the config file inside the repository, eg. `git://.tx/config`.
|
196
|
+
|
197
|
+
For example, the Transifex section of your Txgh config might look like this:
|
198
|
+
|
199
|
+
```yaml
|
200
|
+
transifex:
|
201
|
+
projects:
|
202
|
+
project-slug:
|
203
|
+
tx_config: git://.tx/config
|
204
|
+
...
|
205
|
+
```
|
206
|
+
|
207
|
+
Every time a request is made, Txgh will download `.tx/config` from the given branch and use it to identify changed files.
|
208
|
+
|
209
|
+
Running Txgh
|
210
|
+
---
|
211
|
+
|
212
|
+
Txgh is distributed as a [Docker image](https://quay.io/repository/lumoslabs/txgh) and as a [Rubygem](https://rubygems.org/gems/txgh). You can choose to run it via Docker, install and run it as a Rubygem, or run it straight from a local clone of this repository.
|
213
|
+
|
214
|
+
### With Docker
|
215
|
+
|
216
|
+
Using Docker to run Txgh is pretty straightforward (keep in mind you'll need to have the Docker server set up wherever you want to run Txgh).
|
217
|
+
|
218
|
+
NOTE: You might consider using this [Docker template](https://github.com/lumoslabs/txgh-docker-template) instead of following the instructions below. The template contains all the files and scripts you need to get up and running quickly.
|
219
|
+
|
220
|
+
First, pull the Txgh image:
|
221
|
+
|
222
|
+
```bash
|
223
|
+
docker pull quay.io/lumoslabs/txgh:latest
|
224
|
+
```
|
225
|
+
|
226
|
+
Run the image in a new container:
|
227
|
+
|
228
|
+
```bash
|
229
|
+
docker run
|
230
|
+
-p 9292:9292
|
231
|
+
-e "TXGH_CONFIG=raw://$(cat path/to/config.yml)"
|
232
|
+
quay.io/lumoslabs/txgh:latest
|
233
|
+
```
|
234
|
+
|
235
|
+
At this point, Txgh should be up and running. To test it, try hitting the `health_check` endpoint. You should get a 200 response:
|
236
|
+
|
237
|
+
```bash
|
238
|
+
curl -v localhost:9292/health_check
|
239
|
+
....
|
240
|
+
< HTTP/1.1 200 OK
|
241
|
+
```
|
242
|
+
|
243
|
+
Note that Txgh might not be available on localhost depending on how your Docker client is configured. On a Mac with [docker-machine](https://docs.docker.com/machine/) for instance, you might try this instead:
|
244
|
+
|
245
|
+
```bash
|
246
|
+
curl -v 192.168.99.100:9292/health_check
|
247
|
+
```
|
248
|
+
|
249
|
+
(Where 192.168.99.100 is the IP of your docker machine instance).
|
250
|
+
|
251
|
+
### From Rubygems
|
252
|
+
|
253
|
+
Docker is by far the easiest way to run Txgh, but a close runner-up is via Rubygems. You'll need to have at least Ruby 2.1 installed as well as the [bundler gem](http://bundler.io/). Installing ruby and bundler are outside the scope of this README, but I'd suggest using a ruby installer like [rbenv](https://github.com/rbenv/rbenv) or [rvm](https://rvm.io/) to get the job done. Once ruby is installed, executing `gem install bundler` should be enough to install the bundler gem.
|
254
|
+
|
255
|
+
NOTE: You might consider using this [ruby template](https://github.com/lumoslabs/txgh-ruby-template) instead of following the instructions below. The template contains all the files and scripts you need to get up and running quickly.
|
256
|
+
|
257
|
+
1. Create a new directory for your Txgh instance.
|
258
|
+
|
259
|
+
2. Inside the new directory, create a file named `Gemfile`. This file is a manifest of all your ruby dependencies.
|
260
|
+
|
261
|
+
3. Inside `Gemfile`, add the following lines:
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
source 'http://rubygems.org'
|
265
|
+
gem 'txgh', '~> 1.0'
|
266
|
+
```
|
267
|
+
When bundler parses this file, it will know to fetch dependencies from rubygems.org, the most popular and ubiquitous gem host. It will also know to fetch and install the txgh gem.
|
268
|
+
4. Create another file next to `Gemfile` named `config.ru`. This file describes how to run the Txgh server, including where to mount the various endpoints.
|
269
|
+
5. Inside `config.ru` add the following lines:
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
require 'txgh'
|
273
|
+
|
274
|
+
map '/' do
|
275
|
+
use Txgh::Application
|
276
|
+
use Txgh::Triggers
|
277
|
+
run Sinatra::Base
|
278
|
+
end
|
279
|
+
|
280
|
+
map '/hooks' do
|
281
|
+
use Txgh::Hooks
|
282
|
+
run Sinatra::Base
|
283
|
+
end
|
284
|
+
```
|
285
|
+
|
286
|
+
Where each endpoint is mounted is entirely configurable inside this file, as is any additional middleware or your own custom endpoints you might want to add. Txgh is built on the [Rack](http://rack.github.io/) webserver stack, meaning the wide world of Rack is available to you inside this file. The `map`, `use`, and `run` methods are part of Rack's builder syntax.
|
287
|
+
|
288
|
+
6. Run `bundle install` to install gem dependencies.
|
289
|
+
|
290
|
+
7. Run `TXGH_CONFIG=file://path/to/config.yml bundle exec rackup`. The Txgh instance should start running in the foreground.
|
291
|
+
|
292
|
+
8. Test your Txgh instance by hitting the `health_check` endpoint as described above in the "With Docker" section, i.e. `curl -v localhost:9292/health_check`. You should get an HTTP 200 response.
|
293
|
+
|
294
|
+
### Local Clone
|
295
|
+
|
296
|
+
Running Txgh from a local copy of the source code requires almost the same setup as running it from Rubygems. Notably however the `config.ru` file has already been written for you.
|
297
|
+
|
298
|
+
Refer to the "From Rubygems" section above to get ruby and bundler installed before continuing.
|
299
|
+
|
300
|
+
1. Clone Txgh locally:
|
301
|
+
|
302
|
+
```bash
|
303
|
+
git clone git@github.com:lumoslabs/txgh.git
|
304
|
+
```
|
305
|
+
|
306
|
+
2. Change directory into the newly cloned repo (`cd txgh`) and run `bundle install` to install gem dependencies.
|
307
|
+
3. Run `TXGH_CONFIG=file://path/to/config.yml bundle exec rackup`. The Txgh instance should start running in the foreground.
|
308
|
+
|
309
|
+
4. Test your Txgh instance by hitting the `health_check` endpoint as described above in the "With Docker" section, i.e. `curl -v localhost:9292/health_check`. You should get an HTTP 200 response.
|
310
|
+
|
311
|
+
Running Tests
|
312
|
+
---
|
313
|
+
|
314
|
+
Txgh uses the popular RSpec test framework and has a comprehensive set of unit and integration tests. To run the full test suite, run `bundle exec rake spec:full`, or alternatively `FULL_SPEC=true bundle exec rspec`. To run only the unit tests (which is faster), run `bundle exec rspec`.
|
315
|
+
|
316
|
+
Requirements
|
317
|
+
---
|
318
|
+
|
319
|
+
Txgh requires an Internet connection to run, since its primary function is to connect two web services via webhooks and API calls. Other than that, it does not have any other external requirements like a database or cache.
|
320
|
+
|
321
|
+
Compatibility
|
322
|
+
---
|
323
|
+
|
324
|
+
Txgh was developed with Ruby 2.1.6, but is probably compatible with all versions between 2.0 and 2.3, and maybe even 1.9. Your mileage may vary when running on older versions.
|
325
|
+
|
326
|
+
Authors
|
327
|
+
---
|
328
|
+
|
329
|
+
This repository is a fork of Transifex's [original](https://github.com/transifex/txgh) and is maintained by [Cameron Dutro](https://github.com/camertron) from Lumos Labs.
|
330
|
+
|
331
|
+
License
|
332
|
+
---
|
333
|
+
|
334
|
+
Licensed under the Apache License, Version 2.0. See the LICENSE file included in this repository for the full text.
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Taken from https://github.com/fringd/zipline
|
2
|
+
# Didn't include zipline gem b/c it depends directly on rails and brings in a
|
3
|
+
# ton of useless dependencies
|
4
|
+
|
5
|
+
require 'stringio'
|
6
|
+
require 'zip'
|
7
|
+
|
8
|
+
# a ZipOutputStream that never rewinds output
|
9
|
+
# in order for that to be possible we store only uncompressed files
|
10
|
+
module Zipline
|
11
|
+
class OutputStream < Zip::OutputStream
|
12
|
+
|
13
|
+
#we need to be able to hand out own custom output in order to stream to browser
|
14
|
+
def initialize(io)
|
15
|
+
# Create an io stream thing
|
16
|
+
super StringIO.new, true
|
17
|
+
# Overwrite it with my own
|
18
|
+
@output_stream = io
|
19
|
+
end
|
20
|
+
|
21
|
+
def stream
|
22
|
+
@output_stream
|
23
|
+
end
|
24
|
+
|
25
|
+
def put_next_entry(entry_name, size)
|
26
|
+
new_entry = Zip::Entry.new(@file_name, entry_name)
|
27
|
+
|
28
|
+
#THIS IS THE MAGIC, tells zip to look after data for size, crc
|
29
|
+
new_entry.gp_flags = new_entry.gp_flags | 0x0008
|
30
|
+
|
31
|
+
super(new_entry)
|
32
|
+
|
33
|
+
# Uncompressed size in the local file header must be zero when bit 3
|
34
|
+
# of the general purpose flags is set, so set the size after the header
|
35
|
+
# has been written.
|
36
|
+
new_entry.size = size
|
37
|
+
end
|
38
|
+
|
39
|
+
# just reset state, no rewinding required
|
40
|
+
def finalize_current_entry
|
41
|
+
if current_entry
|
42
|
+
entry = current_entry
|
43
|
+
super
|
44
|
+
write_local_footer(entry)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_local_footer(entry)
|
49
|
+
@output_stream << [ 0x08074b50, entry.crc, entry.compressed_size, entry.size].pack('VVVV')
|
50
|
+
end
|
51
|
+
|
52
|
+
#never need to do this because we set correct sizes up front
|
53
|
+
def update_local_headers
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# helper to deal with difference between rubyzip 1.0 and 1.1
|
58
|
+
def current_entry
|
59
|
+
@currentEntry || @current_entry
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/txgh/github_api.rb
CHANGED
@@ -90,9 +90,14 @@ module Txgh
|
|
90
90
|
end
|
91
91
|
|
92
92
|
def download(repo, path, branch)
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
master = client.ref(repo, branch)
|
94
|
+
commit = client.commit(repo, master[:object][:sha])
|
95
|
+
tree = client.tree(repo, commit[:commit][:tree][:sha], recursive: 1)
|
96
|
+
|
97
|
+
if found = tree[:tree].find { |t| t[:path] == path }
|
98
|
+
b = blob(repo, found[:sha])
|
99
|
+
b['encoding'] == 'utf-8' ? b['content'] : Base64.decode64(b['content'])
|
100
|
+
end
|
96
101
|
end
|
97
102
|
|
98
103
|
def create_status(repo, sha, state, options = {})
|
@@ -37,7 +37,7 @@ module Txgh
|
|
37
37
|
|
38
38
|
def phrases
|
39
39
|
@phrases ||= extractor.from_string(raw) do |extractor|
|
40
|
-
extractor.extract_each
|
40
|
+
extractor.extract_each.map do |key, value|
|
41
41
|
{ 'key' => key, 'string' => value }
|
42
42
|
end
|
43
43
|
end
|
@@ -56,7 +56,7 @@ module Txgh
|
|
56
56
|
serializer.from_stream(stream, language) do |serializer|
|
57
57
|
phrases.each do |phrase|
|
58
58
|
serializer.write_key_value(
|
59
|
-
phrase['key'],
|
59
|
+
phrase['key'], (phrase['string'] || '').to_s
|
60
60
|
)
|
61
61
|
end
|
62
62
|
end
|
@@ -95,15 +95,6 @@ module Txgh
|
|
95
95
|
|
96
96
|
attr_reader :raw
|
97
97
|
|
98
|
-
def str(obj)
|
99
|
-
case obj
|
100
|
-
when Array
|
101
|
-
obj
|
102
|
-
else
|
103
|
-
obj.to_s
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
98
|
def extractor
|
108
99
|
id = EXTRACTOR_MAP.fetch(tx_resource.type) do
|
109
100
|
raise TxghInternalError,
|
data/lib/txgh/version.rb
CHANGED
@@ -35,29 +35,6 @@ describe DiffCalculator do
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
context 'with an array added to HEAD' do
|
39
|
-
let(:head_phrases) do
|
40
|
-
diff_point_phrases + [
|
41
|
-
phrase('villains', %w(Khan Chang Valeris Shinzon))
|
42
|
-
]
|
43
|
-
end
|
44
|
-
|
45
|
-
let(:diff_point_phrases) do
|
46
|
-
[
|
47
|
-
phrase('Bajor', 'Bajoran'),
|
48
|
-
phrase('Cardassia', 'Cardassian')
|
49
|
-
]
|
50
|
-
end
|
51
|
-
|
52
|
-
it 'includes the new array' do
|
53
|
-
expect(diff[:added].size).to eq(1)
|
54
|
-
expect(diff[:modified].size).to eq(0)
|
55
|
-
phrase = diff[:added].first
|
56
|
-
expect(phrase['key']).to eq('villains')
|
57
|
-
expect(phrase['string']).to eq(%w(Khan Chang Valeris Shinzon))
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
38
|
context 'with phrases removed from HEAD' do
|
62
39
|
let(:head_phrases) do
|
63
40
|
[]
|
@@ -73,21 +50,6 @@ describe DiffCalculator do
|
|
73
50
|
end
|
74
51
|
end
|
75
52
|
|
76
|
-
context 'with an array removed from HEAD' do
|
77
|
-
let(:head_phrases) do
|
78
|
-
[]
|
79
|
-
end
|
80
|
-
|
81
|
-
let(:diff_point_phrases) do
|
82
|
-
phrase('villains', %w(Khan Chang Valeris Shinzon))
|
83
|
-
end
|
84
|
-
|
85
|
-
it 'does not include the array' do
|
86
|
-
expect(diff[:added].size).to eq(0)
|
87
|
-
expect(diff[:modified].size).to eq(0)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
53
|
context 'with phrases modified in HEAD' do
|
92
54
|
let(:head_phrases) do
|
93
55
|
[phrase('TheNextGeneration', 'Jean Luc Picard (rocks)')]
|
@@ -106,24 +68,6 @@ describe DiffCalculator do
|
|
106
68
|
end
|
107
69
|
end
|
108
70
|
|
109
|
-
context 'with an array modified in HEAD' do
|
110
|
-
let(:head_phrases) do
|
111
|
-
[phrase('villains', %w(Khan Chang Valeris Shinzon))]
|
112
|
-
end
|
113
|
-
|
114
|
-
let(:diff_point_phrases) do
|
115
|
-
[phrase('villains', %w(Khan Chang Valeris))]
|
116
|
-
end
|
117
|
-
|
118
|
-
it 'includes the entire array' do
|
119
|
-
expect(diff[:added].size).to eq(0)
|
120
|
-
expect(diff[:modified].size).to eq(1)
|
121
|
-
phrase = diff[:modified].first
|
122
|
-
expect(phrase['key']).to eq('villains')
|
123
|
-
expect(phrase['string']).to eq(%w(Khan Chang Valeris Shinzon))
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
71
|
context 'with no phrases modified, added, or removed' do
|
128
72
|
let(:head_phrases) do
|
129
73
|
[
|
data/spec/github_api_spec.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'base64'
|
3
2
|
|
4
3
|
include Txgh
|
5
4
|
|
@@ -179,30 +178,20 @@ describe GithubApi do
|
|
179
178
|
end
|
180
179
|
|
181
180
|
describe '#download' do
|
182
|
-
let(:path) { 'path/to/file.xyz' }
|
183
|
-
|
184
181
|
it 'downloads the file from the given branch' do
|
185
|
-
|
186
|
-
receive(:contents)
|
187
|
-
.with(repo, path: path, ref: branch)
|
188
|
-
.and_return(
|
189
|
-
content: 'content', encoding: 'utf-8'
|
190
|
-
)
|
191
|
-
)
|
182
|
+
path = 'path/to/file.xyz'
|
192
183
|
|
193
|
-
expect(
|
194
|
-
|
184
|
+
expect(client).to receive(:ref).with(repo, branch).and_return(object: { sha: :branch_sha })
|
185
|
+
expect(client).to receive(:commit).with(repo, :branch_sha).and_return(commit: { tree: { sha: :base_tree_sha } })
|
186
|
+
expect(client).to receive(:tree).with(repo, :base_tree_sha, recursive: 1).and_return(
|
187
|
+
tree: [{ path: path, sha: :blob_sha }]
|
188
|
+
)
|
195
189
|
|
196
|
-
|
197
|
-
|
198
|
-
receive(:contents)
|
199
|
-
.with(repo, path: path, ref: branch)
|
200
|
-
.and_return(
|
201
|
-
content: Base64.encode64('content'), encoding: 'base64'
|
202
|
-
)
|
190
|
+
expect(client).to receive(:blob).with(repo, :blob_sha).and_return(
|
191
|
+
{ 'content' => :blob, 'encoding' => 'utf-8' }
|
203
192
|
)
|
204
193
|
|
205
|
-
expect(api.download(repo, path, branch)).to eq(
|
194
|
+
expect(api.download(repo, path, branch)).to eq(:blob)
|
206
195
|
end
|
207
196
|
end
|
208
197
|
end
|
@@ -46,22 +46,6 @@ describe MergeCalculator do
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
context 'with an array added in HEAD' do
|
50
|
-
let(:diff_point_phrases) do
|
51
|
-
[phrase('planet.earth', 'Human')]
|
52
|
-
end
|
53
|
-
|
54
|
-
let(:head_phrases) do
|
55
|
-
diff_point_phrases + [
|
56
|
-
phrase('villains', %w(Kahn Chang Valeris Shinzon))
|
57
|
-
]
|
58
|
-
end
|
59
|
-
|
60
|
-
it 'includes the added array' do
|
61
|
-
expect(merge_result.phrases).to eq(head_phrases)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
49
|
context 'with phrases removed from HEAD' do
|
66
50
|
let(:diff_point_phrases) do
|
67
51
|
head_phrases + [
|
@@ -78,22 +62,6 @@ describe MergeCalculator do
|
|
78
62
|
end
|
79
63
|
end
|
80
64
|
|
81
|
-
context 'with an array removed from HEAD' do
|
82
|
-
let(:diff_point_phrases) do
|
83
|
-
head_phrases + [
|
84
|
-
phrase('villains', %w(Kahn Chang Valeris Shinzon))
|
85
|
-
]
|
86
|
-
end
|
87
|
-
|
88
|
-
let(:head_phrases) do
|
89
|
-
[phrase('planet.earth', 'Human')]
|
90
|
-
end
|
91
|
-
|
92
|
-
it 'does not include the removed array' do
|
93
|
-
expect(merge_result.phrases).to eq(head_phrases)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
65
|
context 'with phrases modified in HEAD' do
|
98
66
|
let(:diff_point_phrases) do
|
99
67
|
[phrase('planet.bajor', 'Cardassian')]
|
@@ -108,20 +76,6 @@ describe MergeCalculator do
|
|
108
76
|
end
|
109
77
|
end
|
110
78
|
|
111
|
-
context 'with an array modified in HEAD' do
|
112
|
-
let(:diff_point_phrases) do
|
113
|
-
[phrase('villains', %w(Kahn Chang Valeris))]
|
114
|
-
end
|
115
|
-
|
116
|
-
let(:head_phrases) do
|
117
|
-
[phrase('villains', %w(Kahn Chang Valeris Shinzon))]
|
118
|
-
end
|
119
|
-
|
120
|
-
it 'includes the modified phrase' do
|
121
|
-
expect(merge_result.phrases).to eq(head_phrases)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
79
|
context 'with no phrases modified, added, or removed' do
|
126
80
|
let(:diff_point_phrases) do
|
127
81
|
[phrase('planet.bajor', 'Bajoran')]
|
@@ -11,29 +11,18 @@ describe ResourceContents do
|
|
11
11
|
)
|
12
12
|
end
|
13
13
|
|
14
|
-
let(:
|
14
|
+
let(:raw_contents) do
|
15
15
|
outdent(%Q(
|
16
16
|
en:
|
17
17
|
welcome:
|
18
|
-
message:
|
18
|
+
message: Hello!
|
19
19
|
goodbye:
|
20
|
-
message:
|
21
|
-
))
|
22
|
-
end
|
23
|
-
|
24
|
-
let(:array_contents) do
|
25
|
-
outdent(%Q(
|
26
|
-
en:
|
27
|
-
captains:
|
28
|
-
- "Janeway"
|
29
|
-
- "Picard"
|
30
|
-
- "Sisko"
|
31
|
-
- "Kirk"
|
20
|
+
message: Goodbye!
|
32
21
|
))
|
33
22
|
end
|
34
23
|
|
35
24
|
let(:contents) do
|
36
|
-
ResourceContents.from_string(tx_resource,
|
25
|
+
ResourceContents.from_string(tx_resource, raw_contents)
|
37
26
|
end
|
38
27
|
|
39
28
|
describe '#phrases' do
|
@@ -43,13 +32,6 @@ describe ResourceContents do
|
|
43
32
|
{ 'key' => 'goodbye.message', 'string' => 'Goodbye!' }
|
44
33
|
])
|
45
34
|
end
|
46
|
-
|
47
|
-
it 'preserves arrays' do
|
48
|
-
rsrc_contents = ResourceContents.from_string(tx_resource, array_contents)
|
49
|
-
expect(rsrc_contents.phrases).to eq([
|
50
|
-
{ 'key' => 'captains', 'string' => %w(Janeway Picard Sisko Kirk) }
|
51
|
-
])
|
52
|
-
end
|
53
35
|
end
|
54
36
|
|
55
37
|
describe '#add' do
|
@@ -65,14 +47,13 @@ describe ResourceContents do
|
|
65
47
|
it 'serializes the phrases to the given stream' do
|
66
48
|
stream = StringIO.new
|
67
49
|
contents.write_to(stream)
|
68
|
-
expect(stream.string).to eq(
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
expect(stream.string).to eq(array_contents)
|
50
|
+
expect(stream.string).to eq(outdent(%Q(
|
51
|
+
en:
|
52
|
+
welcome:
|
53
|
+
message: "Hello!"
|
54
|
+
goodbye:
|
55
|
+
message: "Goodbye!"
|
56
|
+
)))
|
76
57
|
end
|
77
58
|
|
78
59
|
it 'includes phrases that were added after the fact' do
|
data/txgh.gemspec
CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.platform = Gem::Platform::RUBY
|
14
14
|
s.has_rdoc = true
|
15
15
|
|
16
|
-
s.add_dependency 'abroad', '~> 4.
|
16
|
+
s.add_dependency 'abroad', '~> 4.0'
|
17
17
|
s.add_dependency 'faraday', '~> 0.9'
|
18
18
|
s.add_dependency 'faraday_middleware', '~> 0.10'
|
19
19
|
s.add_dependency 'json', '~> 1.8'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: txgh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 6.0.0.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Jackowski
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-08-
|
12
|
+
date: 2016-08-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: abroad
|
@@ -17,14 +17,14 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '4.
|
20
|
+
version: '4.0'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '4.
|
27
|
+
version: '4.0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: faraday
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -104,6 +104,8 @@ extensions: []
|
|
104
104
|
extra_rdoc_files: []
|
105
105
|
files:
|
106
106
|
- LICENSE
|
107
|
+
- README.md
|
108
|
+
- lib/ext/zipline/output_stream.rb
|
107
109
|
- lib/txgh.rb
|
108
110
|
- lib/txgh/category_support.rb
|
109
111
|
- lib/txgh/config.rb
|
@@ -186,12 +188,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
186
188
|
version: '0'
|
187
189
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
188
190
|
requirements:
|
189
|
-
- - "
|
191
|
+
- - ">"
|
190
192
|
- !ruby/object:Gem::Version
|
191
|
-
version:
|
193
|
+
version: 1.3.1
|
192
194
|
requirements: []
|
193
195
|
rubyforge_project:
|
194
|
-
rubygems_version: 2.
|
196
|
+
rubygems_version: 2.2.3
|
195
197
|
signing_key:
|
196
198
|
specification_version: 4
|
197
199
|
summary: A library for syncing translation resources between Github and Transifex.
|