├── .gitignore ├── Makefile ├── README.markdown ├── example ├── diary │ ├── 2012-07-23.wiki │ ├── 2012-07-24.wiki │ ├── 2012-07-25.wiki │ ├── 2012-07-26.wiki │ └── diary.wiki ├── index.wiki ├── link_1.wiki ├── link_2.wiki ├── lonely.wiki └── sub │ └── sub_link_1.wiki └── vimwiki2org.pl /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ### Makefile --- 2 | ## 3 | ## This program is free software; you can redistribute it and/or 4 | ## modify it under the terms of the GNU General Public License as 5 | ## published by the Free Software Foundation; either version 3, or 6 | ## (at your option) any later version. 7 | ## 8 | ## This program is distributed in the hope that it will be useful, 9 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | ## General Public License for more details. 12 | ## 13 | ## You should have received a copy of the GNU General Public License 14 | ## along with this program; see the file COPYING. If not, write to 15 | ## the Free Software Foundation, Inc., 51 Franklin Street, Fifth 16 | ## Floor, Boston, MA 02110-1301, USA. 17 | ## 18 | ###################################################################### 19 | ## 20 | ### Code: 21 | 22 | DESTDIR= 23 | prefix=$(DESTDIR)/usr 24 | exec_prefix=$(prefix) 25 | bindir=$(exec_prefix)/bin 26 | datarootdir=$(prefix)/share 27 | datadir=$(datarootdir) 28 | appname=vimwiki2org 29 | pkgdatadir=$(datadir)/$(appname) 30 | 31 | .PHONY : help install uninstall 32 | all : help 33 | help : 34 | @echo "Usage:" 35 | @echo " make [install|uninstall|help] [DESTDIR=\"$(DESTDIR)\"] [prefix=\"$(prefix)\"]" 36 | 37 | datafiles=Makefile README.markdown 38 | install : 39 | @echo "==> install..." 40 | mkdir -p $(pkgdatadir) 41 | install -m644 $(datafiles) $(pkgdatadir)/ 42 | cp -rf example $(pkgdatadir)/ 43 | mkdir -p $(bindir) 44 | install -m755 vimwiki2org.pl $(bindir)/vimwiki2org 45 | @echo "==> done." 46 | 47 | uninstall : 48 | @echo "==> uninstall..." 49 | rm -rf $(pkgdatadir) 50 | rm -f $(bindir)/vimwiki2org 51 | @echo "==> done." 52 | 53 | ###################################################################### 54 | ### Makefile ends here 55 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | ## description 2 | 3 | vimwiki2org.pl is a simple tool to convert vimwiki file to emacs 4 | org-mode file 5 | 6 | ## usage 7 | 8 | vimwiki2org.pl index.wiki [file ...] 9 | vimwiki2org.pl [options] -- index.wiki [file ...] 10 | 11 | ## examples 12 | 13 | - **print help message** 14 | 15 | vimwiki2org.pl --help 16 | 17 | - **show man page** 18 | 19 | vimwiki2org.pl --man 20 | 21 | - **convert the example vimwikie files to a org-mode file** 22 | 23 | vimwiki2org.pl /usr/share/vimwiki2org/example/index.wiki > vimwiki.org 24 | 25 | - **convert vimwikie files, give the diary index file's relative path, 26 | and will check and append the lonely files in the same folder or sub 27 | folders with the main index file** 28 | 29 | vimwiki2org.pl -d diary/diary.wiki -L fix -- index.wiki > vimwiki.org 30 | 31 | ## install and uninstall 32 | 33 | - **depends** 34 | - perl 5.14 35 | - **compatibility** 36 | - test ok on vimwiki 2.0.1.stu 37 | - **install** 38 | 39 | make install 40 | 41 | - **uninstall** 42 | 43 | make uninstall 44 | 45 | ## binary files 46 | - **/usr/bin/vimwiki2org** 47 | 48 | a wrapper to run the main script vimwiki2org.pl 49 | -------------------------------------------------------------------------------- /example/diary/2012-07-23.wiki: -------------------------------------------------------------------------------- 1 | 2 | === 2012-07-23 Monday === 3 | * list 1 4 | * list 2 5 | * list 2-1 6 | * list 2-2 7 | * list 3 8 | -------------------------------------------------------------------------------- /example/diary/2012-07-24.wiki: -------------------------------------------------------------------------------- 1 | 2 | === 2012-07-24 Tuesday === 3 | * list 1 4 | * list 2 5 | * list 2-1 6 | * list 2-2 7 | * list 3 8 | -------------------------------------------------------------------------------- /example/diary/2012-07-25.wiki: -------------------------------------------------------------------------------- 1 | 2 | === 2012-07-25 Wednesday === 3 | * list 1 4 | * list 2 5 | * list 2-1 6 | * list 2-2 7 | * list 3 8 | -------------------------------------------------------------------------------- /example/diary/2012-07-26.wiki: -------------------------------------------------------------------------------- 1 | 2 | === 2012-07-26 Thursday === 3 | * list 1 4 | * list 2 5 | * list 2-1 6 | * list 2-2 7 | * list 3 8 | -------------------------------------------------------------------------------- /example/diary/diary.wiki: -------------------------------------------------------------------------------- 1 | = Diary = 2 | | [[2012-07-26]] | [[2012-07-25]] | [[2012-07-24]] | [[2012-07-23]] | 3 | -------------------------------------------------------------------------------- /example/index.wiki: -------------------------------------------------------------------------------- 1 | %title Personal Vim Wiki 2 | %toc Table 3 | 4 | text before header 5 | %%commit text 6 | 7 | * List before header 8 | # list 1-1 9 | # list 1-2 10 | 11 | === Header 1 lv3 === 12 | content in header 13 | * list 1 14 | * list 1-3 15 | * list 1-2 16 | * list 2, links in list item, [[link_1]], [[link_2]] 17 | - list 2-1, link_2 again, with description, [[link_2|link_des_2]] 18 | - list 2-2, link in other folder 19 | [[sub/sub_link_1|sub_link_des_1]] 20 | [[./sub/../sub/sub_link_1|sub_link_relative_path]] 21 | - list 2-3, url link 22 | [[http://www.google.com]] 23 | [[https://www.google.com|Google]] 24 | * list 3 25 | * [[diary/diary|Diary]] 26 | 27 | ==== Header 2 lv4 ==== 28 | the following list is indented 29 | * [ ] Do stuff 1, with 'TODO' state 30 | - [ ] Do substuff 1.1 31 | [[link_1|link_des_1]] 32 | - [ ] Do substuff 2.2 33 | # [ ] Do substuff 1.2.1 34 | content in list, indent less 35 | # [ ] Do substuff 1.2.2 36 | content in list, indent more 37 | - [ ] Do substuff 1.3 38 | * [.] Do stuff 2, with 'TODO' state 39 | * [X] Do stuff 3, with 'DONE' state 40 | 41 | == Header 3 lv2 == 42 | * source block with type 43 | {{{class="brush: python" 44 | def hello(world): 45 | for x in range(10): 46 | print("Hello {0} number {1}".format(world, x)) 47 | }}} 48 | * source block with type, simple mode 49 | {{{sh 50 | awk '/key/ {print}' 51 | awk F: '{...}' 52 | }}} 53 | * source block without type 54 | {{{ 55 | awk '/key/ {print}' 56 | awk F: '{...}' 57 | }}} 58 | 59 | === Header 4 lv3 [[link_2]] === 60 | -------------------------------------------------------------------------------- /example/link_1.wiki: -------------------------------------------------------------------------------- 1 | === Link 1 === 2 | * list 1 3 | * list 1-1 4 | * list 1-2 5 | * list 2 6 | link to index 7 | [[index]] 8 | * list 3 9 | * list 3-1 10 | * list 3-2 11 | -------------------------------------------------------------------------------- /example/link_2.wiki: -------------------------------------------------------------------------------- 1 | === Link 2 === 2 | * list 1 3 | * list 1-1 4 | * list 1-2 5 | * list 2 6 | * list 3 7 | -------------------------------------------------------------------------------- /example/lonely.wiki: -------------------------------------------------------------------------------- 1 | === Lonly === 2 | * list 1 3 | * list 1-1 4 | * list 1-2 5 | * list 2 6 | -------------------------------------------------------------------------------- /example/sub/sub_link_1.wiki: -------------------------------------------------------------------------------- 1 | === Sub Link 1 === 2 | * list 1 3 | * list 1-1 4 | * list 1-2 5 | * list 2 6 | link to index 7 | [[../index]] 8 | * list 3 9 | * list 3-1 10 | * list 3-2 11 | -------------------------------------------------------------------------------- /vimwiki2org.pl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/perl -w 2 | 3 | # vimwiki2org.pl --- 4 | 5 | # Filename: vimwiki2org.pl 6 | # Description: 7 | # Author: Xu FaSheng 8 | # Maintainer: Xu FaSheng 9 | # Created: Sat Sep 29 17:39:10 2012 (+0800) 10 | # Keywords: vimwiki org org-mode 11 | # Compatibility: 12 | # test ok on vimwiki 2.0.1.stu 13 | # need perl 5.14 14 | 15 | # Commentary: 16 | # 17 | # This is a simple tool to convert vimwiki files to emacsorg-mode file, 18 | # and the usage is simple: 19 | # perl vimwiki2org.pl index.wiki > vimwiki.org 20 | # perl vimwiki2org.pl -t tag1:tag2 -l log.txt -- index.wiki > vimwiki.org 21 | # 22 | # more options to see help 23 | # perl vimwiki2org.pl --help 24 | # 25 | # more conversion rules to see man page 26 | # perl vimwiki2org.pl --man 27 | # 28 | 29 | # Change Log: 30 | # - 0.2 31 | # + [add] README.markdown 32 | # + [add] Makefile 33 | # - 0.1 34 | # + create 35 | # 36 | # 37 | # This program is free software; you can redistribute it and/or 38 | # modify it under the terms of the GNU General Public License as 39 | # published by the Free Software Foundation; either version 3, or 40 | # (at your option) any later version. 41 | # 42 | # This program is distributed in the hope that it will be useful, 43 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 44 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 45 | # General Public License for more details. 46 | # 47 | # You should have received a copy of the GNU General Public License 48 | # along with this program; see the file COPYING. If not, write to 49 | # the Free Software Foundation, Inc., 51 Franklin Street, Fifth 50 | # Floor, Boston, MA 02110-1301, USA. 51 | # 52 | # 53 | 54 | # Code: 55 | 56 | 57 | use 5.014; 58 | use autodie qw/open close/; 59 | use List::Util qw/first/; 60 | use File::Basename; 61 | use File::Spec; 62 | use Cwd 'abs_path'; 63 | use Getopt::Long; 64 | use Pod::Usage; 65 | use File::Find; 66 | no if $] >= 5.017011, warnings => 'experimental::smartmatch'; 67 | 68 | ## global variables 69 | my $program_name="vimwiki2org.pl"; 70 | my $version_msg = "$program_name version 0.2"; 71 | 72 | my @dispatched_files; 73 | my @open_error_files; 74 | my @lost_files; 75 | my $org_comment = "#"; 76 | 77 | # links in vimwiki 78 | # such as '[[file]] [[file|name]]' 79 | my $link_regexp = '\[\[(.*?)\]\]'; 80 | 81 | # comment and placeholder of vimwiki, which all start with "%" 82 | # such as "%title", "%% comment" 83 | my $comment_regexp = '^\s*%'; 84 | 85 | # headers in vimwiki 86 | # such as "=== header ===" 87 | my $header_regexp = '^(?:\s*)(=+) *(.*\S) *\1(?:\s*)$'; 88 | 89 | # lists in vimwiki 90 | # such as "* list", "- list", "# list" 91 | my $list_regexp = '^(\s*)([#*-]) (.*)$'; 92 | 93 | # plain text in vimwiki 94 | my $plain_regexp = '^(\s*)(.*)$'; 95 | 96 | # source(preformat) block in vimwiki 97 | # such as '{{{', '{{{sh', '}}}' 98 | my $src_block_begin_with_type_regexp = '^\s*{{{\s*\S+.*$'; 99 | my $src_block_begin_no_type_regexp = '^\s*{{{\s*$'; 100 | my $src_block_end_regexp = '^\s*}}}\s*$'; 101 | 102 | # other 103 | my $log_fh; 104 | 105 | # options 106 | my $org_file_tags = "vimwiki"; # add org file tags 107 | my $diary_relative_index_file = "diary/diary.wiki"; 108 | my $log_file = "/tmp/vimwiki2org.log"; 109 | my $vimwiki_ext = '.wiki'; 110 | my $ignore_lonely_header = 1; 111 | my $untyped_preformat_block_convert_type = undef; 112 | my $dispatch_lost_files = ''; 113 | 114 | ## main loop 115 | my $man = 0; 116 | my $help = 0; 117 | my $version = 0; 118 | $Getopt::Long::ignorecase = 0; 119 | GetOptions( 120 | 'file-tags|t:s' => \$org_file_tags, 121 | 'no-file-tags|no-t' => sub{ $org_file_tags='' }, 122 | 'diary-relative-file|d:s' => \$diary_relative_index_file, 123 | 'no-diary-relative-file|no-d' => sub{ $diary_relative_index_file='' }, 124 | 'log-file|l:s' => \$log_file, 125 | 'no-log-file|no-l:s' => sub{ $log_file='' }, 126 | 'vimwiki-ext|e:s' => \$vimwiki_ext, 127 | 'lost-files|L:s' => \$dispatch_lost_files, 128 | 'ignore-lonely-header|i!' => \$ignore_lonely_header, 129 | 'untyped-preformat-block-convert-type|u:s' => \$untyped_preformat_block_convert_type, 130 | 'help|h' => \$help, 131 | 'man|m' => \$man, 132 | 'version|v' => \$version 133 | ) or pod2usage(1); 134 | pod2usage(1) if $help; 135 | pod2usage(-verbose => 2) if $man; 136 | if ($version) { 137 | say $version_msg; 138 | exit; 139 | } 140 | 141 | &open_log(); 142 | if ($org_file_tags) { 143 | say $org_comment, '+FILETAGS: :', $org_file_tags, ":\n"; 144 | } 145 | my $enabled_diary_index_file = 1; 146 | $enabled_diary_index_file=0 if $diary_relative_index_file eq ''; 147 | my $diary_index_file; 148 | foreach (@ARGV) { 149 | # the vimwiki index file 150 | my $index_file = $_; 151 | $index_file = File::Spec->canonpath($index_file); 152 | 153 | if ($enabled_diary_index_file) { 154 | # the vimwiki diary index file, this file will dispatched at end 155 | my $vimwiki_base_dir = dirname $index_file; 156 | $diary_index_file = &build_path($vimwiki_base_dir, 157 | $diary_relative_index_file); 158 | } 159 | &open_and_dispatch($index_file); 160 | &open_and_dispatch($diary_index_file) if $enabled_diary_index_file; 161 | } 162 | if ($dispatch_lost_files eq 'check') { 163 | &check_lost_files(); 164 | } elsif ($dispatch_lost_files eq 'fix') { 165 | &check_lost_files(); 166 | &fix_lost_files(); 167 | } 168 | &close_log(); 169 | 170 | sub build_path { 171 | my $path = File::Spec->catfile(@_); 172 | &clean_path($path); 173 | } 174 | 175 | sub clean_path { 176 | my $path = shift; 177 | # clean up path, remove "./" 178 | $path = File::Spec->canonpath($path); 179 | # clean up path, remove "../" 180 | if ($path =~ /\.\./) { 181 | my $abs_path = abs_path($path); 182 | if (defined $abs_path) { 183 | $path = File::Spec->abs2rel($abs_path); 184 | } 185 | } 186 | $path; 187 | } 188 | 189 | sub open_log { 190 | return unless $log_file; 191 | eval {open $log_fh, ">", $log_file}; 192 | warn $@ if ($@); 193 | &append_log("# MESSAGES"); 194 | } 195 | 196 | sub append_log { 197 | return unless defined $log_fh; 198 | my $msg = shift; 199 | say {$log_fh} $msg if (fileno $log_fh); 200 | } 201 | 202 | sub close_log { 203 | &append_log("\n# DISPATCHED FILES"); 204 | &append_log($_) foreach (@dispatched_files); 205 | &append_log("\n# OPEN FAILED FILES"); 206 | &append_log($_) foreach (@open_error_files); 207 | if ($dispatch_lost_files) { 208 | &append_log("\n# LOST FILES"); 209 | &append_log($_) foreach (@lost_files); 210 | } 211 | eval {close $log_fh}; 212 | } 213 | 214 | my @found_files; 215 | sub check_lost_files { 216 | my @base_dir; 217 | push @base_dir, dirname $_ foreach @ARGV; 218 | @found_files = (); 219 | find(sub {push @found_files, $File::Find::name if /$vimwiki_ext$/;}, @base_dir); 220 | $_ = &clean_path($_) foreach @found_files; 221 | foreach (@found_files) { 222 | if (not @dispatched_files ~~ /^\Q$_\E$/) { 223 | push @lost_files, $_ if (not @lost_files ~~ /^\Q$_\E$/); 224 | } 225 | } 226 | } 227 | 228 | sub fix_lost_files { 229 | my $org_parent_lv = 0; 230 | { 231 | my $org_headline_text = "fixed lost files"; 232 | my $org_headline_lv = $org_parent_lv + 1; 233 | say &build_org_headline($org_headline_text, $org_headline_lv); 234 | $org_parent_lv++; 235 | } 236 | open_and_dispatch($_, $org_parent_lv) foreach @lost_files; 237 | } 238 | 239 | # main function 240 | sub open_and_dispatch { 241 | my $vimwiki_file = shift; 242 | my $org_parent_lv = shift @_ // 0; 243 | 244 | # ignore repeated files 245 | return if (@dispatched_files ~~ /^\Q$vimwiki_file\E$/); 246 | push @dispatched_files, $vimwiki_file; 247 | 248 | my @content; 249 | eval { 250 | open my $fh, "<", $vimwiki_file; 251 | @content = <$fh>; 252 | close $fh; 253 | }; 254 | if ($@) { 255 | # catch error 256 | push @open_error_files, $vimwiki_file; 257 | &append_log($@); 258 | return; 259 | } 260 | chomp(@content); 261 | 262 | # convert filename as the main org headline first 263 | { 264 | my $org_headline_text = basename $vimwiki_file; 265 | $org_headline_text =~ s/\.[^.]+$//; # remove file extension name 266 | my $org_headline_lv = $org_parent_lv + 1; 267 | say &build_org_headline($org_headline_text, $org_headline_lv); 268 | 269 | $org_parent_lv++; 270 | } 271 | 272 | # count headers in vimwiki 273 | my $header_count = grep /$header_regexp/, @content; 274 | 275 | # find the minimum header level in current file, and set it as 276 | # the start org level, for example, the minimus header level is 3, 277 | # like "=== header ===", if not think of its parent level, it will be 278 | # convert to a org header as level 1, like "* header" 279 | my @headers = grep /$header_regexp/, @content; 280 | my $min_header_lv = 0; 281 | if (@headers) { 282 | $min_header_lv = length((shift @headers) =~ s/$header_regexp/$1/r); 283 | foreach (@headers) { 284 | my $header_lv = length(s/$header_regexp/$1/r); 285 | if ($header_lv < $min_header_lv) { 286 | $min_header_lv = $header_lv; 287 | } 288 | } 289 | } 290 | 291 | ## dispatch the content 292 | 293 | # links under current org headline 294 | my @collected_links = (); 295 | my $last_org_headline_lv = $org_parent_lv; 296 | 297 | my $org_parent_lv_for_following_list = $org_parent_lv; 298 | # remember the first list item's prefix space count in a header 299 | # use this to judge if a list is first level under the header 300 | my $first_list_pre_spc_count = undef; 301 | 302 | # a marker to dispatch source code, select is is "#+begin_src" or "#+begin_example" 303 | my $last_begin_as_src; 304 | my $under_src_block; 305 | 306 | foreach(@content) { 307 | # my $exist_link_in_cur_line = 0; 308 | given ($_) { 309 | # comment and placeholder of vimwiki, which all start with "%" 310 | when (/$comment_regexp/) { 311 | say "# ", $_; 312 | } 313 | 314 | # links in vimwiki 315 | when (/$link_regexp/) { 316 | continue if $under_src_block; 317 | $_ = &convert_link_format($_); 318 | continue; 319 | } 320 | 321 | # headers in vimwiki 322 | when (/$header_regexp/) { 323 | continue if $under_src_block; 324 | &expand_collected_links(\@collected_links, $last_org_headline_lv); 325 | 326 | # reset markers 327 | $first_list_pre_spc_count = undef; 328 | 329 | if ($ignore_lonely_header) { 330 | # if there is only one header, ignore it 331 | if ($header_count <= 1) { 332 | say $org_comment, $_; 333 | break; 334 | } 335 | } 336 | 337 | # output as a org headline 338 | my $header_lv = length(s/$header_regexp/$1/r); 339 | my $header_text = s/$header_regexp/$2/r; 340 | my $org_headline_lv = 341 | &compute_org_headline_lv($header_lv, $min_header_lv, 342 | $org_parent_lv); 343 | say &build_org_headline($header_text, $org_headline_lv); 344 | 345 | # mark current header's org level as 346 | # the following list's parent org level 347 | $org_parent_lv_for_following_list = $org_headline_lv; 348 | $last_org_headline_lv = $org_headline_lv; 349 | } 350 | 351 | # lists in vimwiki 352 | when (/$list_regexp/) { 353 | continue if $under_src_block; 354 | my $list_pre_spc_count = length(s/$list_regexp/$1/r); 355 | my $list_text = s/$list_regexp/$3/r; 356 | if (!defined($first_list_pre_spc_count)) { 357 | $first_list_pre_spc_count = $list_pre_spc_count; 358 | } 359 | if ($list_pre_spc_count <= $first_list_pre_spc_count) { 360 | # this list item could be convert to a org headline 361 | &expand_collected_links(\@collected_links, $last_org_headline_lv); 362 | 363 | my $org_headline_lv = $org_parent_lv_for_following_list + 1; 364 | 365 | # convert TODO state, '[X]' -> 'DONE', '[.]' or '[ ]' -> 'TODO' 366 | my $org_headline_text = $list_text =~ s/\[X\]/DONE/r; 367 | $org_headline_text = $org_headline_text =~ s/\[.\]/TODO/r; 368 | say &build_org_headline($org_headline_text, $org_headline_lv); 369 | $last_org_headline_lv = $org_headline_lv; 370 | } else { 371 | # this list item could be convert to a org plain list 372 | # simple align text, and convert all item placeholder to '-' 373 | # such as '[#-*] ' -> '- ' 374 | my $aligned_pre_spc_count = $list_pre_spc_count 375 | - $first_list_pre_spc_count 376 | + $org_parent_lv_for_following_list; 377 | my $aligned_pre_spc = " " x $aligned_pre_spc_count; 378 | s/$list_regexp/$aligned_pre_spc- $3/; 379 | say; 380 | } 381 | } 382 | 383 | # plain text 384 | default { 385 | # convert source code: 386 | # '{{{' -> '#+begin_example', '{{{sh' -> '#+begin_src sh' 387 | # '{{{class="brush: sh"' -> '#+begin_src sh' 388 | if (/$src_block_begin_with_type_regexp/) { 389 | # source code with type 390 | if (/class=/) { 391 | s/{{{.*:\s*(\S+)\s*"\s*$/#+begin_src $1/; 392 | } else { 393 | s/{{{\s*(\S+)\s*$/#+begin_src $1/; 394 | } 395 | 396 | $last_begin_as_src = 1; 397 | $under_src_block = 1; 398 | } elsif (/$src_block_begin_no_type_regexp/) { 399 | # source code without type 400 | if (defined $untyped_preformat_block_convert_type) { 401 | s/{{{\s*/#+begin_src $untyped_preformat_block_convert_type/; 402 | $last_begin_as_src = 1; 403 | } else { 404 | s/{{{\s*/#+begin_example/; 405 | $last_begin_as_src = 0; 406 | } 407 | $under_src_block = 1; 408 | } elsif (/$src_block_end_regexp/) { 409 | if ($last_begin_as_src) { 410 | s/}}}\s*/#+end_src/; 411 | } else { 412 | s/}}}\s*/#+end_example/; 413 | } 414 | $under_src_block = 0; 415 | } 416 | 417 | say; 418 | }; 419 | } # given-when end 420 | 421 | # collect links in current line, and these links will be expand 422 | # before next org headline output, in another hand, they will 423 | # be append at the end of current org headline(if exist) 424 | &collect_links($_, \@collected_links, $vimwiki_file) if (!$under_src_block); 425 | } # foreach end 426 | 427 | # end of file, expand collected_links in last org headline 428 | &expand_collected_links(\@collected_links, $last_org_headline_lv); 429 | } 430 | 431 | sub compute_org_headline_lv { 432 | my $header_lv = shift; 433 | my $min_header_lv = shift; 434 | my $org_parent_lv = shift; 435 | 436 | my $org_headline_lv = $header_lv - $min_header_lv + 1; 437 | $org_headline_lv += $org_parent_lv; 438 | if ($org_headline_lv <= 0) {$org_headline_lv = 1;} 439 | $org_headline_lv; 440 | } 441 | 442 | sub build_org_headline { 443 | my $org_headline_text = shift; 444 | my $org_headline_lv = shift; 445 | my $org_headline = "*" x $org_headline_lv . " " . $org_headline_text; 446 | } 447 | 448 | # convert link format to org type 449 | sub convert_link_format { 450 | my $line = shift; 451 | my @links = $line =~ m/$link_regexp/g; 452 | foreach (@links) { 453 | my $link_content = ""; 454 | my $link_des = ""; 455 | if (index($_, "|") >= 0) { 456 | # exist description in vimwiki link, split it 457 | $link_content = s/\|.*//r; 458 | $link_des = s/.*\|//r; 459 | } else { 460 | # there is no description 461 | $link_content = $_; 462 | } 463 | if (!length $link_content) {next;} 464 | 465 | # convert link format to org type 466 | # such as '[[folder/file|des]]' -> '[[file][des]]' 467 | # or '[[http://url|des]]' -> '[[http://url][des]]' 468 | my $org_link_name; 469 | if ($link_content =~ m{.+://}) { 470 | # url link 471 | $org_link_name = $link_content; 472 | } else { 473 | # file link 474 | $org_link_name = basename $link_content; 475 | } 476 | if (length $link_des) { 477 | $line =~ s/\[\[\Q$_\E\]\]/\[\[$org_link_name\]\[$link_des\]\]/g; 478 | } else { 479 | $line =~ s/\[\[\Q$_\E\]\]/\[\[$org_link_name\]\]/g; 480 | } 481 | } 482 | $line; 483 | } 484 | # collect links under current org headline 485 | sub collect_links { 486 | my $line = shift; 487 | my $collected_links_ref = shift; 488 | my $cur_vimwiki_file = shift; 489 | my $cur_vimwiki_dir = dirname $cur_vimwiki_file; 490 | 491 | my @links = $line =~ m/$link_regexp/g; 492 | foreach (@links) { 493 | my $link_content = ""; 494 | my $link_des = ""; 495 | if (index($_, "|") >= 0) { 496 | $link_content = s/\|.*//r; 497 | $link_des = s/.*\|//r; 498 | } else { 499 | $link_content = $_; 500 | } 501 | if (!length $link_content) {next;} 502 | if ($link_content =~ m{.+://}) {next;} 503 | 504 | # collect the link file's complete path 505 | $link_content .= $vimwiki_ext; 506 | $link_content = &build_path($cur_vimwiki_dir, $link_content); 507 | if(not @$collected_links_ref ~~ /^\Q$link_content\E$/) { 508 | push @$collected_links_ref, $link_content; 509 | } 510 | } 511 | } 512 | 513 | # expand collected links in current org headline 514 | # before start a new org headline 515 | sub expand_collected_links { 516 | my $collected_links_ref = shift; 517 | my $org_parent_lv = shift; 518 | 519 | foreach (@$collected_links_ref) { 520 | if ($enabled_diary_index_file) { 521 | # not expand diary index file here, it will be dispatch at end 522 | if ($_ ne $diary_index_file) { 523 | open_and_dispatch($_, $org_parent_lv); 524 | } else { 525 | &append_log("find diary index file as a link: " . $_); 526 | } 527 | } else { 528 | open_and_dispatch($_, $org_parent_lv); 529 | } 530 | } 531 | 532 | # empty collected links 533 | @$collected_links_ref = (); 534 | } 535 | 536 | __END__ 537 | 538 | =head1 NAME 539 | 540 | vimwiki2org.pl - a simple tool to convert vimwiki to emacs org-mode 541 | 542 | =head1 SYNOPSIS 543 | 544 | vimwiki2org.pl index.wiki [file ...] 545 | 546 | vimwiki2org.pl [options] -- index.wiki [file ...] 547 | 548 | =head1 OPTIONS 549 | 550 | =over 8 551 | 552 | =item B<-t>, B<--file-tags>=tag1:tag2, B<--no-t>, B<--no-file-tags> 553 | 554 | set the org-mode's file tags(#+FILETAGS), if is empty or 555 | use B<--no-file-tags> will not insert file tags 556 | (B value is: "vimwiki") 557 | 558 | =item B<-d>, B<--diary-relative-file>=diary.wiki, B<--no-d>, 559 | B<--no-diary-relative-file> 560 | 561 | set the diary index file's path relative to the file in options, 562 | and the diary index file will be append after the file 563 | as a level 1 org-mode headline, if existed. 564 | if is empty or use B<--no-diary-relative-file>, will not dispatch diary 565 | index file specially 566 | (B value is: "diary/diary.wiki") 567 | 568 | =item B<-l>, B<--log-file>=log.txt, B<--no-l>, B<--no-log-file> 569 | 570 | set the log file, if is empty or use B<--no-log-file> will not write log, 571 | the log file contain three sections:'MESSAGES', 'DISPATCHED FILES', 572 | 'OPEN FAILED FILES' 573 | (B value is: "/tmp/vimwiki2org.log") 574 | 575 | =item B<-L>, B<--lost-files>=|B> 576 | 577 | set if check or fix the vimwiki files which is not dispatched and in the same 578 | folder or sub folders with the file(s) in options, if use 'B', will 579 | append a new section named 'LOST FILES' in log file, if use 'B', will 580 | append the lost files' content under level 1 headline named "lost files" 581 | (B value is: ) 582 | 583 | =item B<-e>, B<--vimwiki-ext>=.wiki 584 | 585 | set the default vimwiki's file extension name, used to build link's file name 586 | (B value is: ".wiki") 587 | 588 | =item B<-i>, B<--ignore-lonely-header>, B<--no-i>, B<--no-ignore-lonely-header> 589 | 590 | set if ignore the only one header in a vimwiki file, because we have 591 | set its filename as a parent org-mode headline 592 | (B option is: B<--ignore-lonely-header>) 593 | 594 | =item B<-u>, B<--untyped-preformat-block-convert-type>=perl 595 | 596 | set the untyped preformat block's type after converting, vimwiki's preformat 597 | block is code which in 'B<{{{...}}}>', some with a type such as 598 | 'B<{{{sh...>', another without a type such as 'B<{{{...>', default, a 599 | typed preformat code will convert to as org source block, such as 600 | 'B<#+', an untyped preformat code will convert to as org 601 | example block, such as 'B<#+begin_example>', if you use 602 | B<--untyped-preformat-block-convert-type>, the untyped preformat block will 603 | convert to as org source block with your defined type, too 604 | (B value is: ) 605 | 606 | =item B<-h>, B<--help> 607 | 608 | prints a brief help message and exits 609 | 610 | =item B<-m>, B<--man> 611 | 612 | prints the manual page and exits 613 | 614 | =item B<-v>, B<--version> 615 | 616 | prints the version information and exits 617 | 618 | =back 619 | 620 | =head1 DESCRIPTION 621 | 622 | B will read the given input vimwiki file(s) and convert to 623 | org-mode and output, here is the conversion rules: 624 | 625 | =over 4 626 | 627 | =item * B and B in vimwiki, which start with 'B<%>' 628 | 629 | all will be treat as org-mode's comment line which start with 'B<#>', 630 | B: 631 | 632 | 'B<%title>' =>'B<#%title>', 'B<%% comment>' => 'B<#%% comment>' 633 | 634 | =item * B in vimwiki, which surround with 'B<=>' 635 | 636 | will be treat as org-mode's headline, and the headline level should be compute 637 | as followed steps: 638 | first, if you enable option 'B<--ignore-lonely-header>'(default is enabled), 639 | and there is only one header in the file, just ignore the header, 640 | and comment the line, because a file always has a overall parent headline 641 | which named as the file name, another overall parent headline is redundant; 642 | second, find the minimum header level in file, and set it as 643 | the start level, B, the minimus header level is 3, 644 | if not think of its parent level, it will be 645 | convert to a org headline as level 1, 646 | 647 | 'B<=== header ===>' => 'B<* header>' 648 | 649 | =item * B item in vimwiki, which start with 'B<->' 'B<*>' 'B<#>' 650 | 651 | the first level list item under a org-mode headline, will be convert to 652 | headline, just like header in vimwiki, 653 | and the TODO tag will convert to org-mode's TODO state, 654 | B: 655 | 656 | 'B<- list item>' => 'B<* list item>', 657 | 658 | 'B<* [ ] list item>' => 'B<* TODO list item>', 659 | 660 | 'B<# [X] list item>' => 'B<* DONE list item>' 661 | 662 | the sub level list item will be convert to standard org-mode plain list, 663 | which start with 'B<->', 664 | B: 665 | 666 | 'B<- list item>' => 'B<- list item>' 667 | 668 | 'B<* [ ] list item>' => 'B<- [ ] list item>' 669 | 670 | =item * B in vimwiki, which surround with 'B<[[>' 'B<]]>' 671 | 672 | links will be treated as two kinds: file link(link to other vimwiki file), 673 | and url link(http://, https://, ftp:// and so on). 674 | first, convert the links to org-mode format, B: 675 | 676 | 'B<[[path/file]]>' => 'B<[[file]]>' 677 | 678 | 'B<[[file|des]]>' => 'B<[[file][des]]>' 679 | 680 | 'B<[[http://url|des]]>' => 'B<[[http://url][des]]>' 681 | 682 | then, collect all the file links under the current org-mode headline, 683 | and before next headline, expand these links, for each file, if 684 | not dispatched yet, and is not the diary index file, will be append as a child 685 | headline, named with the file name 686 | 687 | =item * B(B) block in vimwiki, which surround with 'B<{{{>' 'B<}}}>' 688 | 689 | preformat block will be convert to example or source block in org-mode, 690 | if the preformat block with a type, such as 'perl' or 'python', it will 691 | convert to source block, if no, will convert to a example block, but if 692 | you use the option 'B<--untyped-preformat-block-convert-type>', the 693 | untyped preformat block will convert to source block with your defined type, 694 | B: 695 | 696 | 'B<{{{...}}}>' => 'B<#+begin_example...#+end_example>' 697 | 'B<{{{perl...}}}>' => 'B<#+begin_src perl...#+end_src>' 698 | 'B<{{{class="brush: sh...}}}">' => 'B<#+begin_src sh...#+end_src>' 699 | 700 | and all the text under the prefomat block will be treat as plain text 701 | 702 | =item * B text in vimwiki 703 | 704 | just output without change 705 | 706 | =item * B vimwiki files as options 707 | 708 | each file will be append as a level 1 org-mode headline, named with the file name, 709 | unless the later file is dispatched as a link in the earlier file 710 | 711 | =item * B index file in vimwiki 712 | 713 | each file in options should has a diary index file, default is 'B' relative to the file's path, it will be append after the file, 714 | as a level 1 org-mode headline, if existed 715 | 716 | =back 717 | 718 | =head1 EXAMPLES 719 | 720 | perl vimwiki2org.pl example/index.wiki > vimwiki.org 721 | 722 | perl vimwiki2org.pl -d diary/diary.wiki -l log.txt -L=check -- index.wiki > vimwiki.org 723 | 724 | =head1 AUTHOR 725 | 726 | Written by Xu FaSheng. 727 | 728 | =head1 COPYRIGHT 729 | 730 | This program is free software; you can redistribute it and/or 731 | modify it under the terms of the GNU General Public License as 732 | published by the Free Software Foundation; either version 3, or 733 | (at your option) any later version. 734 | 735 | =cut 736 | --------------------------------------------------------------------------------