ANN: MetaTags 1.0

R

Ryan Pavlik

MetaTags 1.0
============

MetaTags is a rather simple ruby module. Its goal is the ability to
easily define and produce sets of tagged metadata in the code. The
code can then examine this metadata and use it as necessary.

Two full tag sets are provided: one for class info (such as the
class name, a description, and attributes), and one for method info
(such as method name, title, category, description, parameters,
etc.).

While inline documetation systems have a goal of parsing
documentation and providing human-readable output, the goal of
MetaTags is to produce documentation that is readable by the code
itself. The result of this could also be used to provide nice
human-readable documentation, as well.

Here is an example:

class_info <<-DOC
!Class: MyClass
!Desc: This class does something

!attr a: A: The "A" attribute
!attr b: B
DOC
class MyClass
attr_accessor :a
attr_reader :b

method_info <<-DOC
!Method: initialize
!Title: Initialize
!Category: constructors

!Desc: Create a new MyClass object giving optional defaults
for "a" and "b".

!Parameters:
* a: A: The "a" object; defaults to 5
* b: B

!param_flag b: width=80
DOC
def initialize(a, b)
:
end

:
end

You can find MetaTags at the following location:

http://mephle.org/MetaTags/
 
T

Tobias Peters

Ryan said:
method_info <<-DOC
!Method: initialize
!Title: Initialize
!Category: constructors

!Desc: Create a new MyClass object giving optional defaults
for "a" and "b".

!Parameters:
* a: A: The "a" object; defaults to 5
* b: B

!param_flag b: width=80
DOC
def initialize(a, b)
:
end

I'd rather like
method_info <<-DOC
Create a new MyClass object giving optional defaults
for "a" and "b".

Category: constructors
Parameters:
* a: A: The "a" object; defaults to 5
* b: B
DOC
def initialize(a=5,b=B.new) ... end

I.e. less punctuation, make your "Desc:" tag the default, get method
name by overiding method_added

Or, maybe
doc('Create a new MyClass object giving optional defaults ',
'for "a" and "b"',
:category, 'constructors',
:parameters,
:a, 'A: The "a" object; defaults to 5',
:b, 'B')
def initialize(a=5,b=B.new)
 
R

Ryan Pavlik

On Mon, 8 Sep 2003 21:58:17 +0900

I'd rather like
method_info <<-DOC
Create a new MyClass object giving optional defaults
for "a" and "b".

Category: constructors
Parameters:
* a: A: The "a" object; defaults to 5
* b: B
DOC
def initialize(a=5,b=B.new) ... end

I.e. less punctuation, make your "Desc:" tag the default, get method
name by overiding method_added

Unfortunately there's nothing tying the method to the method_info
without telling it. In a few cases, I've used this in subclasses
without any method redefinition, just to make the wordking more
appropriate. (This is used to generate various forms.)

As for punctuation, I'll make it so you can alter the regexp used for
splitting the documentation... but you may find you need something so
that any description strings that have a ": " in them don't get split.
Or, maybe
doc('Create a new MyClass object giving optional defaults ',
'for "a" and "b"',
:category, 'constructors',
:parameters,
:a, 'A: The "a" object; defaults to 5',
:b, 'B')
def initialize(a=5,b=B.new)

Hah. The irony... this is similar to the old format I was using. In
fact, if you prefer this, it'd be trivial to write a function that fills
in the same structure merely using this form. After using it for
awhile, though, I found it to be much harder to both write (lots of
symbols) and look at (same reason) than something that's mostly text.
Since you write one for _every_ method, it tends to wear. Also, with
normal strings, you don't have a lot of room for descriptions and the
like, and I ran into this limitation a number of times.

Thanks for the feedback,

--
Ryan Pavlik <[email protected]>

"Another *perfectly* calculated space-time
splice-n-splice. Now to get back to... wait a second.
I forgot to carry the TWO!" - 8BT
 
W

why the lucky stiff

I'd rather like
method_info <<-DOC
Create a new MyClass object giving optional defaults
for "a" and "b".

Category: constructors
... and so on ...

Looks like YAML just about. Save yourself the parsing and go for:

# Call YAML::load inside method_info ..
method_info <<-DOC
Method: initialize
Title: Initialize
Category: constructors
Desc: Create a new MyClass object giving optional defaults
for "a" and "b".
Parameters:
- a: 'A: The "a" object; defaults to 5'
- b: 'B'
DOC

The parameter list comes back as an array. Lovely.

Even better, check out the %y{ ... } patch:

http://whytheluckystiff.net/ruby/ruby-1.8.0-yamlstr.patch

And then:

method_info %y{
Method: initialize
Title: Initialize
...
}

_why
 
R

Ryan Pavlik

Looks like YAML just about. Save yourself the parsing and go for:
<snip>

Parsing the tags is a simple str.split operation. The tricky bit is
parsing the content of the sections, which don't necessarily look like
YAML at all. For instance:

!param_flags foo: x=5, foo, bar=false

Of course, MetaTags is mostly for defining the set of tags you're using;
theoretically, you could subclass or dynamically modify TagSet to take a
YAML-preparsed string, and then apply the Section parsers to each YAML
string.

Parsing the given tag format is pretty trivial, though. MetaTags just
makes it easy to define a set of tags and their corresponding parsers:

text = TextSection.new
sym = SymbolSection.new
int = IntegerSection.new
ret = ReturnSection.new
par_long = ParametersSection.new
par_short = ParamSection.new
par_flags = ParamFlagsSection.new

TS = TagSet.new(["method", "name", sym, TAG_REQUIRED],
["title", nil, text],
["category", nil, sym],
["order", nil, int],
["desc", nil, text],
["description", "desc", text],
["param", "params", par_short],
["parameters", "params", par_long],
["returns", nil, ret],
["param_flags", nil, par_flags])

You could, if desired, do TS = MyTagSet(...) in your application, and
get a different input format. What's mostly important is that you get
the same information out in the end, as opposed to the input format
(since people obviously have different preferences).

--
Ryan Pavlik <[email protected]>

"Another *perfectly* calculated space-time
splice-n-splice. Now to get back to... wait a second.
I forgot to carry the TWO!" - 8BT
 
S

Sean O'Dell

Ryan said:
Here is an example:

class_info <<-DOC
!Class: MyClass
!Desc: This class does something

!attr a: A: The "A" attribute
!attr b: B
DOC
class MyClass
attr_accessor :a
attr_reader :b

method_info <<-DOC
!Method: initialize
!Title: Initialize
!Category: constructors

!Desc: Create a new MyClass object giving optional defaults
for "a" and "b".

!Parameters:
* a: A: The "a" object; defaults to 5
* b: B

!param_flag b: width=80
DOC
def initialize(a, b)
:
end

:
end

Ryan, for what it's worth, I like this pretty much how it works right
now. If I were to change anything it would be:

Make it so "!" is re-definable and ignore any text that appears before
it. This could be used as a generic source code parser which extracts
documentation, etc. from the source code (by parsing anything after a
comment character + a semaphor). As a stand-alone library, that could
have lots of other uses.

Sean O'Dell
 
R

Ryan Pavlik

Ryan, for what it's worth, I like this pretty much how it works right
now. If I were to change anything it would be:

Thanks... feedback is good.
Make it so "!" is re-definable and ignore any text that appears before
it. This could be used as a generic source code parser which extracts
documentation, etc. from the source code (by parsing anything after a
comment character + a semaphor). As a stand-alone library, that could
have lots of other uses.

I'll definitely include this in the next release... probably on both a
global and a per-TagSet basis. Basically, it just splits the string on
a regexp, and the regexp is currently /^\s*!(\w+):?/. I just picked !
because I believe javadoc and doxygen use it, so it might be familiar.

In the next release I'll also try to include a basic tool that generates
HTML as an example. This wasn't my primary goal, but it'd probably be
useful.

thanks,

--
Ryan Pavlik <[email protected]>

"Another *perfectly* calculated space-time
splice-n-splice. Now to get back to... wait a second.
I forgot to carry the TWO!" - 8BT
 
S

Sean O'Dell

Ryan said:
Thanks... feedback is good.




I'll definitely include this in the next release... probably on both a
global and a per-TagSet basis. Basically, it just splits the string on
a regexp, and the regexp is currently /^\s*!(\w+):?/. I just picked !
because I believe javadoc and doxygen use it, so it might be familiar.

In the next release I'll also try to include a basic tool that generates
HTML as an example. This wasn't my primary goal, but it'd probably be
useful.

Or just dump it as XML and let people worry about transforming it into
HTML themselves. Generating XML is pretty simple; you just bracket
stuff by name and transform &, ', ", < and > character. That part is just:

class String
def escape_xml()
result = self.clone
result.gsub!(/[&'"<>]/) do | m |
case m
when "&"
"&amp;"
when "'"
"&apos;"
when "\""
"&quit;"
when "<"
"&lt;"
when ">"
"&gt;"
end
end
end
end

.... or perhaps someone knows a Rubyism that would be more efficient.

Sean O'Dell
 
A

Aredridel

--=-PfvNEhXEPIUmwHtMIjuM
Content-Type: text/plain
Content-Transfer-Encoding: quoted-printable

=20
class_info <<-DOC
!Class: MyClass
!Desc: This class does something
=20
!attr a: A: The "A" attribute
!attr b: B
DOC

Is that YAML?!

Sure looks similar, but I'm hardly familiar with YAML yet.

If not, YAML might be the way to go...


--=-PfvNEhXEPIUmwHtMIjuM
Content-Type: application/pgp-signature; name=signature.asc
Content-Description: This is a digitally signed message part

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.3 (GNU/Linux)

iD8DBQA/XSXNtP09exA3hooRAivQAKCfQXHTFBmkm8MZ4XJQMOQmAGoHVACfYvKN
vURlbuaCa5V2vfPRpOco25o=
=7QcO
-----END PGP SIGNATURE-----
0
--=-PfvNEhXEPIUmwHtMIjuM--
 
A

Aredridel

--=-51rN3hB9hLXDhAiguiDC
Content-Type: text/plain
Content-Transfer-Encoding: quoted-printable

Even better, check out the %y{ ... } patch:
=20
http://whytheluckystiff.net/ruby/ruby-1.8.0-yamlstr.patch

Matz: Any plans to make this standard? Looks like a winner to me.

If so, I'll add the patch to the PLD 1.8.0 RPM.

Ari

--=-51rN3hB9hLXDhAiguiDC
Content-Type: application/pgp-signature; name=signature.asc
Content-Description: This is a digitally signed message part

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.3 (GNU/Linux)

iD8DBQA/XSYZtP09exA3hooRAmeuAJ4zsUvo5JJgxkGI6hNQbV8nk9GoOQCgqyY9
miScBlFnlQ4srGulxQ/Kb8E=
=Jwnx
-----END PGP SIGNATURE-----
0
--=-51rN3hB9hLXDhAiguiDC--
 
H

Hal Fulton

Aredridel said:
Matz: Any plans to make this standard? Looks like a winner to me.

If so, I'll add the patch to the PLD 1.8.0 RPM.

Read over Chad's comments about a dozen posts ago.

I share his concern, and I wouldn't be surprised if Matz
did also. But IANYM.

Hal
 
A

Aredridel

--=-Ssp9WjdEtSGObIim8DAY
Content-Type: text/plain
Content-Transfer-Encoding: quoted-printable
=20
Read over Chad's comments about a dozen posts ago.

That's what I get for using a threaded reader. I reply, only to
discover a more recent post that tells me the answer to what I ask.
I share his concern, and I wouldn't be surprised if Matz
did also. But IANYM.

I agree. I rather liked _why's suggestion of extensible %_{} syntax,
but that is quite a large change.

Ari

--=-Ssp9WjdEtSGObIim8DAY
Content-Type: application/pgp-signature; name=signature.asc
Content-Description: This is a digitally signed message part

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.3 (GNU/Linux)

iD8DBQA/XSgetP09exA3hooRAsTFAJ9cpVZCiJt1OfzpQuVX3BFv/JBdTgCgmU8K
L27u6v2HW9R9vWqhiIB4hJM=
=U9G3
-----END PGP SIGNATURE-----
0
--=-Ssp9WjdEtSGObIim8DAY--
 
J

Jim Weirich

That's what I get for using a threaded reader. I reply, only to
discover a more recent post that tells me the answer to what I ask.


I agree. I rather liked _why's suggestion of extensible %_{} syntax,
but that is quite a large change.

I'm not sure an extensible %_{} syntax will solve the basic problem.
Suppose my library defines JimData to be %j{jim data goes here} and John
wrote a library where %j{john data goes here}. Since defining %j{}
seems to be a global decision, we would have two libraries that couldn't
/ever/ be used in the same Ruby program. We have nice modules to keep
our name spaces clean, and then we start using a name space with only 62
possibilities (I'm counting upper/lower case letters and digits).

I think an extensible %_{} syntax could only fly if we were able to say
"Over /this/ section of code, %j will mean JimData, and on /that/
section of code %j will mean JohnData. In other words, %_{} needs
scoping rules. And I'm not convinced the value added by extensible %_{}
is more than the added complexity.

I suspect its easier to just say JimData.new %{...}.
 
W

why the lucky stiff

And I'm not convinced the value added by extensible %_{}
is more than the added complexity.

This is why my patch was never submitted to Matz, but simply remains a
personal enhancement for my own tricked-out ~why/bin/ruby. Jim's probably
right that things could get cluttered quickly and the nice gentleman could
step on each other's shined shoes and scuff each other's shined shoes and
trip and face plant and get their noses stuck in the spaces in the
floorboards.

It's nice for personal scripts, though, and demonstrates the fun of Ruby
hackin.

_why
 
T

Tobias Peters

On Mon, 8 Sep 2003 21:58:17 +0900



Unfortunately there's nothing tying the method to the method_info
without telling it.

Yes there is. method_added is what you need. See:
METHOD_DOCS = []
class Method_Doc
def initialize(data, klass)
@data = data
@klass = klass
METHOD_DOCS << self
end
attr_accessor :method_name
attr_reader :data, :klass
end

class Module
def method_doc(data)
@last_method_doc = Method_Doc.new(data, self);
end

alias pre_method_doc_method_added method_added

def method_added(name)
(doc = @last_method_doc) && (doc.method_name = name)
pre_method_doc_method_added(name)
end
end

class Example
method_doc <<-DOC
This does some serious init work
DOC
def initialize(a,b)
@a,@b=a,b
end
end

p METHOD_DOCS
<<<<<<<<<<<<test_method_added.rb<<<<<<<<<<<

$ ruby test_method_added
[#<Method_Doc:0x4013f34c
@method_name=:initialize,
@klass=Example,
@data="\t\tThis does some serious init work\n">]

@klass info comes from Module#method_doc, @method_name info comes from
Module#method_added
In a few cases, I've used this in subclasses
without any method redefinition, just to make the wordking more
appropriate. (This is used to generate various forms.)

I cannot follow.

Tobias
 
M

Mauricio Fernández

Unfortunately there's nothing tying the method to the method_info
without telling it.

Yes there is. method_added is what you need. See:
[...]
<<<<<<<<<<<<test_method_added.rb<<<<<<<<<<<

$ ruby test_method_added
[#<Method_Doc:0x4013f34c
@method_name=:initialize,
@klass=Example,
@data="\t\tThis does some serious init work\n">]

@klass info comes from Module#method_doc, @method_name info comes from
Module#method_added
In a few cases, I've used this in subclasses
without any method redefinition, just to make the wordking more
appropriate. (This is used to generate various forms.)

I cannot follow.

I think he meant the method you suggest wouldn't work if the method is
not redefined, but it makes sense to expand it as follows

METHOD_DOCS = []
class Method_Doc
def initialize(data, klass)
@data = data
@klass = klass
METHOD_DOCS << self
end
attr_accessor :method_name
attr_reader :data, :klass
end

class Module
def method_doc(data, method = nil)
if method
d = Method_Doc.new data, self
d.method_name = method
@last_method_doc = nil # don't mess up
else
@last_method_doc = Method_Doc.new(data, self);
end
end

alias pre_method_doc_method_added method_added

def method_added(name)
@last_method_doc ||= nil # get rid of warning with -w
(doc = @last_method_doc) && (doc.method_name = name)
@last_method_doc = nil # don't modify twice the same docs
pre_method_doc_method_added(name)
end
end

class Example
method_doc <<-DOC
This does some serious init work
DOC
def initialize(a,b)
@a,@b=a,b
end

def foo
end
end

class Bla < Example
method_doc <<-DOC, :initialize # this breaks vim's syntax colouring :p
This does some serious init work, but I explain it using a
terminology more appropriate for Bla.
DOC

def bar # this shouldn't be documented
end
end

p METHOD_DOCS


--
_ _
| |__ __ _| |_ ___ _ __ ___ __ _ _ __
| '_ \ / _` | __/ __| '_ ` _ \ / _` | '_ \
| |_) | (_| | |_\__ \ | | | | | (_| | | | |
|_.__/ \__,_|\__|___/_| |_| |_|\__,_|_| |_|
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

We come to bury DOS, not to praise it.
-- Paul Vojta, (e-mail address removed)
 
R

Ryan Pavlik

class Bla < Example
method_doc <<-DOC, :initialize # this breaks vim's syntax colouring :p
This does some serious init work, but I explain it using a
terminology more appropriate for Bla.
DOC

def bar # this shouldn't be documented
end
end

p METHOD_DOCS

This is exactly right. ;)

Also, a lot of these examples are leaving off all the other minor
"documentation" bits---these are just as necessary as a description.
Menus are constructed based on category groupings, dialogs are
constructed based on parameter descriptions, titles and buttons on
method title, etc.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top