syntax_tree-xml 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 77ac1585f5010fa007a87ee7565667e8e68e5a3842aea6baadcf3af668fb5e8a
4
+ data.tar.gz: c37f1aedf0172034ed2074c63918b04a86321f035ca8b87a34fd7a9f37e3f34e
5
+ SHA512:
6
+ metadata.gz: '0167288cb4ed1856e5230478f5a45b5a2e80d93be75d3d88cc087402c8c0a30d4d751218e04d84ef416724ca0ac6f9fe063da66e83f56467e9a976f9a7d54959'
7
+ data.tar.gz: 9efacf8b18a2962a03a36bb5e72f0871d791b15e58d2f627229dced070a5d42f8d717272d395e2794d78ca72c8caa508f356e03e113a2ee86c109c3212f03df0
@@ -0,0 +1,6 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "bundler"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "daily"
@@ -0,0 +1,34 @@
1
+ name: Main
2
+ on:
3
+ - push
4
+ - pull_request_target
5
+ jobs:
6
+ ci:
7
+ name: CI
8
+ runs-on: ubuntu-latest
9
+ env:
10
+ CI: true
11
+ steps:
12
+ - uses: actions/checkout@master
13
+ - uses: ruby/setup-ruby@v1
14
+ with:
15
+ bundler-cache: true
16
+ ruby-version: '3.1'
17
+ - name: Test
18
+ run: |
19
+ bundle exec rake test
20
+ bundle exec rake stree:check
21
+ automerge:
22
+ name: AutoMerge
23
+ needs: ci
24
+ runs-on: ubuntu-latest
25
+ if: github.event_name == 'pull_request_target' && github.actor == 'dependabot[bot]'
26
+ steps:
27
+ - uses: actions/github-script@v3
28
+ with:
29
+ script: |
30
+ github.pulls.merge({
31
+ owner: context.payload.repository.owner.login,
32
+ repo: context.payload.repository.name,
33
+ pull_number: context.payload.pull_request.number
34
+ })
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ test.xml
data/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2022-05-25
10
+
11
+ ### Added
12
+
13
+ - 🎉 Initial release! 🎉
14
+
15
+ [unreleased]: https://github.com/ruby-syntax-tree/syntax_tree-xml/compare/v0.1.0...HEAD
16
+ [0.1.0]: https://github.com/ruby-syntax-tree/syntax_tree-xml/compare/c34baa...v0.1.0
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,36 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ syntax_tree-xml (0.1.0)
5
+ prettier_print
6
+ syntax_tree (>= 2.0.1)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ docile (1.4.0)
12
+ minitest (5.15.0)
13
+ prettier_print (0.1.0)
14
+ rake (13.0.6)
15
+ simplecov (0.21.2)
16
+ docile (~> 1.1)
17
+ simplecov-html (~> 0.11)
18
+ simplecov_json_formatter (~> 0.1)
19
+ simplecov-html (0.12.3)
20
+ simplecov_json_formatter (0.1.4)
21
+ syntax_tree (2.7.1)
22
+ prettier_print
23
+
24
+ PLATFORMS
25
+ x86_64-darwin-21
26
+ x86_64-linux
27
+
28
+ DEPENDENCIES
29
+ bundler
30
+ minitest
31
+ rake
32
+ simplecov
33
+ syntax_tree-xml!
34
+
35
+ BUNDLED WITH
36
+ 2.3.6
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022-present Kevin Newton
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # SyntaxTree::XML
2
+
3
+ [![Build Status](https://github.com/ruby-syntax-tree/syntax_tree-xml/actions/workflows/main.yml/badge.svg)](https://github.com/ruby-syntax-tree/syntax_tree-xml/actions/workflows/main.yml)
4
+ [![Gem Version](https://img.shields.io/gem/v/syntax_tree-xml.svg)](https://rubygems.org/gems/syntax_tree-xml)
5
+
6
+ [Syntax Tree](https://github.com/ruby-syntax-tree/syntax_tree) support for XML.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem "syntax_tree-xml"
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle install
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install syntax_tree-xml
23
+
24
+ ## Usage
25
+
26
+ From code:
27
+
28
+ ```ruby
29
+ require "syntax_tree/xml"
30
+
31
+ pp SyntaxTree::XML.parse(source) # print out the AST
32
+ puts SyntaxTree::XML.format(source) # format the AST
33
+ ```
34
+
35
+ From the CLI:
36
+
37
+ ```sh
38
+ $ stree ast --plugins=xml file.xml
39
+ (document
40
+ (misc "\n"),
41
+ (element
42
+ (opening_tag "<", "message", ">"),
43
+ (char_data "\n" + " "),
44
+ (element (opening_tag "<", "hello", ">"), (char_data "Hello"), (closing_tag "</", "hello", ">")),
45
+ (char_data "\n" + " "),
46
+ (element (opening_tag "<", "world", ">"), (char_data "World"), (closing_tag "</", "world", ">")),
47
+ (char_data "\n"),
48
+ (closing_tag "</", "message", ">")
49
+ )
50
+ )
51
+ ```
52
+
53
+ or
54
+
55
+ ```sh
56
+ $ stree format --plugins=xml file.xml
57
+ <message>
58
+ <hello>Hello</hello>
59
+ <world>World</world>
60
+ </message>
61
+ ```
62
+
63
+ or
64
+
65
+ ```sh
66
+ $ stree write --plugins=xml file.xml
67
+ file.xml 1ms
68
+ ```
69
+
70
+ ## Contributing
71
+
72
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby-syntax-tree/syntax_tree-xml.
73
+
74
+ ## License
75
+
76
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+ require "syntax_tree/rake_tasks"
6
+
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.libs << "test"
9
+ t.libs << "lib"
10
+ t.test_files = FileList["test/**/*_test.rb"]
11
+ end
12
+
13
+ SyntaxTree::Rake::CheckTask.new
14
+ SyntaxTree::Rake::WriteTask.new
15
+
16
+ task default: :test
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ module XML
5
+ class Format < Visitor
6
+ attr_reader :q
7
+
8
+ def initialize(q)
9
+ @q = q
10
+ end
11
+
12
+ # Visit a Token node.
13
+ def visit_token(node)
14
+ q.text(node.value.strip)
15
+ end
16
+
17
+ # Visit a Document node.
18
+ def visit_document(node)
19
+ child_nodes =
20
+ node
21
+ .child_nodes
22
+ .select do |child_node|
23
+ case child_node
24
+ in Misc[value: Token[type: :whitespace]]
25
+ false
26
+ else
27
+ true
28
+ end
29
+ end
30
+ .sort_by(&:location)
31
+
32
+ q.seplist(child_nodes, -> { q.breakable(force: true) }) do |child_node|
33
+ visit(child_node)
34
+ end
35
+
36
+ q.breakable(force: true)
37
+ end
38
+
39
+ # Visit a Prolog node.
40
+ def visit_prolog(node)
41
+ q.group do
42
+ visit(node.opening)
43
+
44
+ if node.attributes.any?
45
+ q.indent do
46
+ q.breakable
47
+ q.seplist(node.attributes, -> { q.breakable }) do |child_node|
48
+ visit(child_node)
49
+ end
50
+ end
51
+ end
52
+
53
+ q.breakable("")
54
+ visit(node.closing)
55
+ end
56
+ end
57
+
58
+ # Visit a Doctype node.
59
+ def visit_doctype(node)
60
+ q.group do
61
+ visit(node.opening)
62
+ q.text(" ")
63
+ visit(node.name)
64
+
65
+ if node.external_id
66
+ q.text(" ")
67
+ visit(node.external_id)
68
+ end
69
+
70
+ visit(node.closing)
71
+ end
72
+ end
73
+
74
+ # Visit an ExternalID node.
75
+ def visit_external_id(node)
76
+ q.group do
77
+ q.group do
78
+ visit(node.type)
79
+
80
+ if node.public_id
81
+ q.indent do
82
+ q.breakable
83
+ visit(node.public_id)
84
+ end
85
+ end
86
+ end
87
+
88
+ q.indent do
89
+ q.breakable
90
+ visit(node.system_id)
91
+ end
92
+ end
93
+ end
94
+
95
+ # Visit an Element node.
96
+ def visit_element(node)
97
+ inner_nodes =
98
+ node.content&.select do |child_node|
99
+ case child_node
100
+ in CharData[value: Token[type: :whitespace]]
101
+ false
102
+ in CharData[value: Token[value:]] if value.strip.empty?
103
+ false
104
+ else
105
+ true
106
+ end
107
+ end
108
+
109
+ case inner_nodes
110
+ in nil
111
+ visit(node.opening_tag)
112
+ in []
113
+ visit(
114
+ Element::OpeningTag.new(
115
+ opening: node.opening_tag.opening,
116
+ name: node.opening_tag.name,
117
+ attributes: node.opening_tag.attributes,
118
+ closing:
119
+ Token.new(
120
+ type: :close,
121
+ value: "/>",
122
+ location: node.opening_tag.closing.location
123
+ ),
124
+ location: node.opening_tag.location
125
+ )
126
+ )
127
+ in [CharData[value: Token[type: :text, value:]]]
128
+ q.group do
129
+ visit(node.opening_tag)
130
+ q.indent do
131
+ q.breakable("")
132
+ format_text(q, value)
133
+ end
134
+
135
+ q.breakable("")
136
+ visit(node.closing_tag)
137
+ end
138
+ else
139
+ q.group do
140
+ visit(node.opening_tag)
141
+ q.indent do
142
+ q.breakable("")
143
+
144
+ inner_nodes.each_with_index do |child_node, index|
145
+ if index != 0
146
+ q.breakable(force: true)
147
+
148
+ end_line = inner_nodes[index - 1].location.end_line
149
+ start_line = child_node.location.start_line
150
+ q.breakable(force: true) if (start_line - end_line) >= 2
151
+ end
152
+
153
+ case child_node
154
+ in CharData[value: Token[type: :text, value:]]
155
+ format_text(q, value)
156
+ else
157
+ visit(child_node)
158
+ end
159
+ end
160
+ end
161
+
162
+ q.breakable(force: true)
163
+ visit(node.closing_tag)
164
+ end
165
+ end
166
+ end
167
+
168
+ # Visit an Element::OpeningTag node.
169
+ def visit_opening_tag(node)
170
+ q.group do
171
+ visit(node.opening)
172
+ visit(node.name)
173
+
174
+ if node.attributes.any?
175
+ q.indent do
176
+ q.breakable
177
+ q.seplist(node.attributes, -> { q.breakable }) do |child_node|
178
+ visit(child_node)
179
+ end
180
+ end
181
+ end
182
+
183
+ q.breakable(node.closing.value == "/>" ? " " : "")
184
+ visit(node.closing)
185
+ end
186
+ end
187
+
188
+ # Visit an Element::ClosingTag node.
189
+ def visit_closing_tag(node)
190
+ q.group do
191
+ visit(node.opening)
192
+ visit(node.name)
193
+ visit(node.closing)
194
+ end
195
+ end
196
+
197
+ # Visit a Reference node.
198
+ def visit_reference(node)
199
+ visit(node.value)
200
+ end
201
+
202
+ # Visit an Attribute node.
203
+ def visit_attribute(node)
204
+ q.group do
205
+ visit(node.key)
206
+ visit(node.equals)
207
+ visit(node.value)
208
+ end
209
+ end
210
+
211
+ # Visit a CharData node.
212
+ def visit_char_data(node)
213
+ lines = node.value.value.strip.split("\n")
214
+
215
+ q.seplist(lines, -> { q.breakable(indent: false) }) do |line|
216
+ q.text(line)
217
+ end
218
+ end
219
+
220
+ # Visit a Misc node.
221
+ def visit_misc(node)
222
+ visit(node.value)
223
+ end
224
+
225
+ private
226
+
227
+ # Format a text by splitting nicely at newlines and spaces.
228
+ def format_text(q, value)
229
+ sep_line = -> { q.breakable(force: true, indent: false) }
230
+ sep_word = -> { q.group { q.breakable } }
231
+
232
+ q.seplist(value.strip.split("\n"), sep_line) do |line|
233
+ q.seplist(line.split(/\b(?: +)\b/), sep_word) { |word| q.text(word) }
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end