ucd 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7949eed66b84881ded30a6e6ab40e9413c450dec
4
+ data.tar.gz: 68661990858b2a975ed47d2fffa008746c0e6b05
5
+ SHA512:
6
+ metadata.gz: c11ee5b2f389e3cc2c425154c9ff7b259fe8e8257eeab64c75603975997733219602be796401a78782ee6daeca2d8eb6903fdda48940068833a1b1acf4679fbf
7
+ data.tar.gz: 3aa9ad7cd7da7f3bf66450488b1ac795600a33e884626c820a1230d94a3e24b42b744b731c5c3008446c282342f9521b4db5365e2301eaa1b5e672800a41e47e
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.0
5
+ before_install: gem install bundler -v 1.13.6
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at ryan@rynet.us. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ucd.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Ryan Lewis
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,61 @@
1
+ # UCD
2
+
3
+ UML Class Diagram (UCD) is a language for specifying UML class diagrams and a tool for converting it into various
4
+ different formats.
5
+
6
+ ## Install
7
+
8
+ ### Bundler: `gem "ucd"`
9
+
10
+ ### RubyGems: `gem install ucd`
11
+
12
+ ## Executable
13
+
14
+ ```sh
15
+ # Convert composite.ucd to composite.png
16
+ $ ucd --type png --output . composite.ucd
17
+ ```
18
+
19
+ ## Language
20
+
21
+ ### Examples
22
+
23
+ See the examples directory for UCD files and their generated DOT and PNG files.
24
+
25
+ ### Syntax
26
+
27
+ > Note: Syntax is represented in [ABNF](https://tools.ietf.org/html/rfc5234) and are omitting spaces for clarity
28
+
29
+ UCD language simply defines one or more `class` definitions, inside of which the fields, methods, relationships, and
30
+ class relationships are defined.
31
+
32
+ ```abnf
33
+ NAME = 1*( ALPHA / DIGIT / "-" / "_" )
34
+ TYPE = NAME
35
+ CLASS_BODY = *( ( FIELD / METHOD / RELATIONSHIP / CLASS_RELATIONSHIP ) NEWLINE )
36
+ ACCESS = ( "public" / "protected" / "private")
37
+ ARGUMENT = NAME [ ":" TYPE ]
38
+ ARGUMENTS = ARGUMENT *( "," ARGUMENT )
39
+ FROM = 1*( ALPHA / DIGIT / "-" / "_" / "*" )
40
+ TO = FROM
41
+
42
+ CLASS = [ "abstract" / "interface" ] "class" NAME [ "{" CLASS_BODY "}" ]
43
+ FIELD = [ "static" ] [ ACCESS ] field NAME [ ":" TYPE ]
44
+ METHOD = [ "abstract" ] [ "static" ] [ ACCESS ] method NAME [ "(" [ ARGUMENTS ] ")"] [ ":" TYPE ]
45
+ RELATIONSHIP = [ "generalizes" / "realizes" ] NAME
46
+ CLASS_RELATIONSHIP = [ "directional" / "bidirectional" ] ( "dependency" / "association" / "aggregation" / "composition" ) NAME [ FROM ] [ TO ]
47
+ ```
48
+
49
+ ## Development
50
+
51
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
52
+
53
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
54
+
55
+ ## Contributing
56
+
57
+ Bug reports and pull requests are welcome on GitHub at https://github.com/RyanScottLewis/ucd. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
58
+
59
+ ## License
60
+
61
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core/rake_task"
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ require 'rake/version_task'
7
+ Rake::VersionTask.new
8
+
9
+ task :default => :spec
data/TODO.md ADDED
@@ -0,0 +1,4 @@
1
+ # TODO
2
+
3
+ * Atom, VIM, etc color schemes/snippets
4
+ * Use https://github.com/glejeune/Ruby-Graphviz
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ucd"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,17 @@
1
+ abstract class Component {
2
+ method operation
3
+ }
4
+
5
+ class Leaf {
6
+ generalizes Component
7
+ }
8
+
9
+ class Composite {
10
+ protected field children
11
+ method addChild(child)
12
+ method removeChild(child)
13
+ method getChild(index)
14
+
15
+ generalizes Component
16
+ aggregation Component
17
+ }
Binary file
@@ -0,0 +1,17 @@
1
+ abstract class Component {
2
+ method operation
3
+ }
4
+
5
+ class Leaf {
6
+ generalizes Component
7
+ }
8
+
9
+ class Composite {
10
+ protected field children : Component
11
+ method addChild(child : Component) : Component
12
+ method removeChild(child : Component) : Component
13
+ method getChild(index : Integer) : Component
14
+
15
+ generalizes Component
16
+ aggregation Component *
17
+ }
data/exe/ucd ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby -W0
2
+
3
+ require "ucd/interface/command_line"
4
+
5
+ UCD::Interface::CommandLine.run
@@ -0,0 +1,67 @@
1
+ require "ucd/node"
2
+ require "ucd/formatter"
3
+ require "ucd/has_attributes"
4
+
5
+ module UCD
6
+ module Formatter
7
+ class Base
8
+
9
+ class << self
10
+
11
+ def inherited(subclass)
12
+ Formatter.all << subclass
13
+ end
14
+
15
+ def format(node, attributes={})
16
+ new(attributes).format(node)
17
+ end
18
+
19
+ def name
20
+ to_s.split("::").last.downcase.to_sym
21
+ end
22
+
23
+ end
24
+
25
+ include HasAttributes
26
+
27
+ def initialize(attributes={})
28
+ update_attributes(attributes)
29
+ end
30
+
31
+ def name
32
+ self.class.name
33
+ end
34
+
35
+ attr_reader :output_path
36
+
37
+ def output_path=(value)
38
+ @output_path = value.is_a?(Pathname) ? value : Pathname.new(value.to_s) unless value.nil?
39
+ end
40
+
41
+ attr_reader :type
42
+
43
+ def type=(value)
44
+ @type = value.to_s.strip.downcase.to_sym
45
+ end
46
+
47
+ def format(node)
48
+ case node
49
+ when Node::Field then format_field(node)
50
+ when Node::Method then format_method(node)
51
+ when Node::Relationship then format_relationship(node)
52
+ when Node::ClassRelationship then format_class_relationship(node)
53
+ when Node::ClassNode then format_class(node)
54
+ when Node::Document then format_document(node)
55
+ end
56
+ end
57
+
58
+ def format_field(node); raise NotImplementedError; end
59
+ def format_method(node); raise NotImplementedError; end
60
+ def format_relationship(node); raise NotImplementedError; end
61
+ def format_class_relationship(node); raise NotImplementedError; end
62
+ def format_class(node); raise NotImplementedError; end
63
+ def format_document(node); raise NotImplementedError; end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,196 @@
1
+ require "open3"
2
+ require "ucd/formatter/base"
3
+
4
+ module UCD
5
+ module Formatter
6
+ class Graphviz < Base
7
+
8
+ class Attributes < Hash
9
+ def to_s
10
+ to_a.map { |(a, b)| "#{a}=#{b.inspect}" }.join(" ")
11
+ end
12
+ end
13
+
14
+ ACCESS_SYMBOLS = {
15
+ "public" => "+",
16
+ "protected" => "#",
17
+ "private" => "-",
18
+ }
19
+
20
+ VALID_TYPES = %i[dot xdot ps pdf svg svgz fig png gif jpg jpeg json imap cmapx]
21
+
22
+ def initialize(attributes={})
23
+ super
24
+
25
+ @graph_attributes = Attributes.new
26
+ @graph_attributes["splines"] = "ortho"
27
+ @graph_attributes["rankdir"] = "BT"
28
+
29
+ @edge_attributes = Attributes.new
30
+ @edge_attributes["color"] = "gray50"
31
+
32
+ @node_attributes = Attributes.new
33
+ @node_attributes["shape"] = "plain"
34
+ end
35
+
36
+ attr_reader :graph_attributes
37
+ attr_reader :edge_attributes
38
+ attr_reader :node_attributes
39
+
40
+ def type=(value)
41
+ super
42
+
43
+ @type = VALID_TYPES.include?(@type) ? @type : nil
44
+ end
45
+
46
+ def format(node)
47
+ dot = super.lines.map(&:rstrip).join("\n")
48
+ data = generate_from_dot(dot)
49
+
50
+ if @output_path.nil?
51
+ puts data
52
+ else
53
+ self.type ||= @output_path.extname.gsub(/^\./, "")
54
+ output_path = @output_path.extname.empty? ? Pathname.new("#{@output_path}.#{@type}") : @output_path
55
+
56
+ output_path.open("w+") { |file| file.write(data) }
57
+ end
58
+ end
59
+
60
+ def format_field(node)
61
+ symbol = ACCESS_SYMBOLS[node.access]
62
+ result = "#{symbol} #{node.name}"
63
+ result << " : #{node.type}" if node.type
64
+ result = "<U>#{result}</U>" if node.static
65
+
66
+ result
67
+ end
68
+
69
+ def format_method(node)
70
+ symbol = ACCESS_SYMBOLS[node.access]
71
+ result = "#{symbol} #{node.name}"
72
+ arguments = node.arguments.map do |argument|
73
+ "#{argument.name}#{" : #{argument.type}" if argument.type}"
74
+ end.join(", ") if node.arguments
75
+
76
+ result << "(#{arguments})"
77
+ result << " : #{node.type}" if node.type
78
+ result = "<U>#{result}</U>" if node.static
79
+ result = "<I>#{result}</I>" if node.abstract
80
+
81
+ result
82
+ end
83
+
84
+ def format_relationship(node)
85
+ arrow = "diamond" if "composition"
86
+ arrow = "odiamond" if "aggregation"
87
+ dir = "back" if %w[aggregation composition].include?(node.type)
88
+ arrow_key = dir == "back" ? "arrowtail" : "arrowhead"
89
+
90
+ attributes = Attributes.new
91
+
92
+ attributes["style"] = "dashed" if node.type == "dependency"
93
+ attributes["dir"] = dir if dir
94
+ attributes[arrow_key] = arrow
95
+ attributes["headlabel"] = node.from if node.from
96
+ attributes["taillabel"] = node.to if node.to
97
+
98
+ %Q{Class#{node.parent.name} -> Class#{node.name} [#{attributes}]}
99
+ end
100
+
101
+ def format_class_relationship(node)
102
+ attributes = Attributes.new
103
+
104
+ attributes["arrowhead"] = "onormal"
105
+ attributes["style"] = "dashed" if node.type == "realizes"
106
+
107
+ %Q{Class#{node.parent.name} -> Class#{node.name} [#{attributes}]}
108
+ end
109
+
110
+ def format_class(node)
111
+ name = "<B>#{node.name}</B>"
112
+ name = "«abstract»<BR/><I>#{name}</I>" if node.modifier == "abstract"
113
+
114
+ unless node.fields.empty?
115
+ field_rows = node.fields.map { |field| %Q{<TR><TD ALIGN="LEFT">#{format_field(field)}</TD></TR>}}
116
+ field_table = <<-HEREDOC.chomp
117
+
118
+ <TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0">
119
+ #{field_rows.map { |row| " " * 10 + row }.join("\n")}
120
+ </TABLE>
121
+ HEREDOC
122
+ field_table << "\n" << " " * 6
123
+ end
124
+
125
+ unless node.methods.empty?
126
+ method_rows = node.methods.map { |method| %Q{<TR><TD ALIGN="LEFT">#{format_method(method)}</TD></TR>}}
127
+ method_table = <<HEREDOC.chomp
128
+
129
+ <TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0">
130
+ #{method_rows.map { |row| " " * 10 + row }.join("\n")}
131
+ </TABLE>
132
+ HEREDOC
133
+ method_table << "\n" << " " * 6
134
+ end
135
+
136
+ <<-HEREDOC.chomp
137
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
138
+ <TR>
139
+ <TD>#{name}</TD>
140
+ </TR>
141
+ <TR>
142
+ <TD>#{field_table}</TD>
143
+ </TR>
144
+ <TR>
145
+ <TD>#{method_table}</TD>
146
+ </TR>
147
+ </TABLE>
148
+ HEREDOC
149
+ end
150
+
151
+ def format_document(node)
152
+ classes = node.classes.map do |node|
153
+ <<-HEREDOC
154
+ Class#{node.name} [label=<
155
+ #{format_class(node)}
156
+ >]
157
+ HEREDOC
158
+ end.join("\n")
159
+ class_relationships = node.classes.map(&:class_relationships).flatten.map { |node| format_class_relationship(node) }.join("\n")
160
+ relationships = node.classes.map(&:relationships).flatten.map { |node| format_relationship(node) }.join("\n")
161
+
162
+ classes = classes.lines.map { |line| " #{line}" }.join.chomp
163
+ class_relationships = class_relationships.lines.map { |line| " #{line}" }.join.chomp
164
+ relationships = relationships.lines.map { |line| " #{line}" }.join.chomp
165
+
166
+ <<-HEREDOC
167
+ digraph G {
168
+ graph [#{@graph_attributes}]
169
+ edge [#{@edge_attributes}]
170
+ node [#{@node_attributes}]
171
+
172
+ #{classes}
173
+
174
+ #{class_relationships}
175
+
176
+ #{relationships}
177
+ }
178
+ HEREDOC
179
+ end
180
+
181
+ protected
182
+
183
+ def generate_from_dot(dot)
184
+ return dot if @type.nil?
185
+
186
+ Open3.popen3("dot -T#{type}") do |stdin, stdout, stderr, wait|
187
+ stdin.puts(dot)
188
+ stdin.close
189
+ # unless (err = stderr.read).empty? then raise err end
190
+ stdout.read
191
+ end
192
+ end
193
+
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,19 @@
1
+ module UCD
2
+ module Formatter
3
+ class << self
4
+
5
+ def all
6
+ @all ||= []
7
+ end
8
+
9
+ def find_by_name(name)
10
+ name = name.to_sym
11
+
12
+ all.find { |formatter_class| formatter_class.name == name }
13
+ end
14
+
15
+ end
16
+ end
17
+ end
18
+
19
+ require "ucd/formatter/graphviz"
@@ -0,0 +1,9 @@
1
+ module UCD
2
+ module HasAttributes
3
+
4
+ def update_attributes(attributes={})
5
+ attributes.to_h.each { |name, value| send("#{name}=", value) }
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ require "optparse"
2
+ require "ucd/has_attributes"
3
+
4
+ module UCD
5
+ module Interface
6
+ class Base
7
+
8
+ def self.run(attributes={})
9
+ new(attributes).run
10
+ end
11
+
12
+ include HasAttributes
13
+
14
+ def initialize(attributes={})
15
+ update_attributes(attributes)
16
+ end
17
+
18
+ def run
19
+ raise NotImplementedError
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,156 @@
1
+ require "optparse"
2
+ require "pathname"
3
+ require "ucd/interface/base"
4
+ require "ucd/parser"
5
+ require "ucd/formatter"
6
+
7
+ module UCD
8
+ module Interface
9
+ class CommandLine < Base
10
+
11
+ class Error < StandardError; end
12
+ class FileError < Error; end
13
+
14
+ def initialize(attributes={})
15
+ @formatter = Formatter::Graphviz.new
16
+ @verbose = false
17
+ setup_parser
18
+
19
+ super
20
+ end
21
+
22
+ def output_path=(value)
23
+ @output_path = Pathname.new(value.to_s)
24
+ end
25
+
26
+ def paths=(values)
27
+ @paths = values.to_a.map { |path| Pathname.new(path) }
28
+ end
29
+
30
+ def run
31
+ @option_parser.parse!
32
+ self.paths = ARGV
33
+
34
+ @paths.each do |input_path|
35
+ raise FileError, "File does not exist: #{input_path}" unless input_path.exist?
36
+
37
+ @formatter.output_path = @output_path.directory? ? @output_path.join(input_path.basename(".*")) : @output_path if @output_path
38
+ data = input_path.read
39
+ document = Parser.parse(data)
40
+
41
+ result = @formatter.format(document)
42
+ end
43
+ end
44
+
45
+ protected
46
+
47
+ def text_bold(body=nil)
48
+ text_effect(1, body)
49
+ end
50
+
51
+ def text_italic(body=nil)
52
+ text_effect(3, body)
53
+ end
54
+
55
+ def text_bold_italic(body=nil)
56
+ text_bold(text_italic(body))
57
+ end
58
+
59
+ def text_underline(body=nil)
60
+ text_effect(4, body)
61
+ end
62
+
63
+ def text_effect(num, body=nil)
64
+ result = "\e[#{num}m"
65
+ result << "#{body}#{text_reset}" unless body.nil?
66
+
67
+ result
68
+ end
69
+
70
+ def text_reset
71
+ "\e[0m"
72
+ end
73
+
74
+ def setup_parser
75
+ @option_parser = OptionParser.new do |parser|
76
+ parser.banner = <<~BANNER
77
+ #{text_bold("Usage:")} ucd [options] PATHS
78
+
79
+ #{text_bold("Overview:")} Generate output from UML Class Diagram language files
80
+
81
+ #{text_bold("Options:")}
82
+
83
+ BANNER
84
+
85
+ parser.on("-f", "--formatter VALUE", "The output formatter (Default: '#{@formatter.name}')") do |value|
86
+ value = value.to_s.strip.downcase.to_sym
87
+ value = Formatter.find_by_name(value)
88
+ raise Error, "Formatter not found: #{value}" if value.nil?
89
+
90
+ @formatter = value
91
+ end
92
+ parser.on("-t", "--type VALUE", "The output format type") { |value| @formatter.type = value }
93
+ parser.on("-o", "--output VALUE", "The output path") { |value| self.output_path = value }
94
+ parser.on("-h", "--help", "Prints this help") do
95
+ puts parser
96
+ puts <<~HELP
97
+
98
+ #{text_bold("Paths:")}
99
+
100
+ UCD can accept multiple paths for parsing for easier batch processing.
101
+
102
+ The location of the output by default is standard output.
103
+
104
+ The output can be directed to a path with #{text_bold_italic("--output")}, which can be a file or a directory.
105
+ If the output path is a directory, then the filename will be the same as the input filename,
106
+ with it's file extension substituted with the #{text_bold_italic("--type")}.
107
+ If the output path is a file when multiple input files are given, the current input file index is appended
108
+ to the output filename.
109
+
110
+ #{text_underline("Examples")}
111
+
112
+ `ucd project.ucd`
113
+
114
+ Produces DOT notation, sent to standard output
115
+
116
+ `ucd -o . project.ucd`
117
+
118
+ Produces DOT notation, written to #{text_italic("./project.dot")}
119
+
120
+ `ucd -o ./diagram.dot project.ucd`
121
+
122
+ Produces DOT notation, written to #{text_italic("./diagram.dot")}
123
+
124
+ `ucd -o ./diagram.png project.ucd`
125
+
126
+ Produces PNG image, written to #{text_italic("./diagram.png")}
127
+
128
+ `ucd -t png -o . project.ucd`
129
+
130
+ Produces PNG image, written to #{text_italic("./project.png")}
131
+
132
+ `ucd -t png -o . project.ucd core_ext.ucd`
133
+
134
+ Produces PNG images, written to #{text_italic("./project.png")} and #{text_italic("./core_ext.png")}
135
+
136
+ #{text_bold("Formatters:")}
137
+
138
+ #{text_underline("Graphviz")}
139
+
140
+ Generates DOT notation and can use the DOT notation to generate any format Graphviz can produce.
141
+
142
+ The output format is based on #{text_bold_italic("--type")}, which by default is "dot".
143
+ If #{text_bold_italic("--type")} is not given and #{text_bold_italic("--output")} is, the file extension of the #{text_bold_italic("--output")} path will be used.
144
+
145
+ Valid types/extensions are: #{Formatter::Graphviz::VALID_TYPES.join(", ")}
146
+
147
+ HELP
148
+
149
+ exit
150
+ end
151
+ end
152
+ end
153
+
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,17 @@
1
+ require "ucd/has_attributes"
2
+
3
+ module UCD
4
+ module Node
5
+ class Base
6
+
7
+ include HasAttributes
8
+
9
+ def initialize(attributes={})
10
+ update_attributes(attributes)
11
+ end
12
+
13
+ attr_accessor :parent
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,56 @@
1
+ require "ucd/node/base"
2
+ require "ucd/node/field"
3
+ require "ucd/node/method"
4
+ require "ucd/node/relationship"
5
+ require "ucd/node/class_relationship"
6
+ require "ucd/node/has_name"
7
+
8
+ module UCD
9
+ module Node
10
+ class ClassNode < Base
11
+
12
+ include HasName
13
+
14
+ attr_reader :modifier
15
+
16
+ def modifier=(value)
17
+ @modifier = value.to_s # TODO: Validate?
18
+ end
19
+
20
+ attr_reader :members
21
+
22
+ def members=(value)
23
+ @members = value.to_a.map do |member|
24
+ type = member.to_a[0][0] # TODO: This is dumb
25
+ attributes = member.to_a[0][1]
26
+ attributes[:parent] = self
27
+
28
+ case type
29
+ when :field then Field.new(attributes)
30
+ when :method then Method.new(attributes)
31
+ when :relationship then Relationship.new(attributes)
32
+ when :class_relationship then ClassRelationship.new(attributes)
33
+ else # TODO: Raise or something
34
+ end
35
+ end
36
+ end
37
+
38
+ def fields
39
+ @members.find_all { |member| member.class == Field }
40
+ end
41
+
42
+ def methods
43
+ @members.find_all { |member| member.class == Method }
44
+ end
45
+
46
+ def relationships
47
+ @members.find_all { |member| member.class == Relationship }
48
+ end
49
+
50
+ def class_relationships
51
+ @members.find_all { |member| member.class == ClassRelationship }
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,12 @@
1
+ require "ucd/node/relationship"
2
+ require "ucd/node/has_name"
3
+
4
+ module UCD
5
+ module Node
6
+ class ClassRelationship < Relationship
7
+
8
+ include HasName
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ require "ucd/node/base"
2
+ require "ucd/node/class_node"
3
+
4
+ module UCD
5
+ module Node
6
+ class Document < Base
7
+
8
+ attr_reader :classes
9
+
10
+ def classes=(value)
11
+ @classes = value.to_a.map { |attributes| ClassNode.new(attributes) }
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ require "ucd/node/base"
2
+ require "ucd/node/has_name"
3
+ require "ucd/node/has_type"
4
+
5
+ module UCD
6
+ module Node
7
+ class Field < Base
8
+
9
+ include HasName
10
+ include HasType
11
+
12
+ def initialize(attributes={})
13
+ @access = "public"
14
+
15
+ super
16
+ end
17
+
18
+ attr_reader :static
19
+
20
+ def static=(value)
21
+ @static = !!value
22
+ end
23
+
24
+ attr_reader :access
25
+
26
+ def access=(value)
27
+ @access = value.to_s # TODO: Validate?
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ module UCD
2
+ module Node
3
+
4
+ module HasName
5
+
6
+ attr_reader :name
7
+
8
+ def name=(value)
9
+ @name = value.to_s
10
+ end
11
+
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module UCD
2
+ module Node
3
+
4
+ module HasType
5
+
6
+ attr_reader :type
7
+
8
+ def type=(value)
9
+ @type = value.to_s
10
+ end
11
+
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ require "ucd/node/field"
2
+ require "ucd/node/method_argument"
3
+ require "ucd/node/has_name"
4
+
5
+ module UCD
6
+ module Node
7
+ class Method < Field
8
+
9
+ include HasName
10
+
11
+ attr_reader :abstract
12
+
13
+ def abstract=(value)
14
+ @abstract = !!value
15
+ end
16
+
17
+ attr_reader :arguments
18
+
19
+ def arguments=(value)
20
+ @arguments = value.to_a.map { |attributes| MethodArgument.new(attributes) }
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ require "ucd/node/base"
2
+ require "ucd/node/has_name"
3
+ require "ucd/node/has_type"
4
+
5
+ module UCD
6
+ module Node
7
+
8
+ class MethodArgument < Base
9
+
10
+ include HasName
11
+ include HasType
12
+
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ require "ucd/node/base"
2
+ require "ucd/node/has_name"
3
+ require "ucd/node/has_type"
4
+
5
+ module UCD
6
+ module Node
7
+ class Relationship < Base
8
+
9
+ include HasName
10
+ include HasType
11
+
12
+ attr_reader :from
13
+
14
+ def from=(value)
15
+ @from = value.to_s
16
+ end
17
+
18
+ attr_reader :to
19
+
20
+ def to=(value)
21
+ @to = value.to_s
22
+ end
23
+
24
+ end
25
+ end
26
+ end
data/lib/ucd/node.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "ucd/node/field"
2
+ require "ucd/node/method"
3
+ require "ucd/node/relationship"
4
+ require "ucd/node/class_relationship"
5
+ require "ucd/node/class_node"
6
+ require "ucd/node/document"
data/lib/ucd/parser.rb ADDED
@@ -0,0 +1,105 @@
1
+ require "parslet"
2
+ require "ucd/node/document"
3
+
4
+ module UCD
5
+ class Parser < Parslet::Parser
6
+
7
+ def self.parse(io, options={})
8
+ new.parse(io, options)
9
+ end
10
+
11
+ def parse(io, options={})
12
+ Node::Document.new(super)
13
+ end
14
+
15
+ KEYWORDS = %w[
16
+ class
17
+ interface
18
+ abstract static
19
+ public protected private
20
+ field method
21
+ generalizes realizes
22
+ directional bidirectional
23
+ dependency association aggregation composition
24
+ ]
25
+
26
+ KEYWORDS.each do |keyword|
27
+ rule("kw_#{keyword}") { str(keyword) }
28
+ end
29
+
30
+ rule(:newlines) { (match("\n") | str(";")).repeat(1) } # TODO: Unused
31
+ rule(:newlines?) { newlines.maybe } # TODO: Unused
32
+ rule(:spaces) { match("\s").repeat(1) }
33
+ rule(:spaces?) { spaces.maybe }
34
+ rule(:whitespace) { (match("\s") | match("\n") | str(";")).repeat(1) }
35
+ rule(:whitespace?) { whitespace.maybe }
36
+
37
+ rule(:name) { match["[:alnum:]_-"].repeat(1) } # TODO: Any other needed?
38
+
39
+ # -- Field/Method
40
+
41
+ rule(:kw_access_modifier) { kw_public | kw_protected | kw_private }
42
+
43
+ rule(:member_static) { (kw_static.as(:static) >> spaces).maybe }
44
+ rule(:member_access) { (kw_access_modifier.as(:access) >> spaces).maybe }
45
+
46
+ rule(:method_abstract) { (kw_abstract.as(:abstract) >> spaces).maybe }
47
+ rule(:member_type) { (spaces >> str(":") >> spaces >> name.as(:type)).maybe }
48
+
49
+ rule(:field_keyword) { kw_field >> spaces }
50
+ rule(:field_name) { name.as(:name) }
51
+ rule(:field_return_type) { member_type.maybe }
52
+ rule(:field_definition) { (member_static >> member_access >> field_keyword >> field_name >> field_return_type).as(:field) }
53
+
54
+ rule(:method_keyword) { kw_method >> spaces }
55
+ rule(:method_argument) { name.as(:name) >> member_type }
56
+ rule(:method_arguments_inner) { (method_argument >> (spaces? >> str(",") >> spaces? >> method_argument).repeat).repeat.as(:arguments) }
57
+ rule(:method_arguments) { (str("(") >> spaces? >> method_arguments_inner >> spaces? >> str(")")).maybe }
58
+
59
+ rule(:method_name) { name.as(:name) }
60
+ rule(:method_return_type) { member_type.maybe }
61
+ rule(:method_definition) { (method_abstract >> member_static >> member_access >> method_keyword >> method_name >> method_arguments >> method_return_type).as(:method) }
62
+
63
+ # -- Class Relationship
64
+
65
+ rule(:kw_class_relationship_type) { kw_generalizes | kw_realizes }
66
+
67
+ rule(:class_relationship_type) { kw_class_relationship_type.as(:type) >> spaces }
68
+ rule(:class_relationship_definition) { (class_relationship_type >> name.as(:name)).as(:class_relationship) }
69
+
70
+ # -- Relationship
71
+
72
+ rule(:kw_relationship_directionality) { kw_directional | kw_bidirectional }
73
+ rule(:kw_relationship_type) { kw_dependency | kw_association | kw_aggregation | kw_composition }
74
+
75
+ rule(:relationship_directionality) { (kw_relationship_directionality.as(:directionality) >> spaces).maybe }
76
+ rule(:relationship_type) { kw_relationship_type.as(:type) >> spaces }
77
+ rule(:relationship_from) { (spaces >> (name.as(:name) | str("*").repeat(1)).as(:from)).maybe }
78
+ rule(:relationship_to) { (spaces >> (name.as(:name) | str("*").repeat(1)).as(:to)).maybe }
79
+ rule(:relationship_definition) { (relationship_directionality >> relationship_type >> name.as(:name) >> relationship_from >> relationship_to).as(:relationship) }
80
+
81
+ # -- Class
82
+
83
+ rule(:kw_class_modifier) { kw_abstract | kw_interface }
84
+
85
+ rule(:class_modifier) { (kw_class_modifier.as(:modifier) >> spaces).maybe }
86
+ rule(:class_keyword) { kw_class >> spaces }
87
+ rule(:class_inner_definitions) { field_definition | method_definition | class_relationship_definition | relationship_definition }
88
+ rule(:class_inner_definition) { class_inner_definitions >> whitespace? }
89
+ rule(:class_body) { spaces? >> str("{") >> whitespace? >> class_inner_definition.repeat.as(:members) >> str("}") }
90
+ rule(:class_body?) { class_body.maybe }
91
+
92
+ rule(:class_definition) { class_modifier >> class_keyword >> name.as(:name) >> class_body? }
93
+
94
+ # -- Document
95
+
96
+ rule(:document_definitions) { class_definition >> whitespace? }
97
+
98
+ rule(:document) { whitespace? >> document_definitions.repeat.as(:classes) }
99
+
100
+ # -- Root
101
+
102
+ root(:document)
103
+
104
+ end
105
+ end
data/lib/ucd.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "version"
2
+ require "ucd/parser"
3
+ require "ucd/formatter/graphviz"
4
+
5
+ module UCD
6
+ is_versioned
7
+ end
data/ucd.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "ucd"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ucd"
8
+ spec.version = UCD::VERSION.to_s
9
+ spec.authors = ["Ryan Scott Lewis"]
10
+ spec.email = ["ryanscottlewis@gmail.com"]
11
+
12
+ spec.summary = %q{UML Class Diagram Language}
13
+ spec.description = %q{A simple language for easily specifying UML class diagrams and generating output based on them}
14
+ spec.homepage = "https://github.com/RyanScottLewis/ucd"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_dependency "parslet", "~> 1.7.1"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.13"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "rspec", "~> 3.0"
29
+ spec.add_development_dependency "version", "~> 1.0.0"
30
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ucd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Scott Lewis
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-03-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parslet
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.7.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.7.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.13'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.13'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: version
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.0.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.0.0
83
+ description: A simple language for easily specifying UML class diagrams and generating
84
+ output based on them
85
+ email:
86
+ - ryanscottlewis@gmail.com
87
+ executables:
88
+ - ucd
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - ".gitignore"
93
+ - ".rspec"
94
+ - ".travis.yml"
95
+ - CODE_OF_CONDUCT.md
96
+ - Gemfile
97
+ - LICENSE.txt
98
+ - README.md
99
+ - Rakefile
100
+ - TODO.md
101
+ - VERSION
102
+ - bin/console
103
+ - bin/setup
104
+ - examples/composite - minimal.ucd
105
+ - examples/composite.png
106
+ - examples/composite.ucd
107
+ - exe/ucd
108
+ - lib/ucd.rb
109
+ - lib/ucd/formatter.rb
110
+ - lib/ucd/formatter/base.rb
111
+ - lib/ucd/formatter/graphviz.rb
112
+ - lib/ucd/has_attributes.rb
113
+ - lib/ucd/interface/base.rb
114
+ - lib/ucd/interface/command_line.rb
115
+ - lib/ucd/node.rb
116
+ - lib/ucd/node/base.rb
117
+ - lib/ucd/node/class_node.rb
118
+ - lib/ucd/node/class_relationship.rb
119
+ - lib/ucd/node/document.rb
120
+ - lib/ucd/node/field.rb
121
+ - lib/ucd/node/has_name.rb
122
+ - lib/ucd/node/has_type.rb
123
+ - lib/ucd/node/method.rb
124
+ - lib/ucd/node/method_argument.rb
125
+ - lib/ucd/node/relationship.rb
126
+ - lib/ucd/parser.rb
127
+ - ucd.gemspec
128
+ homepage: https://github.com/RyanScottLewis/ucd
129
+ licenses:
130
+ - MIT
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.5.1
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: UML Class Diagram Language
152
+ test_files: []