├── .gitignore
├── CHANGELOG.txt
├── Default.sublime-commands
├── LICENSE.txt
├── README.md
├── customfilters.py
├── filterpipes.py
└── filters.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | lib/
17 | lib64/
18 | parts/
19 | sdist/
20 | var/
21 | *.egg-info/
22 | .installed.cfg
23 | *.egg
24 |
25 | # PyInstaller
26 | # Usually these files are written by a python script from a template
27 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
28 | *.manifest
29 | *.spec
30 |
31 | # Installer logs
32 | pip-log.txt
33 | pip-delete-this-directory.txt
34 |
35 | # Unit test / coverage reports
36 | htmlcov/
37 | .tox/
38 | .coverage
39 | .cache
40 | nosetests.xml
41 | coverage.xml
42 |
43 | # Translations
44 | *.mo
45 | *.pot
46 |
47 | # Django stuff:
48 | *.log
49 |
50 | # Sphinx documentation
51 | docs/_build/
52 |
53 | # PyBuilder
54 | target/
55 |
--------------------------------------------------------------------------------
/CHANGELOG.txt:
--------------------------------------------------------------------------------
1 | Version: 1.1.0 [Apr 27, 2015]
2 | -----------------------------
3 | Allow non-zero returns from external processes.
4 | FilterPipesProcessCommand now has an "expected_returns" parameter
5 | which allows you to specify a list of "success" return codes. It
6 | defaults to just [0], which is typical. Setting this to an empty
7 | set or false will disable checking the return code, and assume
8 | all returns are successful.
9 |
10 | Allow arbitrary options to be passed to Popen.
11 | Added parameter 'subprocess_args' to FilterPipesProcessCommand,
12 | which allows you to specify any additional parameters to pass to
13 | your subprocess call, such as a specific working directory or environment.
14 |
15 |
16 | Version: 1.0.1 [Mar 24, 2015]
17 | -----------------------------
18 | Initial Release
19 |
20 |
--------------------------------------------------------------------------------
/Default.sublime-commands:
--------------------------------------------------------------------------------
1 | [
2 | /* Prompts for a command and executes it on the shell */
3 | {
4 | "caption": "FilterPipes: Send Text to Command",
5 | "command": "filter_pipes_exec_prompt"
6 | },
7 | /* Creates (or opens) your own customer FilterPipes plugin */
8 | {
9 | "caption": "FilterPipes: My Custom Filters Plugin",
10 | "command": "filter_pipes_my_plugin"
11 | },
12 |
13 | /* Various encode/decode functions provided in filters.py */
14 | {
15 | "caption": "FilterPipes: Base64 Encode",
16 | "command": "filter_pipes_base64",
17 | "args": {
18 | "decode": false,
19 | "wrap": 64
20 | }
21 | },
22 | {
23 | "caption": "FilterPipes: Base64 Decode",
24 | "command": "filter_pipes_base64",
25 | "args": {
26 | "decode": true
27 | }
28 | },
29 | {
30 | "caption": "FilterPipes: URL Encode",
31 | "command": "filter_pipes_urlencode",
32 | "args": {
33 | "decode": false
34 | }
35 | },
36 | {
37 | "caption": "FilterPipes: URL Decode",
38 | "command": "filter_pipes_urlencode",
39 | "args": {
40 | "decode": true
41 | }
42 | },
43 | {
44 | "caption": "FilterPipes: String Escape",
45 | "command": "filter_pipes_escape"
46 | },
47 | {
48 | "caption": "FilterPipes: String Unescape",
49 | "command": "filter_pipes_escape",
50 | "args": {
51 | "decode": true
52 | }
53 | },
54 | /* Example of using the filter_pipes_regex functionality */
55 | {
56 | "caption": "FilterPipes: Strip Trailing Space",
57 | "command": "filter_pipes_regex",
58 | "args": {
59 | "regex": "[\t ]+$",
60 | "replacement": "",
61 | "lines": true
62 | }
63 | },
64 | /* Advanced regex with post_init use */
65 | {
66 | "caption": "FilterPipes: Hex to Decimal",
67 | "command": "filter_pipes_int_to_int",
68 | "args": {
69 | "from_base": 16,
70 | "to_base": 10
71 | }
72 | },
73 | {
74 | "caption": "FilterPipes: Decimal to Hex",
75 | "command": "filter_pipes_int_to_int",
76 | "args": {
77 | "from_base": 10,
78 | "to_base": 16
79 | }
80 | },
81 | {
82 | "caption": "FilterPipes: Decimal to Octal",
83 | "command": "filter_pipes_int_to_int",
84 | "args": {
85 | "from_base": 10,
86 | "to_base": 8
87 | }
88 | },
89 | {
90 | "caption": "FilterPipes: Octal to Decimal",
91 | "command": "filter_pipes_int_to_int",
92 | "args": {
93 | "from_base": 8,
94 | "to_base": 10
95 | }
96 | }
97 | ]
98 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FilterPipes
2 |
3 | FilterPipes is a SublimeText (v2 and v3) plugin for filtering selected text
4 | through pipes or filters, either written as Python functions or as external
5 | processes.
6 |
7 | This project lives at [github.com/tylerl/FilterPipes](https://github.com/tylerl/FilterPipes).
8 |
9 | ### Filter Through Process
10 | This plugin provides a prompt for you to supply the shell command to execute.
11 |
12 | 
13 |
14 | ### Filter Through Python
15 | Python code can also be used as the filter function. Several useful examples
16 | are provided as listed below, but the real power comes in when you create your
17 | own functions.
18 |
19 | ### Creating Custom Filters
20 | FilterPipes makes it crazy-simple for you to create your own custom filter
21 | plugins. There are some pre-defined classes for simple text replacement,
22 | regular expressions, character-for-character translations, and external
23 | processes. Creating your own custom filter is as simple as creating a
24 | Python class with a single method called `filter` as demonstrated below:
25 |
26 | 
27 |
28 | # Provided Commands
29 |
30 | The following commands are included. All commands are accessible from
31 | the command palette. No shortcut keys are assigned by default,
32 | though you are free to add your own if you like.
33 |
34 | * **My Custom Filters Plugin**: Create and access your own FilterPipes-derived
35 | custom plugin. The first time you run this command it will create the plugin
36 | for you and add some sample content. Every subsequent run will simply open
37 | your existing plugin directory.
38 | * **Send Text to Command**: Prompts you for a shell command to run, and
39 | then executes that command using your selection(s) as `stdin`, and replacing
40 | them with `stdout` if the program ends successfully.
41 | * **Base64 Encode** and **Base64 Decode**: Encodes and decodes text using
42 | [Base64](Base64 Decode) encoding rules.
43 | * **URL Encode** and **URL Decode**: Similarly, encodes and decodes text
44 | using [URL ("percent") encoding](http://en.wikipedia.org/wiki/Percent-encoding).
45 | * **String Escape** and **String Unescape**: Encodes and decodes strings using
46 | simple string-escaping rules (e.g. TAB character becomes `\t`, newline becomes
47 | `\n`, and so forth.
48 | * **Strip Trailing Space**: Does what it says on the tin: it strips any spaces
49 | at the end of lines. While mildly useful, this is here primarily because I
50 | wanted to include an example of a Regex-based filter.
51 |
52 | ### Example Filters
53 |
54 | These filters are included in the "My Custom Filters Plugin" example. Run the
55 | "My Custom Filters Plugin" command to install them.
56 |
57 | Most of these filters don't use any custom Python class, but instead use the
58 | customization options of the provided generic filters to do something more
59 | interesting. The only code will be the entry in `Default.sublime-commands` file
60 | for your created plugin.
61 |
62 | #### Translation Commands
63 | These provide simple character-for-character translations to apply. Think of
64 | them like the unix `tr` command, if you're nerdy enough to ever have used it.
65 |
66 | * **Swap Quotes**: Swaps single quotes for double quotes (and vice-versa).
67 | (surprisingly useful, but not context-aware).
68 | * **Convert to Straight Quotes**: Turns all the varieties of "smart quotes" into
69 | normal "straight" quotes, like any good programmer would prefer.
70 |
71 | #### Regex Filter
72 | Your regex filter configuration provides a simple search regex and replacement
73 | string. It's not unlike regex-based search-and-replace, except your setup gets
74 | baked into a single simple command.
75 |
76 | * **Collapse Spaces**: Turns runs of any sort of whitespace into a single
77 | space character.
78 |
79 | * **Hex to Decimal, Decimal to Hex, Octal to Decimal, Decimal to Octal**:
80 | Convert integers between popular numeric bases. This is provided as an
81 | example of a regex filter that uses a callback function for replacements
82 | instead of a simple string constant. It's also an example of the use of
83 | using the `post_init` callback to rewrite the runtime configuration
84 | (in this case the search regex) programatically.
85 |
86 | #### Process Filters
87 | Entire plugins have been written for performing this one simple action. Actually,
88 | this plugin started out as one of them. It's gotten much more useful since then,
89 | though. This filter type lets you specify a command to run. Kind of like the
90 | "Send Text to Command" filter, but without the input prompt, and (by default)
91 | without the shell interpretation (you can optionally turn that on, though).
92 |
93 | Note that the included examples require you to install the associated program.
94 | You may have to fiddle with the path (and perhaps command-line options) as well,
95 | depending on your local installation.
96 |
97 | * **Beautify JS (js-beautify)**: Many javascript developers use a command called
98 | "js-beautify" to clean up their code. This filter calls that command... assuming
99 | it's installed in the PATH. Otherwise, it does nothing but serve as an example
100 | of how to write this kind of filter.
101 | * **Minify JS (uglifyjs)**: Just like the above, but exactly the opposite. This
102 | command makes your javascript compact and illegible... assuming you have it
103 | installed -- the same caveat applies as above.
104 |
105 | #### Python Filters
106 | These are the *really* cool ones. You write your own python code to transform
107 | the selected text. Note that the naming of the command follows the same pattern
108 | as per normal for SublimeText. You convert the Python class name from CamelCase
109 | to underscore_case and remove `_command` from the end to ge the name that goes into
110 | your `.sublime-commands` file. This is done internally by SublimeText, so don't get
111 | mad at me about it.
112 |
113 | * **Reverse Words**: This is the example coded in the animation above. It reverses
114 | the order of words delimited by spaces. I have no idea why anyone would ever
115 | want to do that, but the code is tiny and the effect is obvious, so it makes a
116 | pretty compelling piece of example code.
117 | * **to CamelCase**: Converts words from `underscore_case` to `CamelCase`.
118 | * **to mixedCase**: Converts words from `underscore_case` to `mixedCase`, which is
119 | exactly the same as CamelCase, but without the first letter capitalized. In fact,
120 | both these filters use the same Python class. This filter demonstrates how to add
121 | your own custom command options.
122 | * **to underscore_case**: Converts from `CamelCase` back to `underscore_case`, included
123 | mostly out of an obsessive-compulsive need for parity.
124 |
125 | # Creating Your Own
126 |
127 | To write your own plugin, start out by bringing up the command pallete (typically
128 | ctrl+shift+P or
129 | cmd+shift+P) and running "My Custom Filters Plugin".
130 | Even if you don't end up using the plugin it creates, this will at least give
131 | you a working scaffold with all the right configuration and examples such to get started.
132 |
133 | All commands take the optional parameter `use_selections`, which when set to false, tells
134 | the system to ignore your text selections and pass the whole file in. Typically this is
135 | useful for tools that use the entire file for context, such as source code formatters.
136 |
137 | ## Using the built-in filter classes
138 |
139 | Generic filter classes are provided that allow you to do a lot of cool things without
140 | writing any Python code. For these, you don't need a custom plugin, you can just
141 | put the appropriate command invocation in your own `.sublime-settings` or `.sublime-keymap`
142 | file.
143 |
144 | #### Using `filter_pipes_process`
145 |
146 | This sends text to an external process. The general configuration looks like
147 | this. Note that unless you've specified `shell` as `true`, you'll want your
148 | command to be a list of parameters rather than a single string.
149 |
150 | ```json
151 | {
152 | "caption": "YOLO Text",
153 | "command": "filter_pipes_process",
154 | "args": {
155 | "command": ["banner", "-w", "80"],
156 | "shell": false
157 | }
158 | }
159 | ```
160 |
161 | #### Using `filter_pipes_translate`
162 |
163 | Does character-for-character translations using Python's `string.translate`
164 | function. That remark earlier about the `tr` command? Yeah, time to get your *Nerd* on.
165 |
166 | ```json
167 | {
168 | "caption": "H4X0R",
169 | "command": "filter_pipes_translate",
170 | "args": {
171 | "before": "AaEeiIoOlsSTt",
172 | "after": "4433110015577"
173 | }
174 | }
175 | ```
176 |
177 | #### Using `filter_pipes_regex`
178 |
179 | Supply a regex and replacement. Pretty straightforward, but don't forget that
180 | backslashes have to be escaped for JSON encoding.
181 |
182 | ```json
183 | {
184 | "caption": "Format Phone Numbers",
185 | "command": "filter_pipes_regex",
186 | "args": {
187 | "regex": "(\\d{3})(\\d{3})(\\d{4})",
188 | "replacement": "(\\1) \\2-\\3"
189 | }
190 | }
191 | ```
192 |
193 | #### Writing your own custom Python Filters
194 |
195 | Here's where the real magic happens. You can very easily write
196 | custom code to transform your text in any way imaginable, and
197 | it only takes a minute or two to get going.
198 |
199 | Once you've got your custom filter plugin created, open
200 | up the `myfilters.py` file and at the bottom of the file
201 | create a class like this:
202 |
203 | ```python
204 | class SuperAwesomeFilterCommand(filterpipes.FilterPipesCommandBase):
205 | """Does approximately nothing, but does it with style."""
206 | def filter(self, data):
207 | return data
208 | ```
209 |
210 | The class name determines the command name using the SublimeText rules
211 | metioned earlier. So `SuperAwesomeFilterCommand` becomes `super_awesome_filter`.
212 |
213 | Whatever your `filter()` function returns is what your text will
214 | be replaced with. So go get creative.
215 |
216 | Note that if you want your plugin to take some configurable parameter
217 | from the `.sublime-commands` file, it's pretty easy. First you create
218 | a class variable and assign it your desired default value:
219 |
220 | ```python
221 | class SurroundSelectionCommand(filterpipes.FilterPipesCommandBase):
222 | """Prepends and appends some string to the selected text."""
223 | prepend = '{'
224 | append = '}'
225 | def filter(self, data):
226 | return self.prepend + data + self.append
227 | ```
228 |
229 | Then in your `.sublime-keymap` or `.sublime-commands` file, specify
230 | some alternate value for those variables in the "args" section like so:
231 |
232 | ```json
233 | {
234 | "caption": "Double Power Bracket",
235 | "command": "surround_selection",
236 | "args": {
237 | "prepend": ".:[[",
238 | "append": "]]:."
239 | }
240 | }
241 | ```
242 |
243 | # Copyright and License
244 |
245 | ***This is not an official Google product.***
246 |
247 | Copyright 2015 Google Inc. All Rights Reserved.
248 |
249 | Licensed under the Apache License, Version 2.0 (the "License");
250 | you may not use this file except in compliance with the License.
251 | You may obtain a copy of the License at
252 |
253 | http://www.apache.org/licenses/LICENSE-2.0
254 |
255 | Unless required by applicable law or agreed to in writing, software
256 | distributed under the License is distributed on an "AS IS" BASIS,
257 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
258 | See the License for the specific language governing permissions and
259 | limitations under the License.
260 |
261 |
--------------------------------------------------------------------------------
/customfilters.py:
--------------------------------------------------------------------------------
1 | # Copyright 2015 Google Inc. All Rights Reserved.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | # This is not an official Google product.
16 |
17 | """Custom FilterPipes plugin creation.
18 |
19 | The components in this file handle the creation and access
20 | of your custom FilterPipes plugin project. The full content
21 | of the custom plugin is contained here.
22 |
23 | Part of the FilterPipes SublimeText Plugin.
24 | github.com/tylerl/FilterPipes
25 |
26 | """
27 |
28 |
29 | import os
30 | import sublime
31 | import sublime_plugin
32 | CUSTOM_PLUGIN_NAME = 'MyCustomFilterPipes'
33 | CUSTOM_PLUGIN_PROJECT = 'plugin.sublime-project'
34 | README_FILENAME = 'README.txt'
35 |
36 |
37 | class FilterPipesMyPluginCommand(sublime_plugin.WindowCommand):
38 | def _create_plugin_impl(self, plugin_dir):
39 | os.mkdir(plugin_dir, 493) # 0755 (python 2/3 safe)
40 | for name, content in CONTENT_TEMPLATE.items():
41 | filepath = os.path.join(plugin_dir, name)
42 | with os.fdopen(
43 | os.open(filepath, os.O_WRONLY | os.O_CREAT, 420),
44 | 'w') as f: # 420 = 0644
45 | if name == README_FILENAME:
46 | content = content.format(directory=plugin_dir)
47 | f.write(content)
48 |
49 | def _maybe_create_plugin(self, plugin_dir):
50 | if not sublime.ok_cancel_dialog(
51 | 'Your custom filter plugin does not exist yet. '
52 | 'Would you like to create it now?',
53 | 'Create it'
54 | ):
55 | return False
56 | self._create_plugin_impl(plugin_dir)
57 | return True
58 |
59 | def run(self):
60 | plugin_dir = os.path.join(sublime.packages_path(), CUSTOM_PLUGIN_NAME)
61 | if not os.path.exists(plugin_dir):
62 | try:
63 | if not self._maybe_create_plugin(plugin_dir):
64 | return
65 | except Exception as ex:
66 | sublime.message_dialog('Failed to create plugin:\n%s' % (ex))
67 | raise ex
68 | readme_path = os.path.join(plugin_dir, README_FILENAME)
69 | self.window.run_command('open_file', {'file': readme_path})
70 | self.window.run_command('open_dir', {'dir': plugin_dir})
71 |
72 |
73 | CONTENT_TEMPLATE = {
74 | 'Default.sublime-commands':
75 | r"""[
76 | /*********************************************************
77 | * The following example commands show you the basics of *
78 | * working with filter pipes. Delete, modify, rename, and *
79 | * use as you see fit. *
80 | **********************************************************/
81 |
82 | // ###########################################################
83 | // Translate filters. Translates characters in the "before"
84 | // string to the corresponding (by position) character in
85 | // the "after" string.
86 | {
87 | "caption": "FilterPipes Example: Swap Quotes",
88 | "command": "filter_pipes_translate",
89 | "args": {
90 | "before": "'\"",
91 | "after": "\"'"
92 | }
93 | },
94 |
95 | {
96 | "caption": "FilterPipes Example: Convert to Straight Quotes",
97 | "command": "filter_pipes_translate",
98 | "args": {
99 | "before": "\u201c\u201d\u201f\u301d\u301e\uff02\u201e\u301f\u2018\u2019\u201b\uff07\u201a",
100 | "after": "\"\"\"\"\"\"\"\"'''''"
101 | }
102 | },
103 | // ###########################################################
104 | // Regex filters. Runs the selection through a regular
105 | // expression replacement.
106 | //
107 | // You can specify
108 | // "lines": true
109 | // to add the MULTILINE flag to replacement function --
110 | // this makes ^ and $ match the beginning and end of each
111 | // individual line instead of the whole string.
112 |
113 | {
114 | "caption": "FilterPipes Example: collapse spaces",
115 | "command": "filter_pipes_regex",
116 | "args": {
117 | "regex": "\\s+",
118 | "replacement": " "
119 | }
120 | },
121 | // ###########################################################
122 | // Process filters. Runs the selection through an external
123 | // program as a filter instead. Remember you can specify
124 | // "use_selections": false
125 | // if you want to always process the whole file, regardless of
126 | // selections.
127 | //
128 | // Also note that if command is a list, then the first element
129 | // is the executable, and the other elements are parameters.
130 | // If it's instead simply a string, then, then that string is
131 | // interpreted as a shell command.
132 |
133 | // "pip install jsbeautifier" to make this one work
134 | {
135 | "caption": "FilterPipes Example: Beautify JS (js-beautify)",
136 | "command": "filter_pipes_process",
137 | "args": {
138 | "command": ["js-beautify", "-i"]
139 | }
140 | },
141 | {
142 | "caption": "FilterPipes Example: Minify JS (uglifyjs)",
143 | "command": "filter_pipes_process",
144 | "args": {
145 | "command": ["uglifyjs"]
146 | }
147 | },
148 |
149 | // ###########################################################
150 | // Python Filters. These have corresponding entries in the
151 | // myfilters.py file, where the command name is translated to
152 | // camelcase with the word "Command" appended. So for example
153 | // "camel_case_filter" is the class "CamelCaseFilterCommand"
154 | // in myfilters.py. The filter() function determines what the
155 | // filter does. Finally, any args provided here get automatically
156 | // set as class object instance variables; usually overriding
157 | // a Default setting.
158 |
159 | { /* See ReverseWordsCommand */
160 | "caption": "FilterPipes Example: Reverse Words",
161 | "command": "reverse_words"
162 | },
163 |
164 | { /* See CamelCaseFilterCommand */
165 | "caption": "FilterPipes Example: to CamelCase",
166 | "command": "camel_case_filter",
167 | "args": {
168 | "initial_caps": true
169 | }
170 | },
171 |
172 | { /* See CamelCaseFilterCommand */
173 | "caption": "FilterPipes Example: to mixedCase",
174 | "command": "camel_case_filter",
175 | "args": {
176 | "initial_caps": false
177 | }
178 | },
179 |
180 | { /* See UnderscoreCaseFilterCommand */
181 | "caption": "FilterPipes Example: to underscore_case",
182 | "command": "underscore_case_filter"
183 | }
184 | ]
185 | """,
186 | 'myfilters.py':
187 | """"\""Sample filters for doing mildly useful things using FilterPipes."\""
188 |
189 | try:
190 | from FilterPipes import filterpipes # ST3-style import
191 | except ImportError:
192 | import filterpipes # ST2-style import
193 |
194 |
195 | class ReverseWordsCommand(filterpipes.FilterPipesCommandBase):
196 | "\""Reverse the order of selected words. Extremely simple example."\""
197 | def filter(self, data):
198 | return " ".join(reversed(data.split(" ")))
199 |
200 |
201 | class CamelCaseFilterCommand(filterpipes.FilterPipesCommandBase):
202 | "\""Converts words_with_underlines to CamelCase."\""
203 | initial_caps = True
204 |
205 | def filter(self, data):
206 | next_upper = self.initial_caps
207 | out = []
208 | for c in data:
209 | if c == '_':
210 | next_upper = True
211 | elif c.islower():
212 | if next_upper:
213 | out.append(c.upper())
214 | else:
215 | out.append(c)
216 | next_upper = False
217 | else:
218 | next_upper = self.initial_caps and not c.isalnum()
219 | out.append(c)
220 | return ''.join(out)
221 |
222 |
223 | class UnderscoreCaseFilterCommand(filterpipes.FilterPipesCommandBase):
224 | "\""Converts CamelCase to words_with_underlines."\""
225 |
226 | def filter(self, data):
227 | prev_lower = False
228 | out = []
229 | for c in data:
230 | if c.isupper():
231 | if prev_lower:
232 | out.append('_')
233 | out.append(c.lower())
234 | prev_lower = False
235 | elif c.islower():
236 | prev_lower = True
237 | out.append(c)
238 | else:
239 | prev_lower = False
240 | out.append(c)
241 | return ''.join(out)
242 | """,
243 | README_FILENAME:
244 | """CUSTOM PLUGIN FILTERS
245 | =====================
246 |
247 | This directory contains the a set of filters to be customized
248 | by you, the user. It's pre-populated with a few that show you
249 | the basics of how to create your own. These examples are
250 | designed to be useful in their own right. But feel free to
251 | modify, rename, or remove them as you see fit.
252 |
253 | To restore it to this initial state, just remove or
254 | rename this directory and re-create it using the
255 | "My Custom Filters" command.
256 |
257 | This plugin was created at:
258 | {directory}
259 |
260 | FILES
261 | =====
262 |
263 | You should pay attention to the following files. The others... meh.
264 |
265 | Default.sublime-commands
266 | ------------------------
267 |
268 | A good first place to start. This is where you define what commands
269 | will appear in your command palette. You can use the same Python
270 | class to create multiple commands using different arguments. You'll
271 | see several examples of that in example content provided.
272 |
273 | You can create a Default.sublime-keymap file to do the same thing
274 | but for keyboard shortcuts instead of command palette entries.
275 |
276 | myfilters.py
277 | ------------
278 |
279 | This contains examples of custom Python filters. Each filter command
280 | is its own classes with a filter() function. The naming convention
281 | on the class is enforced by Sublime, so follow the pattern you see
282 | in the file; namely: camelcase words ending in "Command". For example,
283 | to create a command named "convert_to_lowercase" your class will be
284 | named ConvertToLowercaseCommand.
285 | """
286 | }
287 |
--------------------------------------------------------------------------------
/filterpipes.py:
--------------------------------------------------------------------------------
1 | # Copyright 2015 Google Inc. All Rights Reserved.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | # This is not an official Google product.
16 |
17 | """FilterPipes Library.
18 |
19 | This module abstracts many of the details for dealing with the
20 | SublimeText API. Several generic commands are provided, which can
21 | be further customized for additional behavior.
22 |
23 | Part of the FilterPipes SublimeText Plugin.
24 | github.com/tylerl/FilterPipes
25 |
26 | """
27 |
28 | __author__ = 'Tyler Larson [github.com/tylerl/]'
29 | __version__ = '1.1.0'
30 | __license__ = 'Apache 2'
31 | __copyright__ = 'Copyright 2015, Google Inc.'
32 |
33 | import subprocess
34 | import sublime
35 | import sublime_plugin
36 | import sys
37 | import re
38 |
39 | ###############################################################
40 | # Python/Sublime version compatibility
41 | if sys.version_info[0] == 2: # Python 2.x specific (ST2)
42 | PYTHON2=True
43 | def is_str(obj):
44 | return isinstance(obj, basestring)
45 | else: # Python 3.x
46 | PYTHON2=False
47 | def is_str(obj): # Python 3.x specific (ST3)
48 | return isinstance(obj, str)
49 | ###############################################################
50 |
51 |
52 | class FilterPipesCommandBase(sublime_plugin.TextCommand):
53 | """Generic base for function-based filter commands.
54 |
55 | This class is not used directly, but rather inherited to build
56 | a filter plugin that actually does something. Selection and
57 | text replacement logic are handled by this class.
58 |
59 | Override filter(self, data) to perform text filter operation.
60 | """
61 | use_selections = True
62 | errors_on_statusbar = True
63 | report_success = True
64 | report_failure = True
65 | report_nochange = True
66 |
67 | def filter(self, data):
68 | """Perform transformation on document text.
69 |
70 | Args:
71 | data: string containing selected text.
72 |
73 | Returns:
74 | string containing the desired replacement, or None to
75 | indicate no operation.
76 |
77 | """
78 | return None
79 |
80 | def success_message(self):
81 | """Message to display on status bar if success."""
82 | return 'FilterPipes: success'
83 |
84 | def failure_message(self):
85 | """Message to display on status bar if unsuccessful."""
86 | return 'FilterPipes: command failed'
87 |
88 | def nochange_message(self):
89 | """Message to display on status bar if replacement matches original."""
90 | return 'FilterPipes: No change'
91 |
92 | def do_replacements(self, edit):
93 | self.success = False
94 | self.replaced = False
95 | replacements = []
96 | for r in self._regions():
97 | replacement = self._get_replacement(r)
98 | if replacement is None:
99 | continue
100 | if replacement:
101 | replacements.append(replacement)
102 | # replace in reverse order to avoid overlap complications
103 | for replacement in reversed(replacements):
104 | self._commit_replacement(edit, replacement)
105 | msg = None
106 | if not self.success:
107 | if self.report_failure:
108 | msg = self.failure_message()
109 | elif not self.replaced:
110 | if self.report_nochange:
111 | msg = self.nochange_message()
112 | else:
113 | if self.report_success:
114 | msg = self.success_message()
115 | if msg:
116 | sublime.status_message(msg)
117 |
118 | def post_init(self):
119 | """Hook for doing some post-init reconfiguration.
120 |
121 | See the IntToInt filter for an example of how this
122 | can be useful.
123 | """
124 | pass
125 |
126 | def apply_settings(self, settings):
127 | for k, v in settings.items():
128 | setattr(self, k, v)
129 |
130 | def run(self, edit, **settings):
131 | try:
132 | self.apply_settings(settings)
133 | self.post_init()
134 | self.do_replacements(edit)
135 | except Exception as ex:
136 | if self.errors_on_statusbar:
137 | sublime.status_message(str(ex))
138 | raise
139 |
140 | def _get_replacement(self, region):
141 | existing = self.view.substr(region)
142 | filtered = self.filter(existing)
143 | if filtered is None:
144 | return None
145 | self.success = True
146 | if filtered == existing:
147 | return None
148 | self.replaced = True
149 | return (region, filtered)
150 |
151 | def _commit_replacement(self, edit, replacement):
152 | region, text = replacement
153 | self.view.replace(edit, region, text)
154 |
155 | def _regions(self):
156 | regions = None
157 | if self.use_selections:
158 | regions = [r for r in self.view.sel() if not r.empty()]
159 | if not regions:
160 | regions = [sublime.Region(0, self.view.size())]
161 | return regions
162 |
163 |
164 | class FilterPipesProcessCommand(FilterPipesCommandBase):
165 | """Generic base for Process-based filter commands.
166 |
167 | Override self.command or self.getcommand() to specify which command to run,
168 | or pass in at run time as a configuration parameter. Set "shell" to true to
169 | execute as a shell command instead of direct process invocation.
170 | """
171 | command = []
172 | use_selections = True
173 | shell = False
174 | report_failure = False # we do our own failure reporting
175 | expected_returns = [0]
176 | subprocess_args = {}
177 |
178 | def _execute_raw(self, command, text):
179 | """Executes a command and returns stdout, stderr, and return code."""
180 | args = self.subprocess_args or {}
181 | args['shell'] = self.shell
182 | cmd = subprocess.Popen(command,
183 | stdin=subprocess.PIPE,
184 | stdout=subprocess.PIPE,
185 | stderr=subprocess.PIPE, **args)
186 | (stdout, stderr) = cmd.communicate(text.encode('UTF-8'))
187 | return (stdout, stderr, cmd.returncode)
188 |
189 | def _expect_success(self, command, text):
190 | try:
191 | stdout, stderr, status = self._execute_raw(command, text)
192 | if not self.expected_returns or status in self.expected_returns:
193 | return stdout.decode('UTF-8')
194 | except OSError as e:
195 | stdout, stderr, status = (None, str(e), e.errno)
196 |
197 | if self.errors_on_statusbar:
198 | sublime.status_message(
199 | 'Error %i executing command [%s]: %s' %
200 | (status, self.get_command_as_str(), stderr))
201 | print(
202 | 'Error %i executing command [%s]:\n%s\n' %
203 | (status, self.get_command_as_str(False), stderr))
204 | return None
205 |
206 | def filter(self, existing):
207 | return self._expect_success(self.get_command(), existing)
208 |
209 | def get_command(self):
210 | return self.command
211 |
212 | def get_command_as_str(self, short=True):
213 | c = self.get_command()
214 | if is_str(c):
215 | return c
216 | if short:
217 | return c[0]
218 | return ' '.join(c)
219 |
220 | def success_message(self):
221 | return 'Filtered through: %s' % (self.get_command_as_str())
222 |
223 |
224 | class FilterPipesTranslateCommand(FilterPipesCommandBase):
225 | """Translates characters from one set to another.
226 |
227 | Like the tr shell command.
228 |
229 | """
230 | before = None
231 | after = None
232 |
233 | def filter(self, data):
234 | if not self.before or not self.after:
235 | return None
236 | if PYTHON2:
237 | trans = dict(zip([ord(c) for c in self.before], self.after))
238 | else:
239 | trans = str.maketrans(self.before,self.after)
240 | return data.translate(trans)
241 |
242 |
243 | class FilterPipesRegexCommand(FilterPipesCommandBase):
244 | """Performs a regular expression replacement.
245 |
246 | Because re.sub is magic, replacement can be either a string or a
247 | function that takes a match object.
248 |
249 | """
250 | regex = None
251 | replacement = None
252 | flags = 0
253 | count = 0
254 | lines = False
255 |
256 | def filter(self, data):
257 | if self.regex is None or self.replacement is None:
258 | return None
259 | if self.lines:
260 | self.flags |= re.MULTILINE
261 | return re.sub(self.regex, self.replacement, data,
262 | count=self.count, flags=self.flags)
263 |
264 |
265 | class FilterPipesExecPromptCommand(sublime_plugin.TextCommand):
266 | """Prompt for a command to filter text through."""
267 |
268 | def run(self, edit):
269 | self.view.window().show_input_panel(
270 | 'Filter Command:', '', self.on_done, None, None)
271 |
272 | def on_done(self, text):
273 | self.view.run_command(
274 | 'filter_pipes_process', {'command': text, 'shell': True})
275 |
--------------------------------------------------------------------------------
/filters.py:
--------------------------------------------------------------------------------
1 | # Copyright 2015 Google Inc. All Rights Reserved.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | # This is not an official Google product.
16 |
17 | """Included filters for FilterPipes.
18 |
19 | These filters build upon the classes from the filterpipes base
20 | library to provide a useful set of features for everyday use.
21 |
22 | Part of the FilterPipes SublimeText Plugin.
23 | github.com/tylerl/FilterPipes
24 |
25 | """
26 |
27 | import sys
28 | if sys.version_info[0] == 3: # Python 3; ST 3
29 | from FilterPipes import filterpipes # ST3-style import
30 | from urllib.parse import quote, unquote
31 | else:
32 | import filterpipes # ST2-style import
33 | from urllib import quote, unquote
34 |
35 | import base64
36 |
37 |
38 | class FilterPipesBase64Command(filterpipes.FilterPipesCommandBase):
39 | decode = False
40 | wrap = 64
41 | urlsafe = False
42 |
43 | def filter(self, text):
44 | b64e = base64.base64encode
45 | b64d = base64.base64decode
46 | if self.urlsafe:
47 | b64e = base64.urlsafe_base64encode
48 | b64d = base64.urlsafe_base64decode
49 | if self.decode:
50 | return b64d(text.encode('UTF-8')).decode('UTF-8')
51 | encoded = b64e(text.encode('UTF-8')).decode('UTF-8')
52 | if self.wrap:
53 | return '\n'.join(
54 | (encoded[i:i + self.wrap] for i in
55 | range(0, len(encoded), self.wrap)))
56 | return encoded
57 |
58 |
59 | class FilterPipesUrlencodeCommand(filterpipes.FilterPipesCommandBase):
60 | decode = False
61 |
62 | def filter(self, text):
63 | if self.decode:
64 | return unquote(text)
65 | else:
66 | return quote(text)
67 |
68 |
69 | class FilterPipesEscapeCommand(filterpipes.FilterPipesCommandBase):
70 | decode = False
71 |
72 | def filter(self, data):
73 | if self.decode:
74 | return data.encode("UTF-8").decode('unicode-escape')
75 | else:
76 | return data.encode('unicode-escape').decode("UTF-8")
77 |
78 |
79 | class FilterPipesIntToIntCommand(filterpipes.FilterPipesRegexCommand):
80 | """Converts integer strings between common bases.
81 |
82 | Included as a demonstration of more complex uses of regex matching,
83 | including configuration using the post_init callback as well as
84 | replacement using a function instead of static text.
85 | """
86 | BASE_REGEX = {
87 | 10: r"\b([0-9]+)\b",
88 | 16: r"\b((?:0[Xx])?[0-9a-fA-F]+)\b",
89 | 8: r"\b([0-7]+)\b"
90 | }
91 | BASE_FMT = {8: "%o", 10: "%i", 16: "%x"}
92 | PREFIX = {8: "0", 10: "", 16: "0x"}
93 | regex = None # set by post_init
94 | from_base = 10
95 | to_base = 16
96 | case = "lower"
97 | output_prefix = True
98 |
99 | def post_init(self):
100 | self.from_base = int(self.from_base)
101 | self.to_base = int(self.to_base)
102 | self.regex = self.BASE_REGEX[self.from_base]
103 | self.output_fmt = self.BASE_FMT[self.to_base]
104 | if self.output_prefix:
105 | self.output_fmt = self.PREFIX[self.to_base] + self.output_fmt
106 | if self.case == "upper":
107 | self.output_fmt = self.output_fmt.upper()
108 |
109 | def replacement(self, match):
110 | txt = match.group(1)
111 | try:
112 | val = int(txt, self.from_base)
113 | return self.output_fmt % (val)
114 | except ValueError:
115 | pass
116 | return match.group(0)
117 |
--------------------------------------------------------------------------------