kramdown-man 0.1.8 → 1.0.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 +4 -4
- data/.github/workflows/ruby.yml +4 -9
- data/.gitignore +4 -3
- data/.yardopts +1 -1
- data/ChangeLog.md +61 -0
- data/Gemfile +6 -3
- data/LICENSE.txt +1 -1
- data/README.md +156 -57
- data/Rakefile +1 -1
- data/bin/kramdown-man +3 -12
- data/gemspec.yml +7 -0
- data/kramdown-man.gemspec +2 -1
- data/lib/kramdown/man/cli.rb +202 -0
- data/lib/kramdown/{converter/man.rb → man/converter.rb} +328 -89
- data/lib/kramdown/man/task.rb +3 -1
- data/lib/kramdown/man/version.rb +3 -1
- data/lib/kramdown/man.rb +28 -3
- data/man/kramdown-man.1 +203 -101
- data/man/kramdown-man.1.md +102 -27
- data/spec/cli_spec.rb +218 -0
- data/spec/converter_spec.rb +969 -0
- data/spec/{kramdown/document_spec.rb → integration_spec.rb} +3 -3
- data/spec/{kramdown/man_spec.rb → man_spec.rb} +1 -1
- data/spec/spec_helper.rb +2 -0
- metadata +18 -11
- data/spec/kramdown/converter/man_spec.rb +0 -562
@@ -1,20 +1,25 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kramdown/man/version'
|
3
4
|
|
4
5
|
require 'kramdown/converter/base'
|
5
6
|
|
6
7
|
module Kramdown
|
7
|
-
module
|
8
|
+
module Man
|
8
9
|
#
|
9
10
|
# Converts markdown into a roff man-page.
|
10
11
|
#
|
11
|
-
|
12
|
+
# @since 1.0.0
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
#
|
16
|
+
class Converter < Kramdown::Converter::Base
|
12
17
|
|
13
18
|
# Comment header
|
14
|
-
HEADER =
|
15
|
-
|
16
|
-
|
17
|
-
|
19
|
+
HEADER = <<~ROFF
|
20
|
+
.\\\" Generated by kramdown-man #{VERSION}
|
21
|
+
.\\\" https://github.com/postmodern/kramdown-man#readme
|
22
|
+
ROFF
|
18
23
|
|
19
24
|
# Typographic Symbols and their UTF8 chars
|
20
25
|
TYPOGRAPHIC_SYMS = {
|
@@ -426,7 +431,7 @@ module Kramdown
|
|
426
431
|
# The roff output.
|
427
432
|
#
|
428
433
|
def convert(root)
|
429
|
-
"#{HEADER}
|
434
|
+
"#{HEADER}#{convert_root(root)}"
|
430
435
|
end
|
431
436
|
|
432
437
|
#
|
@@ -439,9 +444,7 @@ module Kramdown
|
|
439
444
|
# The roff output.
|
440
445
|
#
|
441
446
|
def convert_root(root)
|
442
|
-
root
|
443
|
-
convert_element(child)
|
444
|
-
}.compact.join("\n")
|
447
|
+
convert_children_of(root)
|
445
448
|
end
|
446
449
|
|
447
450
|
#
|
@@ -455,20 +458,19 @@ module Kramdown
|
|
455
458
|
#
|
456
459
|
def convert_element(element)
|
457
460
|
method = "convert_#{element.type}"
|
461
|
+
|
458
462
|
send(method,element) if respond_to?(method)
|
459
463
|
end
|
460
|
-
|
464
|
+
|
461
465
|
#
|
462
466
|
# Converts a `kd:blank` element.
|
463
467
|
#
|
464
468
|
# @param [Kramdown::Element] blank
|
465
469
|
# A `kd:blank` element.
|
466
470
|
#
|
467
|
-
# @return [
|
468
|
-
# The roff output.
|
471
|
+
# @return [nil]
|
469
472
|
#
|
470
473
|
def convert_blank(blank)
|
471
|
-
'.LP'
|
472
474
|
end
|
473
475
|
|
474
476
|
#
|
@@ -481,7 +483,7 @@ module Kramdown
|
|
481
483
|
# The roff output.
|
482
484
|
#
|
483
485
|
def convert_text(text)
|
484
|
-
escape(text.value
|
486
|
+
escape(text.value)
|
485
487
|
end
|
486
488
|
|
487
489
|
#
|
@@ -523,23 +525,30 @@ module Kramdown
|
|
523
525
|
text = header.options[:raw_text]
|
524
526
|
|
525
527
|
case header.options[:level]
|
526
|
-
when 1
|
527
|
-
|
528
|
-
|
528
|
+
when 1
|
529
|
+
<<~ROFF
|
530
|
+
.TH #{text}
|
531
|
+
ROFF
|
532
|
+
when 2
|
533
|
+
<<~ROFF
|
534
|
+
.SH #{text}
|
535
|
+
ROFF
|
536
|
+
else
|
537
|
+
<<~ROFF
|
538
|
+
.SS #{text}
|
539
|
+
ROFF
|
529
540
|
end
|
530
541
|
end
|
531
542
|
|
532
543
|
#
|
533
|
-
#
|
544
|
+
# Ignore `kd:hr` elements.
|
534
545
|
#
|
535
546
|
# @param [Kramdown::Element] hr
|
536
547
|
# A `kd:hr` element.
|
537
548
|
#
|
538
|
-
# @return [
|
539
|
-
# The roff output.
|
549
|
+
# @return [nil]
|
540
550
|
#
|
541
551
|
def convert_hr(hr)
|
542
|
-
".ti 0\n\\l'\\n(.lu'"
|
543
552
|
end
|
544
553
|
|
545
554
|
#
|
@@ -552,9 +561,17 @@ module Kramdown
|
|
552
561
|
# The roff output.
|
553
562
|
#
|
554
563
|
def convert_ul(ul)
|
555
|
-
|
564
|
+
contents = String.new(encoding: Encoding::UTF_8)
|
556
565
|
|
557
|
-
|
566
|
+
ul.children.each do |li|
|
567
|
+
contents << convert_ul_li(li)
|
568
|
+
end
|
569
|
+
|
570
|
+
return <<~ROFF
|
571
|
+
.RS
|
572
|
+
#{contents.chomp}
|
573
|
+
.RE
|
574
|
+
ROFF
|
558
575
|
end
|
559
576
|
|
560
577
|
#
|
@@ -567,15 +584,27 @@ module Kramdown
|
|
567
584
|
# The roff output.
|
568
585
|
#
|
569
586
|
def convert_ul_li(li)
|
570
|
-
|
587
|
+
roff = String.new(encoding: Encoding::UTF_8)
|
588
|
+
|
589
|
+
li.children.each_with_index do |child,index|
|
571
590
|
if child.type == :p
|
572
|
-
|
591
|
+
contents = convert_children_of(child)
|
573
592
|
|
574
|
-
if index == 0
|
575
|
-
|
576
|
-
|
593
|
+
roff << if index == 0
|
594
|
+
<<~ROFF
|
595
|
+
.IP \\(bu 2
|
596
|
+
#{contents.chomp}
|
597
|
+
ROFF
|
598
|
+
else
|
599
|
+
<<~ROFF
|
600
|
+
.IP \\( 2
|
601
|
+
#{contents.chomp}
|
602
|
+
ROFF
|
603
|
+
end
|
577
604
|
end
|
578
|
-
|
605
|
+
end
|
606
|
+
|
607
|
+
return roff
|
579
608
|
end
|
580
609
|
|
581
610
|
#
|
@@ -590,10 +619,18 @@ module Kramdown
|
|
590
619
|
def convert_ol(ol)
|
591
620
|
@ol_index += 1
|
592
621
|
|
593
|
-
|
594
|
-
|
622
|
+
contents = String.new(encoding: Encoding::UTF_8)
|
623
|
+
|
624
|
+
ol.children.each do |li|
|
625
|
+
contents << convert_ol_li(li)
|
626
|
+
end
|
595
627
|
|
596
|
-
return
|
628
|
+
return <<~ROFF
|
629
|
+
.nr step#{@ol_index} 0 1
|
630
|
+
.RS
|
631
|
+
#{contents.chomp}
|
632
|
+
.RE
|
633
|
+
ROFF
|
597
634
|
end
|
598
635
|
|
599
636
|
#
|
@@ -606,15 +643,133 @@ module Kramdown
|
|
606
643
|
# The roff output.
|
607
644
|
#
|
608
645
|
def convert_ol_li(li)
|
609
|
-
|
646
|
+
roff = String.new(encoding: Encoding::UTF_8)
|
647
|
+
|
648
|
+
li.children.each_with_index do |child,index|
|
610
649
|
if child.type == :p
|
611
|
-
|
650
|
+
contents = convert_children_of(child)
|
651
|
+
|
652
|
+
roff << if index == 0
|
653
|
+
<<~ROFF
|
654
|
+
.IP \\n+[step#{@ol_index}]
|
655
|
+
#{contents.chomp}
|
656
|
+
ROFF
|
657
|
+
else
|
658
|
+
<<~ROFF
|
659
|
+
.IP \\n
|
660
|
+
#{contents.chomp}
|
661
|
+
ROFF
|
662
|
+
end
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
return roff
|
667
|
+
end
|
612
668
|
|
613
|
-
|
614
|
-
|
669
|
+
#
|
670
|
+
# Converts a `kd:dl` element.
|
671
|
+
#
|
672
|
+
# @param [Kramdown::Element] dl
|
673
|
+
# A `kd:dl` element.
|
674
|
+
#
|
675
|
+
# @return [String]
|
676
|
+
# The roff output.
|
677
|
+
#
|
678
|
+
def convert_dl(dl)
|
679
|
+
roff = String.new(encoding: Encoding::UTF_8)
|
680
|
+
|
681
|
+
dt_index = 0
|
682
|
+
dd_index = 0
|
683
|
+
|
684
|
+
dl.children.each do |element|
|
685
|
+
case element.type
|
686
|
+
when :dt
|
687
|
+
roff << convert_dt(element, index: dt_index)
|
688
|
+
|
689
|
+
dt_index += 1 # increment the dt count
|
690
|
+
dd_index = 0 # reset the dd count
|
691
|
+
when :dd
|
692
|
+
roff << convert_dd(element, index: dd_index)
|
693
|
+
|
694
|
+
dd_index += 1 # increment the dd count
|
695
|
+
dt_index = 0 # reset the dt count
|
696
|
+
else
|
697
|
+
roff << convert(element)
|
698
|
+
|
699
|
+
# reset both the dt_index and dd_index counters
|
700
|
+
dt_index = 0
|
701
|
+
dd_index = 0
|
702
|
+
end
|
703
|
+
end
|
704
|
+
|
705
|
+
return roff
|
706
|
+
end
|
707
|
+
|
708
|
+
#
|
709
|
+
# Converts a `kd:dt` element within a `kd:dl`.
|
710
|
+
#
|
711
|
+
# @param [Kramdown::Element] dt
|
712
|
+
# A `kd:dt` element.
|
713
|
+
#
|
714
|
+
# @param [Integer] index
|
715
|
+
# The index of the `kd:dt` element. Used to indicate whether this is the
|
716
|
+
# first `kd:dt` element or additional `kd:dt` elements.
|
717
|
+
#
|
718
|
+
# @return [String]
|
719
|
+
# The roff output.
|
720
|
+
#
|
721
|
+
def convert_dt(dt, index: 0)
|
722
|
+
text = convert_text_elements(dt.children)
|
723
|
+
|
724
|
+
if index == 0
|
725
|
+
<<~ROFF
|
726
|
+
.TP
|
727
|
+
#{text}
|
728
|
+
ROFF
|
729
|
+
else
|
730
|
+
<<~ROFF
|
731
|
+
.TQ
|
732
|
+
#{text}
|
733
|
+
ROFF
|
734
|
+
end
|
735
|
+
end
|
736
|
+
|
737
|
+
#
|
738
|
+
# Converts a `kd:dd` element within a `kd:dd`.
|
739
|
+
#
|
740
|
+
# @param [Kramdown::Element] dd
|
741
|
+
# A `kd:dd` element.
|
742
|
+
#
|
743
|
+
# @param [Integer] index
|
744
|
+
# The index of the `kd:dd` element. Used to indicate whether this is the
|
745
|
+
# first `kd:dd` element following a `kd;dt` element or additional
|
746
|
+
# `kd:dt` elements.
|
747
|
+
#
|
748
|
+
# @return [String]
|
749
|
+
# The roff output.
|
750
|
+
#
|
751
|
+
def convert_dd(dd, index: 0)
|
752
|
+
roff = String.new(encoding: Encoding::UTF_8)
|
753
|
+
|
754
|
+
dd.children.each_with_index do |child,child_index|
|
755
|
+
if index == 0 && child_index == 0 && child.type == :p
|
756
|
+
contents = convert_children_of(child)
|
757
|
+
|
758
|
+
# omit the .PP macro for the first paragraph
|
759
|
+
roff << "#{contents.chomp}\n"
|
760
|
+
else
|
761
|
+
if (contents = convert_element(child))
|
762
|
+
# indent all other following paragraphs or other elements
|
763
|
+
roff << <<~ROFF
|
764
|
+
.RS
|
765
|
+
#{contents.chomp}
|
766
|
+
.RE
|
767
|
+
ROFF
|
615
768
|
end
|
616
769
|
end
|
617
|
-
|
770
|
+
end
|
771
|
+
|
772
|
+
return roff
|
618
773
|
end
|
619
774
|
|
620
775
|
#
|
@@ -640,14 +795,13 @@ module Kramdown
|
|
640
795
|
# The roff output.
|
641
796
|
#
|
642
797
|
def convert_blockquote(blockquote)
|
643
|
-
|
644
|
-
case child.type
|
645
|
-
when :p then convert_children(child.children)
|
646
|
-
else convert_element(child)
|
647
|
-
end
|
648
|
-
}.join("\n")
|
798
|
+
contents = convert_children_of(blockquote)
|
649
799
|
|
650
|
-
return
|
800
|
+
return <<~ROFF
|
801
|
+
.RS
|
802
|
+
#{contents.chomp}
|
803
|
+
.RE
|
804
|
+
ROFF
|
651
805
|
end
|
652
806
|
|
653
807
|
#
|
@@ -660,7 +814,16 @@ module Kramdown
|
|
660
814
|
# The roff output.
|
661
815
|
#
|
662
816
|
def convert_codeblock(codeblock)
|
663
|
-
|
817
|
+
contents = escape(codeblock.value)
|
818
|
+
|
819
|
+
return <<~ROFF
|
820
|
+
.PP
|
821
|
+
.RS 4
|
822
|
+
.EX
|
823
|
+
#{contents.chomp}
|
824
|
+
.EE
|
825
|
+
.RE
|
826
|
+
ROFF
|
664
827
|
end
|
665
828
|
|
666
829
|
#
|
@@ -673,9 +836,15 @@ module Kramdown
|
|
673
836
|
# The roff output.
|
674
837
|
#
|
675
838
|
def convert_comment(comment)
|
676
|
-
|
677
|
-
|
678
|
-
|
839
|
+
roff = String.new(encoding: Encoding::UTF_8)
|
840
|
+
|
841
|
+
comment.value.lines.each do |line|
|
842
|
+
roff << <<~ROFF
|
843
|
+
.\\" #{line}
|
844
|
+
ROFF
|
845
|
+
end
|
846
|
+
|
847
|
+
return roff
|
679
848
|
end
|
680
849
|
|
681
850
|
#
|
@@ -688,26 +857,12 @@ module Kramdown
|
|
688
857
|
# The roff output.
|
689
858
|
#
|
690
859
|
def convert_p(p)
|
691
|
-
|
692
|
-
|
693
|
-
if ((children.length >= 2) && (children.first.type == :codespan ||
|
694
|
-
children.first.type == :em ||
|
695
|
-
children.first.type == :strong))
|
696
|
-
newline = children.find_index { |el|
|
697
|
-
el.type == :text && el.value.start_with?("\n")
|
698
|
-
}
|
860
|
+
contents = convert_text_elements(p.children)
|
699
861
|
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
".TP\n#{first_line}\n#{rest}"
|
705
|
-
else
|
706
|
-
".HP\n#{convert_children(children)}"
|
707
|
-
end
|
708
|
-
else
|
709
|
-
".PP\n#{convert_children(children)}"
|
710
|
-
end
|
862
|
+
return <<~ROFF
|
863
|
+
.PP
|
864
|
+
#{contents.chomp}
|
865
|
+
ROFF
|
711
866
|
end
|
712
867
|
|
713
868
|
#
|
@@ -720,7 +875,7 @@ module Kramdown
|
|
720
875
|
# The roff output.
|
721
876
|
#
|
722
877
|
def convert_em(em)
|
723
|
-
"\\fI#{
|
878
|
+
"\\fI#{convert_text_elements(em.children)}\\fP"
|
724
879
|
end
|
725
880
|
|
726
881
|
#
|
@@ -733,7 +888,7 @@ module Kramdown
|
|
733
888
|
# The roff output.
|
734
889
|
#
|
735
890
|
def convert_strong(strong)
|
736
|
-
"\\fB#{
|
891
|
+
"\\fB#{convert_text_elements(strong.children)}\\fP"
|
737
892
|
end
|
738
893
|
|
739
894
|
#
|
@@ -759,38 +914,122 @@ module Kramdown
|
|
759
914
|
# The roff output.
|
760
915
|
#
|
761
916
|
def convert_a(a)
|
762
|
-
href =
|
763
|
-
|
917
|
+
href = a.attr['href']
|
918
|
+
scheme, path = href.split(':',2)
|
764
919
|
|
765
|
-
|
766
|
-
when /^mailto:/
|
767
|
-
email = href[7..-1]
|
920
|
+
text = convert_text_elements(a.children)
|
768
921
|
|
769
|
-
|
770
|
-
|
922
|
+
case scheme
|
923
|
+
when 'mailto'
|
924
|
+
email = escape(path)
|
925
|
+
|
926
|
+
unless text == email
|
927
|
+
<<~ROFF
|
928
|
+
#{text.chomp}
|
929
|
+
.MT #{email}
|
930
|
+
.ME
|
931
|
+
ROFF
|
932
|
+
else
|
933
|
+
<<~ROFF
|
934
|
+
.MT #{email}
|
935
|
+
.ME
|
936
|
+
ROFF
|
771
937
|
end
|
772
|
-
when
|
773
|
-
match =
|
938
|
+
when 'man'
|
939
|
+
if (match = path.match(/\A(?<page>[A-Za-z0-9_-]+)(?:\((?<section>\d[a-z]?)\)|\.(?<section>\d[a-z]?))\z/))
|
940
|
+
man_page_link(match[:page],match[:section])
|
941
|
+
else
|
942
|
+
page = escape(path)
|
774
943
|
|
775
|
-
|
776
|
-
|
944
|
+
<<~ROFF
|
945
|
+
.BR #{page}
|
946
|
+
ROFF
|
947
|
+
end
|
948
|
+
else
|
949
|
+
if (match = href.match(/(?<page>[A-Za-z0-9_-]+)\.(?<section>\d[a-z]?)\.md\z/))
|
950
|
+
man_page_link(match[:page],match[:section])
|
951
|
+
else
|
952
|
+
<<~ROFF
|
953
|
+
#{text.chomp}
|
954
|
+
.UR #{escape(href)}
|
955
|
+
.UE
|
956
|
+
ROFF
|
777
957
|
end
|
958
|
+
end
|
959
|
+
end
|
960
|
+
|
961
|
+
#
|
962
|
+
# Outputs a man page link.
|
963
|
+
#
|
964
|
+
# @param [String] page
|
965
|
+
# The man page name.
|
966
|
+
#
|
967
|
+
# @param [String, nil] section
|
968
|
+
# The optional section of the man page.
|
969
|
+
#
|
970
|
+
# @return [String]
|
971
|
+
# The roff output.
|
972
|
+
#
|
973
|
+
def man_page_link(page,section=nil)
|
974
|
+
if section
|
975
|
+
<<~ROFF
|
976
|
+
.BR #{escape(page)} (#{escape(section)})
|
977
|
+
ROFF
|
778
978
|
else
|
779
|
-
|
979
|
+
<<~ROFF
|
980
|
+
.BR #{escape(page)}
|
981
|
+
ROFF
|
780
982
|
end
|
781
983
|
end
|
782
984
|
|
783
985
|
#
|
784
986
|
# Converts the children of an element.
|
785
987
|
#
|
786
|
-
# @param [Array<Kramdown::Element>]
|
787
|
-
# The
|
988
|
+
# @param [Array<Kramdown::Element>] element
|
989
|
+
# The elements to convert.
|
990
|
+
#
|
991
|
+
# @return [String]
|
992
|
+
# The combined roff output.
|
993
|
+
#
|
994
|
+
def convert_children_of(element)
|
995
|
+
roff = String.new(encoding: Encoding::UTF_8)
|
996
|
+
|
997
|
+
element.children.each do |child|
|
998
|
+
if (contents = convert_element(child))
|
999
|
+
roff << contents
|
1000
|
+
end
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
return roff
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
#
|
1007
|
+
# Converts the children of an element.
|
1008
|
+
#
|
1009
|
+
# @param [Array<Kramdown::Element>] elements
|
1010
|
+
# The text elements to convert.
|
788
1011
|
#
|
789
1012
|
# @return [String]
|
790
1013
|
# The roff output.
|
791
1014
|
#
|
792
|
-
def
|
793
|
-
|
1015
|
+
def convert_text_elements(elements)
|
1016
|
+
roff = String.new(encoding: Encoding::UTF_8)
|
1017
|
+
|
1018
|
+
elements.each do |element|
|
1019
|
+
if (contents = convert_element(element))
|
1020
|
+
if contents.start_with?('.') && !roff.empty? && !roff.end_with?("\n")
|
1021
|
+
# roff macross must exist on their own line
|
1022
|
+
roff << "\n#{contents}"
|
1023
|
+
elsif contents.start_with?(' ') && roff.end_with?("\n")
|
1024
|
+
# remove leadning whitespace following a newline
|
1025
|
+
roff << contents.lstrip
|
1026
|
+
else
|
1027
|
+
roff << contents
|
1028
|
+
end
|
1029
|
+
end
|
1030
|
+
end
|
1031
|
+
|
1032
|
+
return roff
|
794
1033
|
end
|
795
1034
|
|
796
1035
|
#
|
@@ -803,7 +1042,7 @@ module Kramdown
|
|
803
1042
|
# The escaped text.
|
804
1043
|
#
|
805
1044
|
def escape(text)
|
806
|
-
text.gsub(GLYPH_REGEXP)
|
1045
|
+
text.gsub(GLYPH_REGEXP,GLYPHS)
|
807
1046
|
end
|
808
1047
|
|
809
1048
|
end
|
data/lib/kramdown/man/task.rb
CHANGED
data/lib/kramdown/man/version.rb
CHANGED
data/lib/kramdown/man.rb
CHANGED
@@ -1,5 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'kramdown'
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
5
|
+
require 'kramdown/man/version'
|
6
|
+
require 'kramdown/man/converter'
|
7
|
+
|
8
|
+
module Kramdown
|
9
|
+
class Document
|
10
|
+
#
|
11
|
+
# Converts the parsed markdown document to a man page.
|
12
|
+
#
|
13
|
+
# @param [Hash] options
|
14
|
+
# Additional options.
|
15
|
+
#
|
16
|
+
# @return [String]
|
17
|
+
# The converted man page contents.
|
18
|
+
#
|
19
|
+
# @since 1.0.0
|
20
|
+
#
|
21
|
+
# @api public
|
22
|
+
#
|
23
|
+
def to_man(options={})
|
24
|
+
output, warnings = Kramdown::Man::Converter.convert(@root,options)
|
25
|
+
|
26
|
+
@warnings.concat(warnings)
|
27
|
+
return output
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|