zone 0.1.1 → 0.1.2

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.
@@ -0,0 +1,126 @@
1
+ # Zsh Completion for Zone
2
+
3
+ Intelligent command-line completion for the `zone` timezone conversion tool.
4
+
5
+ ## Features
6
+
7
+ - Completes all command-line flags and options
8
+ - Suggests common timezone names (UTC, local, America/New_York, etc.)
9
+ - Provides example timestamp formats
10
+ - Handles mutually exclusive options (e.g., --iso8601 vs --unix)
11
+ - Context-aware completions for option values
12
+
13
+ ## Installation
14
+
15
+ ### Method 1: User-specific installation
16
+
17
+ 1. Create a completions directory if it doesn't exist:
18
+ ```bash
19
+ mkdir -p ~/.zsh/completions
20
+ ```
21
+
22
+ 2. Copy the completion script:
23
+ ```bash
24
+ cp completions/_zone ~/.zsh/completions/
25
+ ```
26
+
27
+ 3. Add to your `~/.zshrc` (before `compinit`):
28
+ ```bash
29
+ fpath=(~/.zsh/completions $fpath)
30
+ autoload -U compinit && compinit
31
+ ```
32
+
33
+ 4. Reload your shell:
34
+ ```bash
35
+ exec zsh
36
+ ```
37
+
38
+ ### Method 2: System-wide installation
39
+
40
+ 1. Copy to the system completions directory:
41
+ ```bash
42
+ sudo cp completions/_zone /usr/local/share/zsh/site-functions/
43
+ ```
44
+
45
+ 2. Rebuild completion cache:
46
+ ```bash
47
+ rm -f ~/.zcompdump && compinit
48
+ ```
49
+
50
+ ### Method 3: Oh My Zsh
51
+
52
+ 1. Copy to Oh My Zsh completions:
53
+ ```bash
54
+ cp completions/_zone ~/.oh-my-zsh/completions/
55
+ ```
56
+
57
+ 2. Reload:
58
+ ```bash
59
+ exec zsh
60
+ ```
61
+
62
+ ## Usage
63
+
64
+ Once installed, press `<Tab>` after typing `zone` to see available completions:
65
+
66
+ ```bash
67
+ zone <Tab> # Shows all options
68
+ zone --zone <Tab> # Shows timezone suggestions
69
+ zone --strftime <Tab> # Prompts for strftime format
70
+ zone 2025 <Tab> # Shows timestamp format examples
71
+ ```
72
+
73
+ ## Examples
74
+
75
+ ```bash
76
+ # Complete timezone names
77
+ zone "$(date)" --zone <Tab>
78
+ # Suggests: UTC, local, America/New_York, Europe/London, Asia/Tokyo, etc.
79
+
80
+ # Complete output formats
81
+ zone "now" --<Tab>
82
+ # Shows: --iso8601, --pretty, --unix, --strftime, etc.
83
+
84
+ # Mutually exclusive options
85
+ zone "now" --unix --<Tab>
86
+ # Won't suggest --iso8601, --pretty, or --strftime (incompatible)
87
+ ```
88
+
89
+ ## Testing
90
+
91
+ Verify the completion is loaded:
92
+ ```bash
93
+ which _zone
94
+ # Should output the path to the completion function
95
+ ```
96
+
97
+ Test syntax:
98
+ ```bash
99
+ zsh -n completions/_zone
100
+ # Should output nothing (syntax valid)
101
+ ```
102
+
103
+ ## Troubleshooting
104
+
105
+ **Completions not working:**
106
+ 1. Ensure `fpath` includes your completions directory:
107
+ ```bash
108
+ echo $fpath
109
+ ```
110
+
111
+ 2. Verify the completion function is loaded:
112
+ ```bash
113
+ which _zone
114
+ ```
115
+
116
+ 3. Rebuild completion cache:
117
+ ```bash
118
+ rm -f ~/.zcompdump && exec zsh
119
+ ```
120
+
121
+ **Completions are outdated:**
122
+ ```bash
123
+ # Force rebuild
124
+ rm -f ~/.zcompdump*
125
+ compinit
126
+ ```
data/completions/_zone ADDED
@@ -0,0 +1,89 @@
1
+ #compdef zone
2
+
3
+ # Zsh completion script for zone
4
+ # https://github.com/anthropics/zone
5
+
6
+ _zone() {
7
+ local context state state_descr line
8
+ typeset -A opt_args
9
+
10
+ local -a output_formats timezones_opts data_opts other_opts
11
+
12
+ output_formats=(
13
+ '(--pretty --unix --strftime)--iso8601[Output in ISO 8601 format (default)]'
14
+ '(--iso8601 --unix --strftime)-p[Output in pretty format]'
15
+ '(--iso8601 --unix --strftime)--pretty[Output in pretty format (e.g., "Jan 02 - 03:04 PM")]'
16
+ '(--iso8601 --pretty --strftime)--unix[Output as Unix timestamp]'
17
+ '(--iso8601 --pretty --unix)-f[Output format using strftime]:format string'
18
+ '(--iso8601 --pretty --unix)--strftime[Output format using strftime]:format string'
19
+ )
20
+
21
+ timezones_opts=(
22
+ '--require[Require external library]:library name'
23
+ '(-z --utc --local)-z[Convert to specified timezone]:timezone:_zone_timezones'
24
+ '(-z --utc --local)--zone[Convert to specified timezone]:timezone:_zone_timezones'
25
+ '(-z --zone --local)--utc[Convert to UTC timezone]'
26
+ '(-z --zone --utc)--local[Convert to local timezone]'
27
+ )
28
+
29
+ data_opts=(
30
+ '(-F)-F[Field index or field name]:field:(1 2 3 4 5)'
31
+ '(-F)--field[Field index or field name]:field:(1 2 3 4 5)'
32
+ '(-d)-d[Field delimiter]:delimiter'
33
+ '(-d)--delimiter[Field delimiter]:delimiter'
34
+ '--headers[Treat first line as headers]'
35
+ )
36
+
37
+ other_opts=(
38
+ '(-v)'{-v,--verbose}'[Enable verbose/debug output]'
39
+ '(-h)'{-h,--help}'[Show help message]'
40
+ )
41
+
42
+ _arguments -C -s \
43
+ $output_formats \
44
+ $timezones_opts \
45
+ $data_opts \
46
+ $other_opts \
47
+ '*:timestamp string:_zone_timestamp'
48
+ }
49
+
50
+ _zone_timezones() {
51
+ local -a timezones
52
+
53
+ # Common timezones for quick completion
54
+ timezones=(
55
+ 'UTC:Coordinated Universal Time'
56
+ 'local:Local system timezone'
57
+ 'America/New_York:Eastern Time'
58
+ 'America/Chicago:Central Time'
59
+ 'America/Denver:Mountain Time'
60
+ 'America/Los_Angeles:Pacific Time'
61
+ 'Europe/London:British Time'
62
+ 'Europe/Paris:Central European Time'
63
+ 'Asia/Tokyo:Japan Standard Time'
64
+ 'Asia/Shanghai:China Standard Time'
65
+ 'Australia/Sydney:Australian Eastern Time'
66
+ )
67
+
68
+ _describe -t timezones 'timezone' timezones
69
+
70
+ # Also allow arbitrary timezone input
71
+ _message -r 'timezone name or fuzzy match'
72
+ }
73
+
74
+ _zone_timestamp() {
75
+ # Suggest common timestamp formats
76
+ local -a formats
77
+ formats=(
78
+ 'now:Current time'
79
+ '$(date):Current date/time from date command'
80
+ '2025-01-15T10:30:00Z:ISO 8601 format'
81
+ '1736937000:Unix timestamp (seconds)'
82
+ '"5 minutes ago":Relative time'
83
+ '"1 hour from now":Relative future time'
84
+ )
85
+
86
+ _describe -t formats 'timestamp format' formats
87
+ }
88
+
89
+ _zone "$@"
@@ -0,0 +1,150 @@
1
+ # User Experience Review of Zone
2
+
3
+ ## My Thoughts on Zone as a User
4
+
5
+ ### What Zone Does Really Well
6
+
7
+ **1. Solves a Real Pain Point**
8
+ Timezone conversion is genuinely annoying. Looking at `2025-01-15T10:30:00Z` in logs and mentally calculating "what time was that for me?" is cognitive overhead. Zone eliminates that instantly. The name is perfect - simple, memorable, and describes exactly what it does.
9
+
10
+ **2. The Dual-Mode Architecture is Brilliant**
11
+ The pattern/field mode split is exactly right:
12
+ - **Pattern mode** feels like `bat` for timestamps - magical and effortless. Just pipe text through and timestamps become readable. No configuration needed.
13
+ - **Field mode** handles the structured data case without polluting the simple case with complexity.
14
+
15
+ Requiring explicit `--field` AND `--delimiter` was the right call. Auto-detection adds complexity and surprising behavior. Explicit is better.
16
+
17
+ **3. The New Defaults are Perfect**
18
+ Before: `zone "2025-01-15T10:30:00Z"` → `2025-01-15T10:30:00Z` (no visible change)
19
+ After: `zone "2025-01-15T10:30:00Z"` → `Jan 15, 2025 - 5:30 AM EST` (immediate value)
20
+
21
+ This is *excellent* UX design. New users see the tool working immediately. The "principle of least surprise" - of course I want my local time, that's why I'm using a timezone tool!
22
+
23
+ **4. Idempotency is Underrated**
24
+ Being able to pipe zone output back through zone is surprisingly powerful. It means zone becomes composable - you can chain transformations without worrying about format compatibility. This is Unix philosophy done right.
25
+
26
+ **5. The Three Pretty Formats are Well-Chosen**
27
+ - `-p 1`: North American default, maximum readability
28
+ - `-p 2`: International/technical users, 24-hour
29
+ - `-p 3`: Sortable, machine-friendly but still readable
30
+
31
+ The progression makes sense and covers real use cases without format proliferation.
32
+
33
+ ### What Could Be Even Better
34
+
35
+ **1. Discovery of Pretty Format Variants**
36
+ Running `zone --help` shows:
37
+ ```
38
+ -p, --pretty [STYLE] Pretty format (1=12hr, 2=24hr, 3=ISO-compact, default: 1)
39
+ ```
40
+
41
+ This is compact but not discoverable. A user has to know to try `-p 2` to see what it looks like. Could consider:
42
+ - Showing example output in `--help` for each format
43
+ - Or: `zone --formats` command that prints all format examples
44
+
45
+ **2. Relative Time Parsing Feels Incomplete**
46
+ Zone parses "5 hours ago" but the pattern doesn't match common formats like:
47
+ - "2 hours ago" (GitHub, Twitter, etc.)
48
+ - "in 3 days" (Google Calendar)
49
+ - "yesterday" / "tomorrow"
50
+
51
+ Either commit fully to natural language parsing (integrate with `chronic` gem?) or remove it entirely. Half-done features are confusing.
52
+
53
+ **3. Color Choice is Good but Could Be Configurable**
54
+ Cyan for timestamps is reasonable, but some users might want:
55
+ - Different colors for different timezones (red=past, green=future)
56
+ - Bold instead of color
57
+ - User-configurable via env var
58
+
59
+ Not critical, but would be nice for power users.
60
+
61
+ **4. Error Messages Could Be More Helpful**
62
+ ```bash
63
+ $ echo "invalid" | zone --field 2 --delimiter ','
64
+ Error: Could not parse time 'invalid'
65
+ ```
66
+
67
+ Could suggest: "Does not look like a timestamp. Expected formats: ISO8601, Unix timestamp, etc."
68
+
69
+ **5. The Name "Pretty" Format**
70
+ Calling it "pretty" format is subjective. Some users might think ISO8601 is "pretty" (it's certainly more standardized). Consider:
71
+ - "human" format (human-readable)
72
+ - "readable" format
73
+ - Just "format 1/2/3" without the "pretty" label
74
+
75
+ Minor bikeshedding, but naming matters.
76
+
77
+ ### Outstanding Design Decisions
78
+
79
+ **1. No Magic, Clear Contracts**
80
+ - Field mode requires explicit delimiter ✓
81
+ - Pattern mode is clearly the default ✓
82
+ - Flags do one thing ✓
83
+
84
+ No surprises, no hidden behavior.
85
+
86
+ **2. Fuzzy Timezone Matching**
87
+ ```bash
88
+ zone --zone tokyo # Just works
89
+ zone --zone pacific # Just works
90
+ zone --zone "new york" # Just works
91
+ ```
92
+
93
+ This is *delightful*. Makes the tool feel intelligent without being magical.
94
+
95
+ **3. No External Dependencies (Base)**
96
+ Pure Ruby stdlib (until you `--require active_support`). Fast startup, easy installation, no surprises.
97
+
98
+ **4. The Pattern Numbering System (P01_, P02_)**
99
+ This is clever engineering:
100
+ - Easy to add new patterns (just add P08_)
101
+ - Priority is explicit and visible
102
+ - Self-documenting code
103
+
104
+ Really nice.
105
+
106
+ ### Use Cases Where Zone Shines
107
+
108
+ 1. **Log Analysis**: `tail -f app.log | zone` - instant readability
109
+ 2. **API Development**: Converting timestamps in JSON responses during debugging
110
+ 3. **Data Migration**: Converting CSV/TSV timestamp columns
111
+ 4. **Slack/IRC Bots**: Processing messages with embedded timestamps
112
+ 5. **International Teams**: Converting meeting times across timezones
113
+
114
+ ### Competitive Analysis
115
+
116
+ **vs `date` command**: Zone is *way* easier for interactive use. Compare:
117
+ ```bash
118
+ # date
119
+ date -d "2025-01-15T10:30:00Z" "+%b %d, %Y - %I:%M %p %Z"
120
+
121
+ # zone
122
+ zone "2025-01-15T10:30:00Z"
123
+ ```
124
+
125
+ Zone wins on ergonomics by a mile.
126
+
127
+ **vs `dateutils`**: More powerful but also more complex. Zone targets the 80% use case perfectly.
128
+
129
+ **vs Online Converters**: Zone works offline, in pipelines, is automatable. Different category.
130
+
131
+ ### Final Verdict
132
+
133
+ Zone is a **10/10 tool for its intended use case**. It's:
134
+ - Simple enough for beginners (just type `zone` with a timestamp)
135
+ - Powerful enough for experts (field mode, regex delimiters, chaining)
136
+ - Well-documented
137
+ - Follows Unix philosophy
138
+ - Actually useful in daily work
139
+
140
+ The recent changes (colors, pretty formats, better defaults) transformed it from "neat utility" to "essential tool I'd add to every machine."
141
+
142
+ ### One More Thing
143
+
144
+ The pattern mode really feels like magic the first time you use it:
145
+ ```bash
146
+ echo "Server started at 1736937000 and crashed at 1736940600" | zone
147
+ # Server started at Jan 15, 2025 - 5:30 AM EST and crashed at Jan 15, 2025 - 6:30 AM EST
148
+ ```
149
+
150
+ That's the kind of "just works" experience that makes a tool memorable. Well done.
data/exe/zone CHANGED
@@ -2,269 +2,6 @@
2
2
 
3
3
  # frozen_string_literal: true
4
4
 
5
- require 'rubygems'
6
- require 'logger'
7
- require 'optparse'
8
- require 'tzinfo'
9
- require 'time'
10
- require 'date'
5
+ require_relative '../lib/zone'
11
6
 
12
- options = { delimiter: nil, strftime: nil, iso8601: false, pretty: false, headers: false, unix: false, index: 1, zone: nil, utc: false, local: false }
13
- parser = OptionParser.new do |parser|
14
- parser.banner = "Usage: zone [options] [timestamps...]"
15
- parser.separator ""
16
- parser.separator "Output Formats:"
17
- parser.on '--iso8601', 'Output in ISO 8601 (default: true)'
18
- parser.on '--strftime FORMAT', '-f', 'Output format using strftime (default: none)'
19
- parser.on '--pretty', 'Output in pretty format (e.g., "Jan 02 - 03:04 PM")'
20
- parser.on '--unix', 'Output as Unix timestamp (default: false)'
21
-
22
- parser.separator ""
23
- parser.separator "Timezones:"
24
- parser.on '--zone TZ', 'Convert to time zone (default: local time zone)'
25
- parser.on '--local', 'Convert to local time zone (alias for --zone local)'
26
- parser.on '--utc', 'Convert to UTC time zone (alias for --zone UTC)'
27
-
28
- parser.separator ""
29
- parser.separator "Data Processing:"
30
- parser.on '--index N', '-i N', Integer, 'Index of the field to convert (default: 1)'
31
- parser.on '--delimiter PATTERN', '-d', 'Field delimiter (default: space)'
32
- parser.on '--headers', 'Skip the first line as headers'
33
-
34
- parser.separator ""
35
- parser.separator "Other:"
36
- parser.on '--verbose', '-v', 'Enable verbose/debug output'
37
- parser.on '--help', '-h', 'Show this help message' do
38
- puts parser
39
- exit
40
- end
41
- end
42
-
43
- parser.parse!(into: options)
44
-
45
- COLORS = {
46
- reset: "\e[0m",
47
- bold: "\e[1m",
48
- cyan: "\e[36m",
49
- yellow: "\e[33m",
50
- red: "\e[31m",
51
- gray: "\e[90m"
52
- }
53
-
54
- $logger = Logger.new($stderr).tap do |l|
55
- l.formatter = ->(severity, _datetime, _progname, message) {
56
- color = case severity
57
- when "INFO" then COLORS[:cyan]
58
- when "WARN" then COLORS[:yellow]
59
- when "ERROR" then COLORS[:red]
60
- else COLORS[:gray]
61
- end
62
-
63
- prefix = case severity
64
- when "INFO" then "→"
65
- when "WARN" then "⚠"
66
- when "ERROR" then "✗"
67
- else "·"
68
- end
69
-
70
- "#{color}#{prefix} #{message}#{COLORS[:reset]}\n"
71
- }
72
- l.level = options.delete(:verbose) ? Logger::INFO : Logger::WARN
73
- end
74
-
75
- format = (
76
- case options
77
- in { strftime: nil, iso8601: true, unix: false, pretty: false } then :iso8601
78
- in { strftime: nil, iso8601: false, unix: true, pretty: false } then :unix
79
- in { strftime: String, iso8601: false, unix: false, pretty: false } then :strftime
80
- in { strftime: nil, iso8601: false, unix: false, pretty: true } then :pretty
81
- in { strftime: nil, iso8601: false, unix: false, pretty: false } then :iso8601
82
- else
83
- $logger.error 'Error: Only one of --strftime, --iso8601, or --unix can be specified.'
84
- exit 1
85
- end
86
- )
87
-
88
- zone = (
89
- case options
90
- in { utc: true, local: false, zone: nil } then 'utc'
91
- in { utc: false, local: true, zone: nil } then 'local'
92
- in { zone: nil, utc: false, local: false } then 'utc'
93
- in { zone: String => z, utc: false, local: false } then z
94
- else
95
- $logger.error 'Error: Only one of --zone, --local, or --utc can be specified.'
96
- exit 1
97
- end
98
- )
99
-
100
- class TimezoneSearch
101
- attr_reader :keyword, :debug
102
-
103
- def self.all_zones
104
- TZInfo::Timezone.all_identifiers
105
- end
106
-
107
- def initialize(
108
- keyword,
109
- debug: false,
110
- logger: $logger || Logger.new($stderr)
111
- )
112
- @keyword = keyword
113
- @debug = debug
114
- @logger = logger
115
- end
116
-
117
- def execute
118
- begin
119
- TZInfo::Timezone.get(keyword)
120
- rescue TZInfo::InvalidTimezoneIdentifier
121
- search_wildcard
122
- end
123
- end
124
-
125
- def us_wildcard
126
- keyword
127
- .gsub(/^(?:US)?\/?/, 'US/')
128
- .gsub(/$/,'.*')
129
- .then { Regexp.new(it, Regexp::IGNORECASE) }
130
- end
131
-
132
- def all_wildcard
133
- Regexp.new(".*#{keyword}.*", Regexp::IGNORECASE)
134
- end
135
-
136
- def search_wildcard
137
- case self.class.all_zones
138
- in [*, ^(us_wildcard) => found_zone, *]
139
- in [*, ^(all_wildcard) => found_zone, *]
140
- else nil
141
- end
142
-
143
- return unless found_zone
144
-
145
- TZInfo::Timezone.get(found_zone).tap do
146
- @logger.info "Using time zone '#{found_zone}' matching pattern '#{@keyword}'."
147
- end
148
- end
149
-
150
- private
151
-
152
- def log(message)
153
- warn message if @debug
154
- end
155
- end
156
-
157
- zone_callables = Hash.new do |hash, key|
158
- hash[key] = ->(time) {
159
- search = TimezoneSearch.new(key)
160
- if zone = search.execute
161
- zone.to_local(time)
162
- else
163
- $logger.warn "Error: Invalid time zone identifier '#{key}'."
164
- time
165
- end
166
- }
167
- end
168
-
169
- zone_callables.merge!(
170
- 'utc' => ->(t) { t.utc },
171
- 'local' => ->(t) { t.localtime },
172
- 'UTC' => ->(t) { t.utc },
173
- )
174
-
175
- actual_index = options[:index] - 1
176
- zone_callable = zone_callables[zone]
177
-
178
- # Detect if args are timestamps (not filenames)
179
- timestamps = []
180
- if ARGV.any? && ARGV.all? { |arg| arg.match?(/^\d/) || arg.match?(/[A-Z][a-z]{2}/) || arg.match?(/:/) }
181
- $logger.info "Treating arguments as timestamp strings."
182
- timestamps = ARGV.dup
183
- ARGV.clear # Clear so ARGF will read from STDIN if piped
184
- end
185
-
186
- # Build input enumerable
187
- input = (
188
- if timestamps.any?
189
- timestamps.each
190
- elsif ARGV.any? || !STDIN.tty?
191
- ARGF.each_line(chomp: true)
192
- else
193
- [Time.now.to_s].each
194
- end
195
- )
196
-
197
- input.each do |line|
198
- case [options, $.]
199
- in { headers: true }, 1 then next
200
- in { delimiter: String => delimiter }
201
- in { delimiter: nil }
202
- $logger.info "Auto-detecting delimiter for line: #{line.inspect}"
203
- options[:delimiter] = (
204
- case line
205
- in /,\s*/
206
- $logger.info "Using comma with whitespace as delimiter."
207
- /,\s*/
208
- in /\t/
209
- $logger.info "Using tab as delimiter."
210
- "\t"
211
- in /,/
212
- $logger.info "Using comma as delimiter."
213
- ','
214
- else
215
- $logger.info "Could not detect delimiter. Using whitespace."
216
- /\s+/
217
- end
218
- )
219
- else
220
- ""
221
- end
222
-
223
- delimiter = options[:delimiter] || ""
224
-
225
- fields = (
226
- case [line, delimiter]
227
- in String, "" then [line]
228
- in String, /^.+$/ then line.split(delimiter)
229
- else [line]
230
- end
231
- )
232
-
233
- target = fields[actual_index]
234
-
235
- time = (
236
- begin
237
- case target
238
- in Time then target
239
- in DateTime then target.to_time
240
- in Date then target.to_time
241
- in /^[0-9\.]+$/
242
- Time.at(target.to_f)
243
- else
244
- DateTime.parse(target).to_time
245
- end
246
- rescue StandardError => e
247
- $logger.warn "Warning: Could not parse time '#{target}'. Skipping line."
248
- $logger.warn " #{e.class}: #{e.message}"
249
- next
250
- end
251
- )
252
-
253
- converted = zone_callable.call(time)
254
- formatted = (
255
- case format
256
- in :pretty
257
- if (Time.now - converted).abs > 30 * 24 * 60 * 60
258
- converted.strftime("%b %d, %Y - %I:%M %p %Z")
259
- else
260
- converted.strftime("%b %d - %I:%M %p %Z")
261
- end
262
- in :strftime then converted.strftime(options[:strftime])
263
- in :iso8601 then converted.iso8601
264
- in :unix then converted.to_i
265
- end
266
- )
267
-
268
- fields[actual_index] = formatted
269
- puts fields.join(delimiter)
270
- end
7
+ Zone::CLI.run(ARGV)
data/lib/zone/cli.rb ADDED
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'options'
4
+ require_relative 'input'
5
+ require_relative 'output'
6
+ require_relative 'transform'
7
+ require_relative 'colors'
8
+ require_relative 'logging'
9
+ require_relative 'pattern'
10
+ require_relative 'field'
11
+
12
+ module Zone
13
+ class CLI
14
+ def self.run(argv)
15
+ new(argv).run
16
+ end
17
+
18
+ def initialize(argv)
19
+ @argv = argv
20
+ end
21
+
22
+ def run
23
+ options = Options.new
24
+ options.parse!(@argv)
25
+ options.validate!
26
+
27
+ setup_logger!(options.verbose)
28
+ setup_active_support!
29
+
30
+ input = Input.new(@argv)
31
+ output = Output.new(color_mode: options.color)
32
+ transformation = Transform.build(zone: options.zone, format: options.format)
33
+
34
+ if options.field
35
+ Field.process(input, output, transformation, options, @logger)
36
+ else
37
+ Pattern.process(input, output, transformation, @logger)
38
+ end
39
+ rescue OptionParser::MissingArgument, OptionParser::InvalidOption, OptionParser::InvalidArgument => e
40
+ $stderr.puts Colors.colors($stderr).red("Error:") + " #{e.message}"
41
+ $stderr.puts "Run 'zone --help' for usage information."
42
+ exit 1
43
+ rescue ArgumentError, StandardError => e
44
+ message = e.message.gsub(/'([^']+)'/) do
45
+ "'#{Colors.colors($stderr).bold($1)}'"
46
+ end
47
+ $stderr.puts Colors.colors($stderr).red("Error:") + " #{message}"
48
+ exit 1
49
+ end
50
+
51
+ private
52
+
53
+ def setup_logger!(verbose)
54
+ @logger = Logging.build(verbose: verbose)
55
+ end
56
+
57
+ def setup_active_support!
58
+ if defined?(ActiveSupport)
59
+ ActiveSupport.to_time_preserves_timezone = true
60
+ end
61
+ end
62
+
63
+ end
64
+ end