-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds flag statement (TODO, FIXME, ...) parsing for docs #3519
Conversation
|
@BlaXpirit @samueleaton explained his reasoning behind this PR in #3460. |
As well as NOTE and OPTIMIZE. |
I like the different color! Perhaps also add support for |
@keplersj If we were to do DEPRECATED, would it be the same format with a colon? Would the text that follows the colon be an explanation on why it is deprecated? Like this: # one of our beloved customers
#
# TODO: create methods for this class
#
class Customer
# fetches a customer from the database by id
#
# DEPRECATED: Use *retrieve* instead
# TODO: query db for customer
# TODO: return nil if customer not found with id
# TODO: return an instance of `Customer` if customer exists
def self.fetch(customer_id : String)
end
end I personally think deprecated should be on the same line as the method description: # one of our beloved customers
#
# TODO: create methods for this class
#
class Customer
# fetches a customer from the database by id (DEPRECATED)
#
# TODO: query db for customer
# TODO: return nil if customer not found with id
# TODO: return an instance of `Customer` if customer exists
def self.fetch(customer_id : String)
end
end This way the deprecated flag would be next to the method definition rather than underneath it. It would also make it so you can't put multiple DEPRECATED flags, which seems appropriate. |
@samueleaton I like the first one better, I think it's a lot like Scala's deprecated annotation. |
As of the latest commit, the following will be parsed in the docs:
|
BUG and FIXME are seem similar. Is there one that people are more fond of? I am in favor of BUG. Its shorter and is a more common word (easier to remember). |
FIXME is more common. It may no be a bug, something poorly written for example. |
@@ -241,6 +251,17 @@ class Crystal::Doc::Generator | |||
end | |||
end | |||
|
|||
# adds extra line break to TODO lines | |||
def isolate_flag_lines(string) | |||
string.split(/\n/).map do |line| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String.build do |io|
string.each_line.join('\n', io) do |line|
if line =~ /^ ?(TODO|FIXME|BUG|NOTE|OPTIMIZE|DEPRECATED):/
io << line << '\n'
else
io << line
end
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like the regex here and the regex on line 235-240 could be replaced by a method which takes a line and returns {flag, text}
or nil
. We can even combine them, as HTML passes through markdown unimpeded.
For example:
if flag, text = parse_flag(line)
io << %(<span class="flag flag-#{flag.downcase}">#{flag}</span> #{text}\n)
else
io << line
end
which requires only one pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you saying to remove the regex completely or just to consolidate the regex into a single method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RX14 I remember why I had to do the regex twice (let me know if your solution would still apply here).
If I were to replace the TODO:
with the HTML before the line is parsed, then the markdown parser will escape the HTML. But if I replace the TODO:
with HTML after the parser, then each TODO:
statement is not guaranteed to be in its own paragraph.
That is why:
- before the markdown parser I am adding an extra
\n
after eachTODO:
line (to guarantee that each line is in its own paragraph) - after the markdown parser I am replacing the
TODO:
with the HTML (because the HTML would be escaped otherwise)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was assuming that html passed through the markdown parser unescaped, because that's what I've seen most engines do. Github lets you use certain html tags in markdown.
I think that it is probably a bad default to let html through, but it should at least be an option. However if we went this route we would have to sanitize html to include only certain tags (github has a list they let through their markdown). It might be worth looking at nature markdown implementations to see how this is done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However my first suggestion with the each_line and join with block are still valid with this workaround.
@ysbaddaden Two Questions: (1) Do we want to require that every flag ends in a colon? (2) Do we want all flags to be required to be uppercase? Ending in ColonFor example, here the DEPRECATED would become a flag: class Customer
# fetches a customer from the database by id
#
# DEPRECATED: This method was buggy and is deprecated
def self.fetch(customer_id : String)
end
end But here the DEPRECATED would not become a flag: class Customer
# fetches a customer from the database by id
#
# DEPRECATED
def self.fetch(customer_id : String)
end
end Require UppercaseDo we want this to work? class Customer
# fetches a customer from the database by id
#
# deprecated: This method was buggy and is deprecated
def self.fetch(customer_id : String)
end
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both FIXME
and BUG
should be supported, there's no need to chose one over the other.
@Sija Gotcha. I reverted the commit. |
No big rule, but I would say that the colon is optional, as long as the definition is on its own line, whereas uppercase is mandatory. That is:
|
end.join("<p>") | ||
end | ||
|
||
# Adds extra line break to flag keyword lines | ||
def isolate_flag_lines(string) | ||
string.split(/\n/).map do |line| | ||
if line =~ /^ ?(TODO|FIXME|BUG|NOTE|OPTIMIZE|DEPRECATED):?/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd use a hash containing flags and corresponding colors — or even just an array with flags, moving the colors into the css itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Sija Something like this?
FLAG_COLORS = {
"BUG" => "red",
"DEPRECATED" => "red",
"FIXME" => "red",
"NOTE" => "yellow",
"OPTIMIZE" => "green",
"TODO" => "orange"
}
FLAGS = FLAG_COLORS.keys
def generate_flags(string)
FLAGS.reduce(string) do |str, flag|
flag_regexp = /<p>\s*#{flag}(:| )?/
element_sub = %(<p><span class="flag #{FLAG_COLORS[flag]}">#{flag}</span> )
str.gsub(flag_regexp, element_sub)
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, and you could reuse it in isolate_flag_lines
method too.
@samueleaton seems that the failure in Travis is caused by formatting issues. Can you reformat Thank you. |
@@ -226,9 +237,11 @@ class Crystal::Doc::Generator | |||
end | |||
|
|||
def doc(context, string) | |||
String.build do |io| | |||
Markdown.parse string, MarkdownDocRenderer.new(context, io) | |||
str = isolate_flag_lines string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why create new variable? couldn't that just be string = isolate_flag_lines string
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Been doing some functional programming and it is showing 😅 . I can change it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could probably just remove that line altogether and do this
Markdown.parse isolate_flag_lines(string), MarkdownDocRenderer.new(context, io)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keeping it in a separate line seems clearer to me.
padding: 2px 4px 1px; | ||
border-radius: 3px; | ||
margin-right: 3px; | ||
font-size: 11px; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest folding multiple border
declarations into:
border: 1px solid transparent;
and l8r in span.flag.<color>
:
border-color: #color;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This came to mind but I i didn't think it was necessary. In hindsight it is better that way. Good eye. 👁 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🍪
@@ -52,7 +63,7 @@ class Crystal::Doc::Generator | |||
Markdown.parse body, MarkdownDocRenderer.new(program_type, io) | |||
end | |||
|
|||
File.write "#{@dir}/index.html", MainTemplate.new(body, types, repository_name) | |||
File.write "#{@dir}/index.html", MainTemplate.new(generate_flags(body), types, repository_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually thing that instead of duplicating changes in this method, this can be replaced with a call to doc(program_type, body)
, where body
is the old definition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This hasn't been fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed it. I even made an optimization where it won't even run the markdown parser on an empty string for the README if the file doesn't exist.
GTG? |
I think is good enough to be merged, @samueleaton can you rebase and squash all the changes? Thank you. |
O still don't quite understand why you wouldn't put a big TODO in public documentation. Is it to encourage pull requests? Is it a remainder for self? Is it to make a library look incomplete? I'm not against this change, I just can't see the usefulness. |
Hello @asterite I'm not sure to follow:
You mean the opposite? Since docs of your project (public or private) will be processed by That of course doesn't cancel or render pointless to have a I don't think they are mutually exclusive. |
306691b
to
e4efc0b
Compare
e4efc0b
to
76ac1c1
Compare
@asterite its squashed and the reviews are fixed. You will also need to merge the pending request at crystal-book #45. |
body = String.build do |io| | ||
Markdown.parse body, MarkdownDocRenderer.new(program_type, io) | ||
end | ||
body = if filename |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I much prefer the old style of having body =
inside the block. Also isn't this String.build
redundant? Doesn't doc
return a string anyway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed. although I would have done...
body = filename ? doc(program_type, File.read(filename)) : ""
... but i don't think it would have been received well
76ac1c1
to
503f561
Compare
Is this ready to merge? If so, I'll merge it |
Seems like it :) |
I can't find anything to complain about any more, at least :) Maybe if we hit performance issues in the doc generator the regex can be replaced but I think it's a non-issue for now. |
@asterite I found and fixed the problem. I expected |
Looks good now, thank you! ❤️ |
@Sija I think that that is correct. Your example looks like 2 lines to me. However there should be a way to have multi line todos, maybe indent? |
@RX14 I think that particular paragraphs and code blocks are supposed to be surrounded by an empty line, which should allow you for writing multiline statements without additional indentation. # Multiline comments have all lines
# concatenated until empty line is found.
#
# This would be next paragraph. |
So what's the difference between these? # TODO this is a long string which is a
# TODO which wraps into another line. # TODO this is todo 1
# TODO this is todo 2 I meant to first one to render "this is a long string which is a TODO which wraps into another line." and the second to render 2 TODOs. |
@Sija That is how it was intended. But I agree with @RX14 that there should be support for multiline flags. However, I would think that users are more likely to have 2 or more flags in a row than have a really long flag, so I don't think a set of flags should have an extra blank line between each flag. |
I agree that |
@Sija I'm working on a project and now agree with you about the So I think I'll make another PR with the following change: Example # My Class
#
# NOTE: Xxxxxx xxxxx xx xxxxxxx x xx.
# NOTE: Yyyyyy y yyy y y yyy yyy yy yyyyy yyyy
# yyy yyyyy y yyyy y yyyyy yyyyyyy yy yy yyyy yy
# yyyyyyyyy yyyyyyyy y yy yy yyyy y.
# NOTE: Zzzzz zzzzzz zzz zzzzzz.
#
# Qqqq qq q q q qqqqq qqq q qqqq qq q qqq q qqqq q qqqq q.
class MyClass
end |
Adds parsing for flag statements (TODO, FIXME, BUG, ...) for docs as discussed in issue #3460
I had to create a workaround because
TODO:
and other keywords are not part of the markdown parser.The code I added:
(1) Makes sure that lines starting with
TODO:
are isolated into their own paragraph(2) Splits the HTML output by paragraph, and then replaces
TODO:
with the relevant HTMLIf there is another way to do this (e.g. with the markdown parser) point me in the right direction and I'll see what I can do.
Screenshot