Skip to content

Commit

Permalink
Converted RDoc-formatted comments to Markdown
Browse files Browse the repository at this point in the history
Added usage to RepeatedElementVal, which is needed because the
usage cannot be derived unless the element isn't empty.

Improved documentation in various parts of the code base. Mostly
finished 'Generating.md', which describes how to generate X12 using
Builder::BuilderDsl.

Created a Reader::Position class that will be used to track the source
of each token (and the call stack when generating X12). Currently the
instance of Reader::Input is being used, which isn't ideal.
  • Loading branch information
kputnam committed Sep 28, 2011
1 parent 06901ec commit 4ccebbf
Show file tree
Hide file tree
Showing 45 changed files with 659 additions and 292 deletions.
193 changes: 193 additions & 0 deletions doc/Generating.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
Generating X12
==============

Stupidedi has a simple interface for generating X12 documents. Once you have
defined a transaction set or implementation guide (see [Defining](Defining.md)),
you can generate well-formed documents using [`Builder::BuilderDsl`][1].

[1]: ../../Stupidedi/Builder/BuilderDsl.html

Configuration
-------------

Minimal configuration is needed so Stupidedi can load the correct definitions to
ensure well-formedness. The configuration below links interchange version 00501
to an instance of [InterchangeDef][2], and links the transaction set (identified
by three elements) to an instance of [TransactionSetDef][3].

[2]: ../../Stupidedi/Envelope/InterchangeDef.html
[3]: ../../Stupidedi/Envelope/TransactionSetDef.html

config = Stupidedi::Config.new

# Link the "00501" value in ISA12 element to the definition
config.interchange.register("00501") do
Stupidedi::Dictionaries::Interchanges::FiveOhOne::InterchangeDef
end

# Link "005010X222" in GS08 or ST03, "HC" in GS01, and "837"
# in ST01 to the implementation guide definition
config.transaction_set.register("005010X222", "HC", "837") do
Stupidedi::Guides::FiftyTen::X222::HC837
end

Generating a Segment
--------------------

The [`Builder::BuilderDsl`][1] API uses `method_missing` to dynamically respond
to method calls. If the method name matches the format of a segment identifier,
a segment is constructed and added to the parse tree. The arguments to the
method call should be the elements of the segment.

b = Stupidedi::Builder::BuilderDsl.new(config)

b.ISA("00", "", # authorization information
"00", "", # authentication information
"ZZ", "SUBMITTER ID", # submitter identification
"ZZ", "RECEIVER ID", # recipient identification
Time.now.utc, # date
Time.now.utc, # time
"^", # repetition separator
"00501", # interchange version
"333666999", # control number
"1", # acknowledgement request
"T", # usage indicator
"~") # segment terminator

Alternatively, the [`#segment!`][4] method can be used to avoid the overhead of
method lookup incurred by `method_missing`.

[4]: ../../Stupidedi/Builder/BuilderDsl.html#segment!-instance_method

### Simple Elements

Simple elements of _any_ type can be constructed from Strings (this is how X12
is parsed from a file), but certain element types can be constructed from other
types of Ruby values.

The description of each element type below pertains to the `FiftyTen` functional
group definition. The `SimpleElementDef` and `SimpleElementVal` classes define
the minimal interfaces that are extended by subclasses like `AN` and `StringVal`.
See the [`FiftyTen::ElementTypes`][5] namespace for more examples.

[5]: ../../Stupidedi/Dictionaries/FunctionalGroups/FiftyTen/ElementTypes.html

#### Strings

String elements (declared with type `AN`) can be constructed from any value that
responds to `#to_s`. The constructed element is a [`StringVal`][6].

[6]: ../../Stupidedi/Dictionaries/FunctionalGroups/FiftyTen/ElementTypes/StringVal.html

#### Identifiers

Identifier elements (declared with type `ID`) can be constructed from any value
that responds to `#to_s`. The constructed element is an [`IdentifierVal`][7].

[7]: ../../Stupidedi/Dictionaries/FunctionalGroups/FiftyTen/ElementTypes/IdentifierVal.html

#### Dates

Date elements (declared with type `DT`) can be constructed from a `String` of
either six or eight characters, and from any value that responds to `#year`,
`#month`, and `#day`. This includes the `Date`, `Time`, and `DateTime` classes
included with the standard Ruby libraries. The constructed element is a [`DateVal`][8].

[8]: ../../Stupidedi/Dictionaries/FunctionalGroups/FiftyTen/ElementTypes/DateVal.html

#### Times

Time elements (declared with type `TM`) can be constructed from a `String` of
either two, four, six, or more than six characters, and from the `Time` and
`DateTime` values. The constructed element is a [`TimeVal`][9].

[9]: ../../Stupidedi/Dictionaries/FunctionalGroups/FiftyTen/ElementTypes/TimeVal.html

#### Numbers

Numeric and decimal elements (declared with type `Nn` and `R`, respectively) can
be constructed from any value that responds to `#to_d`. The constructed element
is a [`NumericVal`][10] or [`DecimalVal`][11].

[10]: ../../Stupidedi/Dictionaries/FunctionalGroups/FiftyTen/ElementTypes/NumericVal.html
[11]: ../../Stupidedi/Dictionaries/FunctionalGroups/FiftyTen/ElementTypes/DecimalVal.html

### Composite Elements

Composite elements are constructed using the `#composite` method. The arguments
to the method call should be the component elements. For instance, to generate
an `HI` segment with three composite elements:

b.HI(b.composite("ABK", "7868"),
b.composite("ABF", "052"),
b.composite("ABF", "E9283"))

### Repeated Elements

Repeated elements are constructed using the `#repeated` method. The arguments to
the method call should be either all simple elements or all composite elements,
according to the segment definition.

b.AK9(b.repeated("R", "X", "E"), 1, 1, 0)

b.IK4(b.repeated(
b.composite(3, 1),
b.composite(4)),
1068, 7, "B")

Element Placeholders
--------------------

Because [`Builder::BuilderDsl`][1] builds a parse tree as segments are
generated, it can infer and validate certain information about the elements in
a segment.

### Blank Elements

Blank elements (simple, composite, and repeated) can be generated from a `nil`
argument, but you can improve readability by using `#blank`, and no-argument
calls to `#composite` and `#repeated`.

b.AK9(nil, 1, 1, 0)
b.AK9(b.repeated, 1, 1, 0)

b.HI(nil, b.composite(b.blank, "052"))
b.HI(b.composite, b.composite(b.blank, "052"))

Also, if fewer than the defined number of elements are given as arguments, the
missing arguments generate blank elements. For example, the following statements
both generate the same segment.

b.ST("835", "1234")
b.ST("835", "1234", b.blank)

### Default Elements

Certain elements are declared with a single value in `#allowed_values`. These
are usually qualifier elements, like `NM102`, whose value adds little
readability. These values can be inferred by [`Builder::BuilderDsl`][1] when the
`#default` placeholder is used. For example, when generating the X222 837P, the
following statements generate the same segment.

b.BHT("0019", "00", control_number, Time.now.utc, Time.now.utc, "CH")

b.BHT(b.default, "00", control_number, Time.now.utc, Time.now.utc, "CH")

When a default value cannot be inferred, a [`ParseError`][12] is thrown.

[12]: ../../Stupidedi/Exceptions/ParseError.html

### Unused Elements

For elements that are declared to never be sent, `nil` or `#blank` will generate
the empty segment, however using `#notused` may be more self-documenting. If
`Builder::BuilderDsl` determines that the element is not declared as such, it
will raise a [`ParseError`][12].

Syntax Validation
-----------------

### Segment Order

### Element Types

Empty file added doc/design/Parser.md
Empty file.
Empty file added doc/design/Reader.md
Empty file.
10 changes: 5 additions & 5 deletions lib/ruby/array.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class Array

# Return the first item. Raises an +IndexError+ if the Array is +empty?+.
# Return the first item. Raises an `IndexError` if the Array is `empty?`.
#
# @example
# [1, 2, 3].head #=> 1
Expand All @@ -11,7 +11,7 @@ def head
x
end

# True if {#at} is defined for the given +n+
# True if {#at} is defined for the given `n`
#
# @example
# [1, 2, 3].defined_at?(0) #=> true
Expand All @@ -37,7 +37,7 @@ def tail
xs
end

# Selects all elements except the last +n+ ones.
# Selects all elements except the last `n` ones.
#
# @example
# [1, 2, 3].tail #=> [1, 2]
Expand All @@ -50,7 +50,7 @@ def init(n = 1)
slice(0..-(n + 1)) or []
end

# Select all elements except the first +n+ ones.
# Select all elements except the first `n` ones.
#
# @example
# [1, 2, 3].drop(1) #=> [2, 3]
Expand All @@ -63,7 +63,7 @@ def drop(n)
slice(n..-1) or []
end

# Select all elements except the last +n+ ones.
# Select all elements except the last `n` ones.
#
# @return [Array]
def take(n)
Expand Down
8 changes: 4 additions & 4 deletions lib/ruby/blank.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class String

# True if the string is +empty?+ or contains all whitespace
# True if the string is `empty?` or contains all whitespace
#
# @example
# "abc".blank? #=> false
Expand All @@ -14,7 +14,7 @@ def blank?

module Enumerable

# True if the collection is +empty?+
# True if the collection is `empty?`
#
# @example
# [1,2].blank? #=> false
Expand All @@ -27,7 +27,7 @@ def blank?

class NilClass

# Always +true+. Note this overrides {Object#blank?} which returns false.
# Always `true`. Note this overrides {Object#blank?} which returns false.
#
# @example
# nil.blank? #=> true
Expand All @@ -39,7 +39,7 @@ def blank?

class Object

# Always +false+. Note that {NilClass#blank?} is overridden to return +true+
# Always `false`. Note that {NilClass#blank?} is overridden to return `true`
#
# @example
# false.blank? #=> false
Expand Down
4 changes: 2 additions & 2 deletions lib/ruby/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ def snoc(array = [])

# @group Combinators

# Yields +self+ to a block argument
# Yields `self` to a block argument
def bind
yield self
end

# Yields +self+ to a side-effect block argument and return +self+
# Yields `self` to a side-effect block argument and return `self`
#
# @return self
def tap
Expand Down
12 changes: 6 additions & 6 deletions lib/ruby/string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,26 @@ def at(n)
self[n, 1] unless n >= length
end

# Return the string with +n+ characters removed from the front
# Return the string with `n` characters removed from the front
#
# @example
# "abc".drop(0) #=> "abc"
# "abc".drop(2) #=> "c"
#
# @param [Integer] n number of characters to drop (+n > 0+)
# @param [Integer] n number of characters to drop (`n > 0`)
#
# @return [String]
def drop(n)
(length >= n) ? self[n..-1] : ""
end

# Return the first +n+ characters from the front
# Return the first `n` characters from the front
#
# @example
# "abc".take(0) #=> ""
# "abc".take(2) #=> "ab"
#
# @param [Integer] n number of characters to select (+n > 0+)
# @param [Integer] n number of characters to select (`n > 0`)
#
# @return [String]
def take(n)
Expand All @@ -45,15 +45,15 @@ def take(n)
# "abc".split_at(0) #=> ["", "abc"]
# "abc".split_at(2) #=> ["ab", "c"]
#
# @param [Integer] n number of characters at which to split (+n > 0+)
# @param [Integer] n number of characters at which to split (`n > 0`)
#
# @return [Array(String, String)]
def split_at(n)
[take(n), drop(n)]
end

# True if the string is long enough such that {#at} is defined for the
# given +n+
# given `n`
#
# @example
# "abc".defined_at?(0) #=> true
Expand Down
6 changes: 3 additions & 3 deletions lib/ruby/to_d.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class String
\Z/ix

# Converts the string to a BigDecimal after validating the format. If the
# string does not match the pattern for a valid number, an +ArgumentError+
# string does not match the pattern for a valid number, an `ArgumentError`
# is raised.
#
# @example
Expand Down Expand Up @@ -62,8 +62,8 @@ def to_d

class Float

# Raises a +TypeError+ exception. The reason this method is defined at
# all is to produce a more meaningful error than +NoSuchMethod+.
# Raises a `TypeError` exception. The reason this method is defined at
# all is to produce a more meaningful error than `NoSuchMethod`.
#
# @return [void]
def to_d
Expand Down
6 changes: 3 additions & 3 deletions lib/ruby/try.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ class Object

# @group Combinators

# Sends the arguments to +self+ or yields +self+ (when +self+ is non-+nil+).
# This is overridden by {NilClass#try}, which always returns +nil+.
# Sends the arguments to `self` or yields `self` (when `self` is non-`nil`).
# This is overridden by {NilClass#try}, which always returns `nil`.
def try(*args, &block)
if args.empty?
yield self
Expand All @@ -17,7 +17,7 @@ class NilClass

# @group Combinators

# Returns +nil+ (when +self+ is +nil+). This overrides {Object#try}
# Returns `nil` (when `self` is `nil`). This overrides {Object#try}
#
# @return nil
def try(*args)
Expand Down
2 changes: 1 addition & 1 deletion lib/stupidedi/builder/abstract_state.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def segment(segment_tok, segment_use, parent = nil)
element_vals = element_uses.zip(element_toks).map do |use, tok|
if tok.nil?
if use.repeatable?
Values::RepeatedElementVal.empty(use.definition, parent)
Values::RepeatedElementVal.empty(use.definition, parent, use)
else
use.empty
end
Expand Down
Loading

0 comments on commit 4ccebbf

Please sign in to comment.