├── Support ├── working_copy.rb ├── cvs_diff.rb ├── diff.rb ├── Stylesheets │ └── cvs_style.css ├── versioned_file.rb ├── cvs_commit.rb └── lib │ └── Builder.rb ├── README.mdown ├── Commands ├── Log.plist ├── Annotate.plist ├── Revert.plist ├── Commit....plist ├── Diff With Newest (HEAD).plist ├── Diff With Working Copy (BASE).plist ├── Add to Repository.plist ├── Diff With Previous Revision (PREV).plist ├── Reset sticky tags.plist ├── Annotate Line.plist ├── Update with tag....plist ├── Merge from tag....plist ├── Check out Revision....plist └── Diff With Revision....plist └── info.plist /Support/working_copy.rb: -------------------------------------------------------------------------------- 1 | module CVS 2 | class WorkingCopy < VersionedFile 3 | def initialize(path) 4 | @path = (path =~ %r{/$}) ? path : "#{path}/" # add / if not there 5 | end 6 | 7 | def dirname 8 | @path 9 | end 10 | 11 | def basename 12 | '.' 13 | end 14 | 15 | def status 16 | cvs(:update, :pretend => true, :quiet => true).inject({}) do |files,line| 17 | files.update($1 => status_from_line(line)) if line =~ /^\S (.*)$/ 18 | files 19 | end 20 | end 21 | end 22 | end -------------------------------------------------------------------------------- /Support/cvs_diff.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby18 -w 2 | # encoding: utf-8 3 | 4 | $LOAD_PATH << ENV['TM_SUPPORT_PATH'] + "/lib" 5 | require 'progress' 6 | require 'versioned_file' 7 | 8 | module CVS 9 | def CVS.diff_active_file(revision, command) 10 | target_path = ENV['TM_FILEPATH'] 11 | output_path = File.basename(target_path) + ".diff" 12 | 13 | TextMate::call_with_progress(:title => command, 14 | :message => "Accessing CVS Repository…", 15 | :output_filepath => output_path) do 16 | have_data = false 17 | 18 | # idea here is to stream the data rather than submit it in one big block 19 | VersionedFile.diff(target_path, revision).each_line do |line| 20 | have_data = true unless line.empty? 21 | puts line 22 | end 23 | 24 | if not have_data then 25 | # switch to tooltip output to report lack of differences 26 | puts "No differences found." 27 | exit 206; 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /README.mdown: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | You can install this bundle in TextMate by opening the preferences and going to the bundles tab. After installation it will be automatically updated for you. 4 | 5 | # General 6 | 7 | * [Bundle Styleguide](http://kb.textmate.org/bundle_styleguide) — _before you make changes_ 8 | * [Commit Styleguide](http://kb.textmate.org/commit_styleguide) — _before you send a pull request_ 9 | * [Writing Bug Reports](http://kb.textmate.org/writing_bug_reports) — _before you report an issue_ 10 | 11 | # License 12 | 13 | If not otherwise specified (see below), files in this repository fall under the following license: 14 | 15 | Permission to copy, use, modify, sell and distribute this 16 | software is granted. This software is provided "as is" without 17 | express or implied warranty, and with no claim as to its 18 | suitability for any purpose. 19 | 20 | An exception is made for files in readable text which contain their own license information, or files where an accompanying file exists (in the same directory) with a “-license” suffix added to the base-name name of the original file, and an extension of txt, html, or similar. For example “tidy” is accompanied by “tidy-license.txt”. -------------------------------------------------------------------------------- /Commands/Log.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env bash 9 | 10 | cd "$TM_DIRECTORY" && 11 | "${TM_CVS:=cvs}" log "$TM_FILENAME" 12 | input 13 | none 14 | inputFormat 15 | text 16 | keyEquivalent 17 | ^Z 18 | name 19 | Log 20 | outputCaret 21 | afterOutput 22 | outputFormat 23 | text 24 | outputLocation 25 | newWindow 26 | requiredCommands 27 | 28 | 29 | command 30 | cvs 31 | locations 32 | 33 | /opt/local/bin/cvs 34 | /usr/local/bin/cvs 35 | 36 | variable 37 | TM_CVS 38 | 39 | 40 | uuid 41 | 9EA691A5-A166-4D8F-955F-270490F02827 42 | version 43 | 2 44 | 45 | 46 | -------------------------------------------------------------------------------- /Commands/Annotate.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env bash 9 | 10 | cd "$TM_DIRECTORY" && 11 | "${TM_CVS:=cvs}" annotate "$TM_FILENAME" 12 | input 13 | none 14 | inputFormat 15 | text 16 | keyEquivalent 17 | ^Z 18 | name 19 | Annotate 20 | outputCaret 21 | afterOutput 22 | outputFormat 23 | text 24 | outputLocation 25 | newWindow 26 | requiredCommands 27 | 28 | 29 | command 30 | cvs 31 | locations 32 | 33 | /opt/local/bin/cvs 34 | /usr/local/bin/cvs 35 | 36 | variable 37 | TM_CVS 38 | 39 | 40 | uuid 41 | 338A3670-DA8E-4036-87E0-DF2E212254C8 42 | version 43 | 2 44 | 45 | 46 | -------------------------------------------------------------------------------- /Support/diff.rb: -------------------------------------------------------------------------------- 1 | module CVS 2 | class Diff 3 | attr_accessor :raw 4 | 5 | def initialize(raw) 6 | @raw = raw 7 | end 8 | 9 | def changes(reload=false) 10 | @changes = nil if reload 11 | @changes ||= @raw.split(/\n/)[5..-1].select { |l| l =~ /^\d/ }.inject({}) do |changes,line| 12 | changes.merge change_from_hunk_header(line) 13 | end rescue [] 14 | end 15 | 16 | # def source_line_status(n) 17 | # changes.find { |(lines,status)| lines.include?(n) }.last rescue :unchanged 18 | # end 19 | 20 | def source_line(n) 21 | m = n 22 | changes.each do |(lines,status)| 23 | case status 24 | when :added 25 | return :added if lines.include?(n) 26 | m -= lines.to_a.length if lines.end < n 27 | when :deleted 28 | m += lines.to_a.length if lines.begin <= n 29 | end 30 | end 31 | m 32 | end 33 | 34 | private 35 | 36 | def change_from_hunk_header(line) 37 | case line 38 | when /^(\d+),(\d+)d(\d+)$/ 39 | {($1.to_i)..($2.to_i) => :deleted} 40 | when /^(\d+)a(\d+),(\d+)$/ 41 | {($2.to_i)..($3.to_i) => :added} 42 | when /^(\d+)c(\d+)/ 43 | {($1.to_i)..($1.to_i) => :changed} 44 | end 45 | end 46 | end 47 | end -------------------------------------------------------------------------------- /Commands/Revert.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env bash 9 | 10 | rm "$TM_FILEPATH" && 11 | cd "$TM_DIRECTORY" && 12 | "${TM_CVS:=cvs}" update "$TM_FILENAME" 13 | 14 | input 15 | none 16 | inputFormat 17 | text 18 | keyEquivalent 19 | ^Z 20 | name 21 | Revert 22 | outputCaret 23 | afterOutput 24 | outputFormat 25 | text 26 | outputLocation 27 | toolTip 28 | requiredCommands 29 | 30 | 31 | command 32 | cvs 33 | locations 34 | 35 | /opt/local/bin/cvs 36 | /usr/local/bin/cvs 37 | 38 | variable 39 | TM_CVS 40 | 41 | 42 | uuid 43 | 20865252-80D2-4CA4-9834-391D09210C4F 44 | version 45 | 2 46 | 47 | 48 | -------------------------------------------------------------------------------- /Commands/Commit....plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env bash 9 | 10 | export CommitWindow="$TM_SUPPORT_PATH/bin/CommitWindow.app/Contents/MacOS/CommitWindow" 11 | 12 | cd "${TM_PROJECT_DIRECTORY:-$TM_DIRECTORY}" 13 | ruby18 -- "${TM_BUNDLE_SUPPORT}/cvs_commit.rb" 14 | input 15 | none 16 | inputFormat 17 | text 18 | keyEquivalent 19 | ^Z 20 | name 21 | Commit... 22 | outputCaret 23 | afterOutput 24 | outputFormat 25 | html 26 | outputLocation 27 | newWindow 28 | requiredCommands 29 | 30 | 31 | command 32 | cvs 33 | locations 34 | 35 | /opt/local/bin/cvs 36 | /usr/local/bin/cvs 37 | 38 | variable 39 | TM_CVS 40 | 41 | 42 | uuid 43 | BE6728A5-AFC4-4D98-9EC7-C2E951483B71 44 | version 45 | 2 46 | 47 | 48 | -------------------------------------------------------------------------------- /Commands/Diff With Newest (HEAD).plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env bash 9 | 10 | ruby18 <<'END' 11 | 12 | ENV['CVS_PATH'] = ENV['TM_CVS'] 13 | $LOAD_PATH << ENV['TM_BUNDLE_SUPPORT'] 14 | require 'cvs_diff' 15 | 16 | CVS::diff_active_file(:head, "Diff With Newest") 17 | END 18 | 19 | input 20 | none 21 | inputFormat 22 | text 23 | keyEquivalent 24 | ^Z 25 | name 26 | Diff With Newest (HEAD) 27 | outputCaret 28 | afterOutput 29 | outputFormat 30 | text 31 | outputLocation 32 | newWindow 33 | requiredCommands 34 | 35 | 36 | command 37 | cvs 38 | locations 39 | 40 | /opt/local/bin/cvs 41 | /usr/local/bin/cvs 42 | 43 | variable 44 | TM_CVS 45 | 46 | 47 | uuid 48 | 22FC4CAB-4664-4CFC-BC8E-C2294616E464 49 | version 50 | 2 51 | 52 | 53 | -------------------------------------------------------------------------------- /Commands/Diff With Working Copy (BASE).plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env bash 9 | 10 | ruby18 <<'END' 11 | 12 | ENV['CVS_PATH'] = ENV['TM_CVS'] 13 | $LOAD_PATH << ENV['TM_BUNDLE_SUPPORT'] 14 | require 'cvs_diff' 15 | 16 | CVS::diff_active_file(:base, "Diff With Working Copy") 17 | END 18 | 19 | input 20 | none 21 | inputFormat 22 | text 23 | keyEquivalent 24 | ^Z 25 | name 26 | Diff With Working Copy (BASE) 27 | outputCaret 28 | afterOutput 29 | outputFormat 30 | text 31 | outputLocation 32 | newWindow 33 | requiredCommands 34 | 35 | 36 | command 37 | cvs 38 | locations 39 | 40 | /opt/local/bin/cvs 41 | /usr/local/bin/cvs 42 | 43 | variable 44 | TM_CVS 45 | 46 | 47 | uuid 48 | 00C541DE-9A5C-4C59-A075-E754BAEB25C2 49 | version 50 | 2 51 | 52 | 53 | -------------------------------------------------------------------------------- /Commands/Add to Repository.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env bash 9 | 10 | ruby18 -r "$TM_SUPPORT_PATH/lib/shelltokenize.rb" <<END 11 | cvs = ENV['TM_CVS'] || "cvs" 12 | TextMate.selected_paths_array.each do |path| 13 | puts %x{cd "#{File.dirname path}" && #{cvs} add "#{File.basename path}"} 14 | end 15 | END 16 | input 17 | none 18 | inputFormat 19 | text 20 | keyEquivalent 21 | ^Z 22 | name 23 | Add to Repository 24 | outputCaret 25 | afterOutput 26 | outputFormat 27 | text 28 | outputLocation 29 | toolTip 30 | requiredCommands 31 | 32 | 33 | command 34 | cvs 35 | locations 36 | 37 | /opt/local/bin/cvs 38 | /usr/local/bin/cvs 39 | 40 | variable 41 | TM_CVS 42 | 43 | 44 | uuid 45 | ADCD4FCD-D39D-41B3-88D0-84C5BE115535 46 | version 47 | 2 48 | 49 | 50 | -------------------------------------------------------------------------------- /Commands/Diff With Previous Revision (PREV).plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env bash 9 | 10 | ruby18 <<'END' 11 | 12 | ENV['CVS_PATH'] = ENV['TM_CVS'] 13 | $LOAD_PATH << ENV['TM_BUNDLE_SUPPORT'] 14 | require 'cvs_diff' 15 | 16 | CVS::diff_active_file(:prev, "Diff With Previous Revision") 17 | END 18 | 19 | input 20 | none 21 | inputFormat 22 | text 23 | keyEquivalent 24 | ^Z 25 | name 26 | Diff With Previous Revision (PREV) 27 | outputCaret 28 | afterOutput 29 | outputFormat 30 | text 31 | outputLocation 32 | newWindow 33 | requiredCommands 34 | 35 | 36 | command 37 | cvs 38 | locations 39 | 40 | /opt/local/bin/cvs 41 | /usr/local/bin/cvs 42 | 43 | variable 44 | TM_CVS 45 | 46 | 47 | uuid 48 | E29C9E3B-B7FB-4ED1-94C3-2F702CD090B5 49 | version 50 | 2 51 | 52 | 53 | -------------------------------------------------------------------------------- /Commands/Reset sticky tags.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env bash 9 | 10 | ruby18 <<'END' 11 | 12 | ENV['CVS_PATH'] = ENV['TM_CVS'] 13 | $LOAD_PATH << ENV['TM_BUNDLE_SUPPORT'] 14 | require 'versioned_file' 15 | 16 | file = CVS::VersionedFile.new(ENV['TM_FILEPATH']) 17 | file.update(:reset_tags => true) 18 | 19 | puts "All sticky tags reset" 20 | 21 | END 22 | 23 | input 24 | none 25 | inputFormat 26 | text 27 | keyEquivalent 28 | ^Z 29 | name 30 | Reset sticky tags 31 | outputCaret 32 | afterOutput 33 | outputFormat 34 | text 35 | outputLocation 36 | toolTip 37 | requiredCommands 38 | 39 | 40 | command 41 | cvs 42 | locations 43 | 44 | /opt/local/bin/cvs 45 | /usr/local/bin/cvs 46 | 47 | variable 48 | TM_CVS 49 | 50 | 51 | uuid 52 | 1FE7E10E-70B4-44D7-924D-879C54F19289 53 | version 54 | 2 55 | 56 | 57 | -------------------------------------------------------------------------------- /Commands/Annotate Line.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env bash 9 | 10 | ruby18 <<'END' 11 | 12 | ENV['CVS_PATH'] = ENV['TM_CVS'] 13 | $LOAD_PATH << ENV['TM_BUNDLE_SUPPORT'] 14 | require 'versioned_file' 15 | require 'diff' 16 | 17 | file = CVS::VersionedFile.new(ENV['TM_FILEPATH']) 18 | annotation = file.cvs(:annotate) 19 | diff = CVS::Diff.new(file.diff(:head)) 20 | 21 | n = diff.source_line(ENV['TM_LINE_NUMBER'].to_i-1) 22 | case n 23 | when Symbol then puts "#{n} locally" 24 | else puts annotation.split(/\n/)[n] 25 | end 26 | 27 | END 28 | 29 | input 30 | none 31 | inputFormat 32 | text 33 | keyEquivalent 34 | ^Z 35 | name 36 | Annotate Line 37 | outputCaret 38 | afterOutput 39 | outputFormat 40 | text 41 | outputLocation 42 | toolTip 43 | requiredCommands 44 | 45 | 46 | command 47 | cvs 48 | locations 49 | 50 | /opt/local/bin/cvs 51 | /usr/local/bin/cvs 52 | 53 | variable 54 | TM_CVS 55 | 56 | 57 | uuid 58 | 76E34DE2-1DCB-47B8-BA2F-4F3341A3AB9C 59 | version 60 | 2 61 | 62 | 63 | -------------------------------------------------------------------------------- /Commands/Update with tag....plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env bash 9 | 10 | ruby18 <<'END' 11 | 12 | ENV['CVS_PATH'] = ENV['TM_CVS'] 13 | $LOAD_PATH << ENV['TM_BUNDLE_SUPPORT'] 14 | require 'versioned_file' 15 | 16 | tag=`CocoaDialog inputbox --title Tag --informative-text 'Which tag?' --button1 Update --button2 'Cancel'` 17 | 18 | accept, tag = *tag.to_a.map {|l| l.chomp} 19 | 20 | exit unless accept =~ /1/ 21 | 22 | file = CVS::VersionedFile.new(ENV['TM_FILEPATH']) 23 | file.update(:tag => tag) 24 | 25 | puts "Tagged #{file.basename} with tag #{tag}." 26 | 27 | END 28 | 29 | input 30 | none 31 | inputFormat 32 | text 33 | keyEquivalent 34 | ^Z 35 | name 36 | Update with tag... 37 | outputCaret 38 | afterOutput 39 | outputFormat 40 | text 41 | outputLocation 42 | toolTip 43 | requiredCommands 44 | 45 | 46 | command 47 | cvs 48 | locations 49 | 50 | /opt/local/bin/cvs 51 | /usr/local/bin/cvs 52 | 53 | variable 54 | TM_CVS 55 | 56 | 57 | uuid 58 | 1F22884A-6702-4FB6-B4E7-D49B2431BD4E 59 | version 60 | 2 61 | 62 | 63 | -------------------------------------------------------------------------------- /Commands/Merge from tag....plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env bash 9 | 10 | ruby18 <<'END' 11 | 12 | ENV['CVS_PATH'] = ENV['TM_CVS'] 13 | $LOAD_PATH << ENV['TM_BUNDLE_SUPPORT'] 14 | require 'versioned_file' 15 | 16 | tag=`CocoaDialog inputbox --title Merge --informative-text 'Which tag?' --button1 Update --button2 'Cancel'` 17 | 18 | accept, tag = *tag.to_a.map {|l| l.chomp} 19 | 20 | exit unless accept =~ /1/ 21 | 22 | file = CVS::VersionedFile.new(ENV['TM_FILEPATH']) 23 | file.update(:tag => tag, :sticky => false) 24 | 25 | puts "Merged #{file.basename} from #{tag}." 26 | 27 | END 28 | 29 | input 30 | none 31 | inputFormat 32 | text 33 | keyEquivalent 34 | ^Z 35 | name 36 | Merge from tag... 37 | outputCaret 38 | afterOutput 39 | outputFormat 40 | text 41 | outputLocation 42 | discard 43 | requiredCommands 44 | 45 | 46 | command 47 | cvs 48 | locations 49 | 50 | /opt/local/bin/cvs 51 | /usr/local/bin/cvs 52 | 53 | variable 54 | TM_CVS 55 | 56 | 57 | uuid 58 | 473C6519-F164-4496-A699-F9DE2CAB56DD 59 | version 60 | 2 61 | 62 | 63 | -------------------------------------------------------------------------------- /Support/Stylesheets/cvs_style.css: -------------------------------------------------------------------------------- 1 | /* general stuff.. */ 2 | body { 3 | font-family: "Lucida Grande", sans-serif; 4 | font-size: 13px; 5 | } 6 | 7 | h1 { 8 | font-size: 24px; 9 | text-shadow: #ddd 2px 3px 3px; 10 | } 11 | 12 | /* make horizontal rules slightly less heavy */ 13 | hr { 14 | color: #ccc; 15 | background-color: #ccc; 16 | height: 1px; 17 | border: 0px; 18 | } 19 | 20 | /* enable word-wrap for text in
 */
21 | pre {
22 | word-wrap: break-word;
23 | }
24 | 
25 | /* for error formating.. */
26 | div.error {
27 |    font-family: "Bitstream Vera Sans Mono", monospace;
28 |    font-size: 12px;
29 |    color: #f30;
30 |    background-color: #fee;
31 |    border: 2px solid #f52;
32 |    padding: 4px;
33 |    margin: 3px;
34 | }
35 | 
36 | div.error h2 {
37 |    font-family: "Lucida Grande", sans-serif;
38 |    font-size: 17px;
39 |    margin-top: 0;
40 | }
41 | 
42 | div.error a:link, div.error a:visited {
43 |    background-color: transparent;
44 |    color: inherit;
45 | }
46 | div.error a:hover, div.error a:active {
47 |    background-color: #f52;
48 |    text-decoration: none;
49 |    color: #fee;
50 | }
51 | 
52 | 
53 | /* for showing the path or other information about the current operation */
54 | div.command {
55 |    color: #03f;
56 |    background-color: #eef;
57 |    border: 2px solid #25f;
58 |    padding: 4px;
59 |    margin: 3px;
60 |    font-family: "Bitstream Vera Sans Mono", monospace;
61 |    font-size: 10px;
62 | }
63 | 
64 | div.command h2 {
65 |    font-family: "Lucida Grande", sans-serif;
66 |    font-size: 15px;
67 |    margin-top: 0;
68 | }
69 | 
70 | 
71 | /* about links.. */
72 | a:link, a:visited {
73 |    background-color: transparent;
74 |    color: #35a;
75 |    text-decoration: underline;
76 |    padding-left: 1px;
77 |    padding-right: 1px;
78 | }
79 | 
80 | a:hover, a:active {
81 |    background-color: #136;
82 |    color: #fff;
83 |    text-decoration: none;
84 | }
85 | 
86 | 
87 | /* for everything alternating: */
88 | .alternate {
89 |    background-color: #f2f2f2;
90 | }
91 | 


--------------------------------------------------------------------------------
/Commands/Check out Revision....plist:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 | 	beforeRunningCommand
 6 | 	nop
 7 | 	command
 8 | 	#!/usr/bin/env bash
 9 | [[ -f "${TM_SUPPORT_PATH}/lib/bash_init.sh" ]] && . "${TM_SUPPORT_PATH}/lib/bash_init.sh"
10 | 
11 | cd "$TM_DIRECTORY"
12 | revs=$("${TM_CVS:=cvs}" log "$TM_FILENAME"|grep '^revision' \
13 | 	2> >(CocoaDialog progressbar --indeterminate \
14 | 		--title "Check out Revision…" \
15 | 		--text "Retrieving List of Revisions…" \
16 | 	))
17 | 
18 | revs=$(echo $revs|sed 's/revision //g')
19 | 
20 | revs=`osascript<<END
21 | 	set AppleScript's text item delimiters to {" "}
22 | 	tell app "SystemUIServer"
23 | 		activate
24 | 		set ourList to (every text item of "$revs")
25 | 		if the count of items in ourList is 0 then
26 | 			display dialog "No older revisions of file '$(basename "$TM_FILEPATH")' found" buttons ("OK")
27 | 			set the result to false
28 | 		else
29 | 			choose from list ourList with prompt "Check out an older revision of '$(basename "$TM_FILEPATH")':"
30 | 		end if
31 | 	end tell
32 | END`
33 | 
34 | # exit if user canceled
35 | if [[ $revs = "false" ]]; then
36 | 	exit_discard
37 | fi
38 | 
39 | export REVS="$revs"
40 | ruby18 <<'END'
41 | 
42 | ENV['CVS_PATH'] = ENV['TM_CVS']
43 | $LOAD_PATH << ENV['TM_BUNDLE_SUPPORT']
44 | require 'versioned_file'
45 | 
46 | print CVS::VersionedFile.version(ENV['TM_FILEPATH'], ENV['REVS'])
47 | END
48 | 	input
49 | 	none
50 | 	inputFormat
51 | 	text
52 | 	keyEquivalent
53 | 	^Z
54 | 	name
55 | 	Check out Revision...
56 | 	outputCaret
57 | 	interpolateByLine
58 | 	outputFormat
59 | 	text
60 | 	outputLocation
61 | 	replaceDocument
62 | 	requiredCommands
63 | 	
64 | 		
65 | 			command
66 | 			cvs
67 | 			locations
68 | 			
69 | 				/opt/local/bin/cvs
70 | 				/usr/local/bin/cvs
71 | 			
72 | 			variable
73 | 			TM_CVS
74 | 		
75 | 	
76 | 	uuid
77 | 	2C5DB599-04DC-40CC-BBE8-0A73620BC42A
78 | 	version
79 | 	2
80 | 
81 | 
82 | 


--------------------------------------------------------------------------------
/Commands/Diff With Revision....plist:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 | 	beforeRunningCommand
 6 | 	nop
 7 | 	command
 8 | 	#!/usr/bin/env bash
 9 | [[ -f "${TM_SUPPORT_PATH}/lib/bash_init.sh" ]] && . "${TM_SUPPORT_PATH}/lib/bash_init.sh"
10 | 
11 | cd "$TM_DIRECTORY"
12 | revs=$("${TM_CVS:=cvs}" log "$TM_FILENAME"|grep '^revision' \
13 | 	2> >(CocoaDialog progressbar --indeterminate \
14 | 		--title "Diff Revision…" \
15 | 		--text "Retrieving List of Revisions…" \
16 | 	))
17 | 
18 | revs=$(echo $revs|sed 's/revision //g')
19 | 
20 | revs=`osascript<<END
21 | 	set AppleScript's text item delimiters to {" "}
22 | 	tell app "SystemUIServer"
23 | 		activate
24 | 		set ourList to (every text item of "$revs")
25 | 		if the count of items in ourList is 0 then
26 | 			display dialog "No older revisions of file '$(basename "$TM_FILEPATH")' found" buttons ("OK")
27 | 			set the result to false
28 | 		else
29 | 			choose from list ourList with prompt "Diff '$(basename "$TM_FILEPATH")' with older revision:"
30 | 		end if
31 | 	end tell
32 | END`
33 | 
34 | # exit if user canceled
35 | if [[ $revs = "false" ]]; then
36 | 	osascript -e 'tell app "TextMate" to activate' &>/dev/null &	exit_discard
37 | fi
38 | 
39 | export REVS="$revs"
40 | ruby18 <<'END'
41 | 
42 | ENV['CVS_PATH'] = ENV['TM_CVS']
43 | $LOAD_PATH << ENV['TM_BUNDLE_SUPPORT']
44 | require 'cvs_diff'
45 | 
46 | CVS::diff_active_file(ENV['REVS'], "Diff With Revision...")
47 | END
48 | 	input
49 | 	none
50 | 	inputFormat
51 | 	text
52 | 	keyEquivalent
53 | 	^Z
54 | 	name
55 | 	Diff With Revision...
56 | 	outputCaret
57 | 	afterOutput
58 | 	outputFormat
59 | 	text
60 | 	outputLocation
61 | 	newWindow
62 | 	requiredCommands
63 | 	
64 | 		
65 | 			command
66 | 			cvs
67 | 			locations
68 | 			
69 | 				/opt/local/bin/cvs
70 | 				/usr/local/bin/cvs
71 | 			
72 | 			variable
73 | 			TM_CVS
74 | 		
75 | 	
76 | 	uuid
77 | 	6416A49F-8B3E-47EE-81B4-F2F7F19C6B41
78 | 	version
79 | 	2
80 | 
81 | 
82 | 


--------------------------------------------------------------------------------
/info.plist:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 | 	contactEmailRot13
 6 | 	oevna.qbabina@tznvy.pbz
 7 | 	contactName
 8 | 	Brian Donovan
 9 | 	description
10 | 	This bundle gives you easy access to most functions of the <a href="http://www.nongnu.org/cvs/">CVS revision control system</a>.
11 | 	mainMenu
12 | 	
13 | 		items
14 | 		
15 | 			ADCD4FCD-D39D-41B3-88D0-84C5BE115535
16 | 			2C5DB599-04DC-40CC-BBE8-0A73620BC42A
17 | 			BE6728A5-AFC4-4D98-9EC7-C2E951483B71
18 | 			52464886-2584-4632-A105-12E3A9E6051F
19 | 			2BABA244-2BB2-4F3C-BA72-66ADEA8FAA01
20 | 			D2411BE8-CF0D-4F61-A51F-9587F267D6D0
21 | 			20865252-80D2-4CA4-9834-391D09210C4F
22 | 		
23 | 		submenus
24 | 		
25 | 			2BABA244-2BB2-4F3C-BA72-66ADEA8FAA01
26 | 			
27 | 				items
28 | 				
29 | 					338A3670-DA8E-4036-87E0-DF2E212254C8
30 | 					76E34DE2-1DCB-47B8-BA2F-4F3341A3AB9C
31 | 					9EA691A5-A166-4D8F-955F-270490F02827
32 | 				
33 | 				name
34 | 				History
35 | 			
36 | 			52464886-2584-4632-A105-12E3A9E6051F
37 | 			
38 | 				items
39 | 				
40 | 					22FC4CAB-4664-4CFC-BC8E-C2294616E464
41 | 					00C541DE-9A5C-4C59-A075-E754BAEB25C2
42 | 					E29C9E3B-B7FB-4ED1-94C3-2F702CD090B5
43 | 					6416A49F-8B3E-47EE-81B4-F2F7F19C6B41
44 | 				
45 | 				name
46 | 				Diff
47 | 			
48 | 			D2411BE8-CF0D-4F61-A51F-9587F267D6D0
49 | 			
50 | 				items
51 | 				
52 | 					473C6519-F164-4496-A699-F9DE2CAB56DD
53 | 					1F22884A-6702-4FB6-B4E7-D49B2431BD4E
54 | 					1FE7E10E-70B4-44D7-924D-879C54F19289
55 | 				
56 | 				name
57 | 				Tags
58 | 			
59 | 		
60 | 	
61 | 	name
62 | 	CVS
63 | 	ordering
64 | 	
65 | 		ADCD4FCD-D39D-41B3-88D0-84C5BE115535
66 | 		338A3670-DA8E-4036-87E0-DF2E212254C8
67 | 		76E34DE2-1DCB-47B8-BA2F-4F3341A3AB9C
68 | 		2C5DB599-04DC-40CC-BBE8-0A73620BC42A
69 | 		BE6728A5-AFC4-4D98-9EC7-C2E951483B71
70 | 		22FC4CAB-4664-4CFC-BC8E-C2294616E464
71 | 		00C541DE-9A5C-4C59-A075-E754BAEB25C2
72 | 		E29C9E3B-B7FB-4ED1-94C3-2F702CD090B5
73 | 		6416A49F-8B3E-47EE-81B4-F2F7F19C6B41
74 | 		9EA691A5-A166-4D8F-955F-270490F02827
75 | 		20865252-80D2-4CA4-9834-391D09210C4F
76 | 		1FE7E10E-70B4-44D7-924D-879C54F19289
77 | 		473C6519-F164-4496-A699-F9DE2CAB56DD
78 | 		1F22884A-6702-4FB6-B4E7-D49B2431BD4E
79 | 	
80 | 	uuid
81 | 	62FB4173-A31D-41D6-8201-16C7866F567E
82 | 
83 | 
84 | 


--------------------------------------------------------------------------------
/Support/versioned_file.rb:
--------------------------------------------------------------------------------
  1 | module CVS
  2 |   CVS_PATH = ENV['CVS_PATH'] || 'cvs' unless defined?(CVS_PATH)
  3 |   
  4 |   class VersionedFile
  5 |     attr_accessor :path
  6 |     
  7 |     def initialize(path)
  8 |       @path = path
  9 |     end
 10 |     
 11 |     def dirname
 12 |       (File.dirname(@path) =~ %r{/$}) ? File.dirname(@path) : "#{File.dirname(@path)}/"
 13 |     end
 14 |     
 15 |     def basename
 16 |       File.basename @path
 17 |     end
 18 |     
 19 |     def status
 20 |       status_from_line cvs(:update, :pretend => true, :quiet => true)
 21 |     end
 22 |     
 23 |     def revision
 24 |       $1 if cvs(:status) =~ /Working revision:\s*([\d\.]+)/
 25 |     end
 26 |     
 27 |     def revisions(reload = false)
 28 |       @revisions = nil if reload
 29 |       @revisions ||= cvs(:log).inject([]) { |list,line| list << $1 if line =~ /^revision ([\d\.]+)/i; list}
 30 |     end
 31 |     
 32 |     def update(options={})
 33 |       options = options.dup
 34 |       if options.key?(:tag)
 35 |         options[:tag] = expand_revision(options[:tag])
 36 |         options[:sticky] = true unless options.key?(:sticky)
 37 |         options[:command_options] = "#{options[:sticky] ? '-r' : '-j'} #{options[:tag]}"
 38 |       elsif options[:reset_tags]
 39 |         options[:command_options] = '-A'
 40 |       end
 41 |       cvs(:update, options)
 42 |     end
 43 |     
 44 |     def diff(revision, other_revision = nil)
 45 |       revision, other_revision = expand_revision(revision), expand_revision(other_revision)
 46 |       
 47 |       if other_revision
 48 |         cvs(:diff, "-r #{other_revision} -r #{revision}")
 49 |       else
 50 |         cvs(:diff, "-r #{revision}")
 51 |       end
 52 |     end
 53 |     
 54 |     def version(revision)
 55 |       cvs(:update, "-p -r #{expand_revision(revision)}")
 56 |     end
 57 |     
 58 |     def commit(options={})
 59 |       options = options.dup
 60 |       options[:command_options] = "-m '#{options.delete(:message).gsub(/'/, "\\'")}'" if options.key?(:message)
 61 |       cvs(:commit, options)
 62 |     end
 63 |     
 64 |     def cvs(command, options={})
 65 |       options = {:command_options => options} if options.is_a? String
 66 |       cvs_options = [options[:cvs_options]].flatten.compact
 67 |       cvs_options << '-n' if options[:pretend]
 68 |       cvs_options << '-q' if options[:quiet]
 69 |       cvs_options << '-Q' if options[:silent]
 70 |       cvs_options = cvs_options.join(' ')
 71 |       
 72 |       files = options[:files] || [basename]
 73 |       files = files.map { |file| %("#{file.gsub(/"/, '\\"')}") }.join(' ')
 74 |       %x{cd "#{dirname}"; "#{CVS_PATH}" #{cvs_options} #{command} #{options[:command_options]} #{files} 2> /dev/null}
 75 |     end
 76 | 
 77 |     %w(status revisions diff revision version cvs).each do |method|
 78 |       class_eval "def self.#{method}(path, *args); new(path).#{method}(*args); end"
 79 |     end
 80 |     
 81 |     protected
 82 |     
 83 |     def expand_revision(revision)
 84 |       case revision
 85 |       when :head then 'HEAD'
 86 |       when :base then 'BASE'
 87 |       when :prev then revisions[revisions.index(self.revision)+1] rescue nil
 88 |       else revision
 89 |       end
 90 |     end
 91 |     
 92 |     def status_from_line(line)
 93 |       case line
 94 |       when /^(U|P) /i then :stale
 95 |       when /^A /i then     :added
 96 |       when /^M /i then     :modified
 97 |       when /^C /i then     :conflicted
 98 |       when /^\? /i then    :unknown
 99 |       when /^R /i then     :removed
100 |       else                 :current
101 |       end      
102 |     end
103 |   end
104 | end


--------------------------------------------------------------------------------
/Support/cvs_commit.rb:
--------------------------------------------------------------------------------
  1 | require 'English' # you are angry, english!
  2 | 
  3 | cvs				= ENV['TM_CVS'] || 'cvs' unless defined?(TM_CVS)
  4 | #commit_paths	= ENV['CommitPaths']
  5 | commit_tool		= ENV['CommitWindow']
  6 | bundle			= ENV['TM_BUNDLE_SUPPORT']
  7 | support			= ENV['TM_SUPPORT_PATH']
  8 | ignore_file_pattern = /(\/.*)*(\/\..*|\.(tmproj|o|pyc)|Icon)/
  9 | 
 10 | CURRENT_DIR		= Dir.pwd + "/"
 11 | 
 12 | require (bundle + '/versioned_file.rb')
 13 | require (bundle + '/working_copy.rb')
 14 | require (support + '/lib/shelltokenize.rb')
 15 | require (bundle + "/lib/Builder.rb")
 16 | 
 17 | mup = Builder::XmlMarkup.new(:target => STDOUT)
 18 | 
 19 | mup.html {
 20 | 	mup.head {
 21 | 			mup.title("CVS commit")
 22 | 			mup.style( "@import 'file://"+bundle+"/Stylesheets/cvs_style.css';", "type" => "text/css")
 23 | 	}
 24 | 
 25 | 	mup.body { 
 26 | 		mup.h1("CVS Commit")
 27 | 		STDOUT.flush
 28 | 		mup.hr
 29 | 
 30 | 		# Ignore files without changes
 31 | #puts TextMate::selected_paths_for_shell
 32 |     working_copies = TextMate::selected_paths_array.map do |path|
 33 |       File.directory?(path) ?
 34 |         CVS::WorkingCopy.new(path) :
 35 |         CVS::VersionedFile.new(path)
 36 |     end
 37 |     
 38 | 		#status_command = %Q{"#{cvs}" -nq update #{TextMate::selected_paths_for_shell}}
 39 | #puts status_command
 40 | 		#status_output = %x{#{status_command}}
 41 | #puts status_output
 42 | 		#paths = status_output.scan(/^(.)....(\s+)(.*)\n/)
 43 | 		status = working_copies.inject({}) do |h,wc|
 44 | 		  case wc
 45 | 	    when CVS::WorkingCopy then h.update(wc.status)
 46 |       when CVS::VersionedFile then h.update(wc.path => wc.status)
 47 |       end
 48 | 		  h
 49 | 		end
 50 | 		paths = status.keys
 51 | 		
 52 | 
 53 |     def paths_for_status(hash, *status)
 54 |       hash.inject([]) { |arr,(k,v)| arr << k if status.include?(v); arr }
 55 |     end
 56 |     
 57 | # 		def status_to_paths()
 58 | # 			paths = matches.collect { |m| m[2] }
 59 | # 			paths.collect{|path| path.sub(/^#{CURRENT_DIR}/, "") }
 60 | # 		end
 61 | 
 62 | 		def matches_to_status(matches)
 63 | 			matches.collect {|m| m[0]}
 64 | 		end
 65 | 		
 66 | 		# Ignore files with '?', but report them to the user
 67 | 		#unknown_paths = paths.select { |m| m[0] == '?' }
 68 | 		
 69 | 		unknown_paths = paths_for_status(status, :unknown)
 70 | 		unknown_to_report_paths = unknown_paths.select { |path| ignore_file_pattern =~ path }
 71 | 		
 72 |     #unknown_to_report_paths = paths.select{ |m| m[0] == '?' and not ignore_file_pattern =~ m[2]}
 73 | 		unless unknown_to_report_paths.empty?
 74 | 			mup.div( "class" => "info" ) {
 75 | 				mup.text! "These files are not added to the repository, and so will not be committed:"		
 76 | 				mup.ul{ unknown_to_report_paths.each{ |path| mup.li(path) } }
 77 | 			}
 78 | 		end
 79 | 
 80 | 		# Fail if we have conflicts -- cvs commit will fail, so let's
 81 | 		# error out before the user gets too involved in the commit
 82 | 		conflict_paths = paths_for_status(status, :conflicted)
 83 | 
 84 | 		unless conflict_paths.empty?
 85 | 			mup.div( "class" => "error" ) {
 86 | 				mup.text! "Cannot continue; there are merge conflicts in files:"		
 87 | 				mup.ul{ conflict_paths.each { |path| mup.li(path) } }
 88 | 				mup.text! "Canceled."
 89 | 			}	
 90 | 			exit -1
 91 | 		end
 92 | 
 93 | 		# Remove the unknown paths from the commit
 94 | 		commit_paths = paths.select { |path| [:modified, :added, :removed].include? status[path] }
 95 | 
 96 | 		if commit_paths.empty?
 97 | 			mup.div( "class" => "info" ) {
 98 | 				mup.text! "File(s) not modified; nothing to commit."
 99 | 				mup.ul{ unknown_paths.each { |path| mup.li(path) } }
100 | 			}
101 | 			exit 0
102 | 		end
103 | 
104 | 		STDOUT.flush
105 | 
106 | 		commit_status = commit_paths.map { |path| status[path].to_s[0,1].upcase }.join(":")
107 | 		
108 | 		commit_path_text = commit_paths.collect { |path| path.quote_filename_for_shell }.join(" ")
109 | 
110 | 		commit_args = %x{"#{commit_tool}" --status #{commit_status} #{commit_path_text}}
111 | 
112 | 		status = $CHILD_STATUS
113 | 		if status != 0
114 | 			mup.div( "class" => "error" ) {
115 | 				mup.text! "Canceled (#{status >> 8})."
116 | 			}	
117 | 			exit -1
118 | 		end
119 | 
120 | 		mup.div("class" => "command"){ mup.strong(%Q{#{cvs} commit }); mup.text!(commit_args) }
121 | 		
122 | 		mup.pre {
123 | 			STDOUT.flush
124 | 
125 |       puts working_copies.first.cvs(:commit, commit_args.gsub(working_copies.first.dirname, ''))
126 | 		}
127 | 	}
128 | }


--------------------------------------------------------------------------------
/Support/lib/Builder.rb:
--------------------------------------------------------------------------------
  1 | # This is a single-file version of Jim Weirich's Builder suite version 1.2.3, 
  2 | # including some very minor tweaks required to make it work with Ruby 1.6.8.
  3 | # Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
  4 | # All rights reserved.
  5 | #
  6 | # Create XML markup easily.  All (well, almost all) methods sent to
  7 | # an XmlMarkup object will be translated to the equivalent XML
  8 | # markup.  Any method with a block will be treated as an XML markup
  9 | # tag with nested markup in the block.
 10 | #
 11 | # Examples will demonstrate this easier than words.  In the
 12 | # following, +xm+ is an +XmlMarkup+ object.
 13 | #
 14 | #   xm.em("emphasized")             # => emphasized
 15 | #   xm.em { xmm.b("emp & bold") }   # => emph & bold
 16 | #   xm.a("A Link", "href"=>"http://onestepback.org")
 17 | #                                   # => A Link
 18 | #   xm.div { br }                    # => 

19 | # xm.target("name"=>"compile", "option"=>"fast") 20 | # # => 21 | # # NOTE: order of attributes is not specified. 22 | # 23 | # xm.instruct! # 24 | # xm.html { # 25 | # xm.head { # 26 | # xm.title("History") # History 27 | # } # 28 | # xm.body { # 29 | # xm.comment! "HI" # 30 | # xm.h1("Header") #

Header

31 | # xm.p("paragraph") #

paragraph

32 | # } # 33 | # } # 34 | # 35 | 36 | 37 | # blankslate.rb: 38 | 39 | #!/usr/bin/env ruby 40 | #-- 41 | # Copyright 2004 by Jim Weirich (jim@weirichhouse.org). 42 | # All rights reserved. 43 | 44 | # Permission is granted for use, copying, modification, distribution, 45 | # and distribution of modified versions of this work as long as the 46 | # above copyright notice is included. 47 | #++ 48 | 49 | module Builder 50 | 51 | # BlankSlate provides an abstract base class with no predefined 52 | # methods (except for \_\_send__ and \_\_id__). 53 | # BlankSlate is useful as a base class when writing classes that 54 | # depend upon method_missing (e.g. dynamic proxies). 55 | class BlankSlate 56 | class << self 57 | def hide(name) 58 | undef_method name if 59 | instance_methods.include?(name.to_s) and 60 | name !~ /^(__|instance_eval)/ 61 | end 62 | end 63 | 64 | instance_methods.each { |m| hide(m) } 65 | end 66 | end 67 | 68 | # Since Ruby is very dynamic, methods added to the ancestors of 69 | # BlankSlate after BlankSlate is defined will show up in the 70 | # list of available BlankSlate methods. We handle this by defining a hook in the Object and Kernel classes that will hide any defined 71 | module Kernel 72 | class << self 73 | alias_method :blank_slate_method_added, :method_added 74 | def method_added(name) 75 | blank_slate_method_added(name) 76 | return if self != Kernel 77 | Builder::BlankSlate.hide(name) 78 | end 79 | end 80 | end 81 | 82 | class Object 83 | class << self 84 | alias_method :blank_slate_method_added, :method_added 85 | def method_added(name) 86 | blank_slate_method_added(name) 87 | return if self != Object 88 | Builder::BlankSlate.hide(name) 89 | end 90 | end 91 | end 92 | 93 | # xmlbase.rb 94 | module Builder 95 | 96 | # Generic error for builder 97 | class IllegalBlockError < RuntimeError; end 98 | 99 | # XmlBase is a base class for building XML builders. See 100 | # Builder::XmlMarkup and Builder::XmlEvents for examples. 101 | class XmlBase < BlankSlate 102 | 103 | # Create an XML markup builder. 104 | # 105 | # out:: Object receiving the markup.1 +out+ must respond to 106 | # <<. 107 | # indent:: Number of spaces used for indentation (0 implies no 108 | # indentation and no line breaks). 109 | # initial:: Level of initial indentation. 110 | # 111 | def initialize(indent=0, initial=0) 112 | @indent = indent 113 | @level = initial 114 | @self = nil 115 | end 116 | 117 | # Create a tag named +sym+. Other than the first argument which 118 | # is the tag name, the arguements are the same as the tags 119 | # implemented via method_missing. 120 | def tag!(sym, *args, &block) 121 | self.__send__(sym, *args, &block) 122 | end 123 | 124 | # Create XML markup based on the name of the method. This method 125 | # is never invoked directly, but is called for each markup method 126 | # in the markup block. 127 | def method_missing(sym, *args, &block) 128 | text = nil 129 | attrs = nil 130 | sym = "#{sym}:#{args.shift}" if args.first.kind_of?(Symbol) 131 | args.each do |arg| 132 | case arg 133 | when Hash 134 | attrs ||= {} 135 | attrs.update(arg) # was merge!, which ruby 1.6.8 doesn't support 136 | else 137 | text ||= '' 138 | text << arg.to_s 139 | end 140 | end 141 | if block 142 | unless text.nil? 143 | raise ArgumentError, "XmlMarkup cannot mix a text argument with a block" 144 | end 145 | _capture_outer_self(block) if @self.nil? 146 | _indent 147 | _start_tag(sym, attrs) 148 | _newline 149 | _nested_structures(block) 150 | _indent 151 | _end_tag(sym) 152 | _newline 153 | elsif text.nil? 154 | _indent 155 | _start_tag(sym, attrs, true) 156 | _newline 157 | else 158 | _indent 159 | _start_tag(sym, attrs) 160 | text! text 161 | _end_tag(sym) 162 | _newline 163 | end 164 | @target 165 | end 166 | 167 | # Append text to the output target. Escape any markup. May be 168 | # used within the markup brakets as: 169 | # 170 | # builder.p { |b| b.br; b.text! "HI" } #=>


HI

171 | def text!(text) 172 | _text(_escape(text)) 173 | end 174 | 175 | # Append text to the output target without escaping any markup. 176 | # May be used within the markup brakets as: 177 | # 178 | # builder.p { |x| x << "
HI" } #=>


HI

179 | # 180 | # This is useful when using non-builder enabled software that 181 | # generates strings. Just insert the string directly into the 182 | # builder without changing the inserted markup. 183 | # 184 | # It is also useful for stacking builder objects. Builders only 185 | # use << to append to the target, so by supporting this 186 | # method/operation builders can use other builders as their 187 | # targets. 188 | def <<(text) 189 | _text(text) 190 | end 191 | 192 | # For some reason, nil? is sent to the XmlMarkup object. If nil? 193 | # is not defined and method_missing is invoked, some strange kind 194 | # of recursion happens. Since nil? won't ever be an XML tag, it 195 | # is pretty safe to define it here. (Note: this is an example of 196 | # cargo cult programming, 197 | # cf. http://fishbowl.pastiche.org/2004/10/13/cargo_cult_programming). 198 | def nil? 199 | false 200 | end 201 | 202 | private 203 | 204 | def _escape(text) 205 | text. 206 | gsub(%r{&}, '&'). 207 | gsub(%r{<}, '<'). 208 | gsub(%r{>}, '>') 209 | end 210 | 211 | def _capture_outer_self(block) 212 | @self = eval("self", block) 213 | end 214 | 215 | def _newline 216 | return if @indent == 0 217 | text! "\n" 218 | end 219 | 220 | def _indent 221 | return if @indent == 0 || @level == 0 222 | text!(" " * (@level * @indent)) 223 | end 224 | 225 | def _nested_structures(block) 226 | @level += 1 227 | block.call(self) 228 | ensure 229 | @level -= 1 230 | end 231 | end 232 | end 233 | 234 | # xmlmarkup.rb 235 | module Builder 236 | 237 | # Create XML markup easily. All (well, almost all) methods sent to 238 | # an XmlMarkup object will be translated to the equivalent XML 239 | # markup. Any method with a block will be treated as an XML markup 240 | # tag with nested markup in the block. 241 | # 242 | # Examples will demonstrate this easier than words. In the 243 | # following, +xm+ is an +XmlMarkup+ object. 244 | # 245 | # xm.em("emphasized") # => emphasized 246 | # xm.em { xmm.b("emp & bold") } # => emph & bold 247 | # xm.a("A Link", "href"=>"http://onestepback.org") 248 | # # => A Link 249 | # xm.div { br } # =>

250 | # xm.target("name"=>"compile", "option"=>"fast") 251 | # # => 252 | # # NOTE: order of attributes is not specified. 253 | # 254 | # xm.instruct! # 255 | # xm.html { # 256 | # xm.head { # 257 | # xm.title("History") # History 258 | # } # 259 | # xm.body { # 260 | # xm.comment! "HI" # 261 | # xm.h1("Header") #

Header

262 | # xm.p("paragraph") #

paragraph

263 | # } # 264 | # } # 265 | # 266 | # == Notes: 267 | # 268 | # * The order that attributes are inserted in markup tags is 269 | # undefined. 270 | # 271 | # * Sometimes you wish to insert text without enclosing tags. Use 272 | # the text! method to accomplish this. 273 | # 274 | # Example: 275 | # 276 | # xm.div { #
277 | # xm.text! "line"; xm.br # line
278 | # xm.text! "another line"; xmbr # another line
279 | # } #
280 | # 281 | # * The special XML characters <, >, and & are converted to <, 282 | # > and & automatically. Use the << operation to 283 | # insert text without modification. 284 | # 285 | # * Sometimes tags use special characters not allowed in ruby 286 | # identifiers. Use the tag! method to handle these 287 | # cases. 288 | # 289 | # Example: 290 | # 291 | # xml.tag!("SOAP:Envelope") { ... } 292 | # 293 | # will produce ... 294 | # 295 | # ... " 296 | # 297 | # tag! will also take text and attribute arguments (after 298 | # the tag name) like normal markup methods. (But see the next 299 | # bullet item for a better way to handle XML namespaces). 300 | # 301 | # * Direct support for XML namespaces is now available. If the 302 | # first argument to a tag call is a symbol, it will be joined to 303 | # the tag to produce a namespace:tag combination. It is easier to 304 | # show this than describe it. 305 | # 306 | # xml.SOAP :Envelope do ... end 307 | # 308 | # Just put a space before the colon in a namespace to produce the 309 | # right form for builder (e.g. "SOAP:Envelope" => 310 | # "xml.SOAP :Envelope") 311 | # 312 | # * XmlMarkup builds the markup in any object (called a _target_) 313 | # that accepts the << method. If no target is given, 314 | # then XmlMarkup defaults to a string target. 315 | # 316 | # Examples: 317 | # 318 | # xm = Builder::XmlMarkup.new 319 | # result = xm.title("yada") 320 | # # result is a string containing the markup. 321 | # 322 | # buffer = "" 323 | # xm = Builder::XmlMarkup.new(buffer) 324 | # # The markup is appended to buffer (using <<) 325 | # 326 | # xm = Builder::XmlMarkup.new(STDOUT) 327 | # # The markup is written to STDOUT (using <<) 328 | # 329 | # xm = Builder::XmlMarkup.new 330 | # x2 = Builder::XmlMarkup.new(:target=>xm) 331 | # # Markup written to +x2+ will be send to +xm+. 332 | # 333 | # * Indentation is enabled by providing the number of spaces to 334 | # indent for each level as a second argument to XmlBuilder.new. 335 | # Initial indentation may be specified using a third parameter. 336 | # 337 | # Example: 338 | # 339 | # xm = Builder.new(:ident=>2) 340 | # # xm will produce nicely formatted and indented XML. 341 | # 342 | # xm = Builder.new(:indent=>2, :margin=>4) 343 | # # xm will produce nicely formatted and indented XML with 2 344 | # # spaces per indent and an over all indentation level of 4. 345 | # 346 | # builder = Builder::XmlMarkup.new(:target=>$stdout, :indent=>2) 347 | # builder.name { |b| b.first("Jim"); b.last("Weirich) } 348 | # # prints: 349 | # # 350 | # # Jim 351 | # # Weirich 352 | # # 353 | # 354 | # * The instance_eval implementation which forces self to refer to 355 | # the message receiver as self is now obsolete. We now use normal 356 | # block calls to execute the markup block. This means that all 357 | # markup methods must now be explicitly send to the xml builder. 358 | # For instance, instead of 359 | # 360 | # xml.div { strong("text") } 361 | # 362 | # you need to write: 363 | # 364 | # xml.div { xml.strong("text") } 365 | # 366 | # Although more verbose, the subtle change in semantics within the 367 | # block was found to be prone to error. To make this change a 368 | # little less cumbersome, the markup block now gets the markup 369 | # object sent as an argument, allowing you to use a shorter alias 370 | # within the block. 371 | # 372 | # For example: 373 | # 374 | # xml_builder = Builder::XmlMarkup.new 375 | # xml_builder.div { |xml| 376 | # xml.stong("text") 377 | # } 378 | # 379 | class XmlMarkup < XmlBase 380 | 381 | # Create an XML markup builder. Parameters are specified by an 382 | # option hash. 383 | # 384 | # :target=>target_object:: 385 | # Object receiving the markup. +out+ must respond to the 386 | # << operator. The default is a plain string target. 387 | # :indent=>indentation:: 388 | # Number of spaces used for indentation. The default is no 389 | # indentation and no line breaks. 390 | # :margin=>initial_indentation_level:: 391 | # Amount of initial indentation (specified in levels, not 392 | # spaces). 393 | # 394 | def initialize(options={}) 395 | indent = options[:indent] || 0 396 | margin = options[:margin] || 0 397 | super(indent, margin) 398 | @target = options[:target] || "" 399 | end 400 | 401 | # Return the target of the builder. 402 | def target! 403 | @target 404 | end 405 | 406 | def comment!(comment_text) 407 | _ensure_no_block block_given? 408 | _special("", comment_text, nil) 409 | end 410 | 411 | # Insert an XML declaration into the XML markup. 412 | # 413 | # For example: 414 | # 415 | # xml.declare! :ELEMENT, :blah, "yada" 416 | # # => 417 | def declare!(inst, *args, &block) 418 | _indent 419 | @target << "" 435 | _newline 436 | end 437 | 438 | # Insert a processing instruction into the XML markup. E.g. 439 | # 440 | # For example: 441 | # 442 | # xml.instruct! 443 | # #=> 444 | # xml.instruct! :aaa, :bbb=>"ccc" 445 | # #=> 446 | # 447 | def instruct!(directive_tag=:xml, attrs={}) 448 | _ensure_no_block block_given? 449 | if directive_tag == :xml 450 | a = { :version=>"1.0", :encoding=>"UTF-8" } 451 | attrs = a.dup.update attrs # was merge, which isn't available with ruby 1.6.8 452 | end 453 | _special( 454 | "", 456 | nil, 457 | attrs, 458 | [:version, :encoding, :standalone]) 459 | end 460 | 461 | private 462 | 463 | # NOTE: All private methods of a builder object are prefixed when 464 | # a "_" character to avoid possible conflict with XML tag names. 465 | 466 | # Insert text directly in to the builder's target. 467 | def _text(text) 468 | @target << text 469 | end 470 | 471 | # Insert special instruction. 472 | def _special(open, close, data=nil, attrs=nil, order=[]) 473 | _indent 474 | @target << open 475 | @target << data if data 476 | _insert_attributes(attrs, order) if attrs 477 | @target << close 478 | _newline 479 | end 480 | 481 | # Start an XML tag. If end_too is true, then the start 482 | # tag is also the end tag (e.g.
483 | def _start_tag(sym, attrs, end_too=false) 484 | @target << "<#{sym}" 485 | _insert_attributes(attrs) 486 | @target << "/" if end_too 487 | @target << ">" 488 | end 489 | 490 | # Insert an ending tag. 491 | def _end_tag(sym) 492 | @target << "" 493 | end 494 | 495 | # Insert the attributes (given in the hash). 496 | def _insert_attributes(attrs, order=[]) 497 | return if attrs.nil? 498 | order.each do |k| 499 | v = attrs[k] 500 | @target << %{ #{k}="#{v}"} if v 501 | end 502 | attrs.each do |k, v| 503 | @target << %{ #{k}="#{v}"} unless order.member?(k) 504 | end 505 | end 506 | 507 | def _ensure_no_block(got_block) 508 | if got_block 509 | fail IllegalBlockError, 510 | "Blocks are not allowed on XML instructions" 511 | end 512 | end 513 | 514 | end 515 | 516 | end 517 | 518 | --------------------------------------------------------------------------------