1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-07 03:50:08 +02:00

🎨 Cleanup code

This commit is contained in:
2021-01-26 18:42:26 +01:00
parent 0f6e47a594
commit 77f4de8ca9
10 changed files with 258 additions and 205 deletions

View File

@ -15,7 +15,7 @@
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; };
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; };
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; };
C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */; };
C41C1B4B22B019FF00E7CF16 /* PhpInstall.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpInstall.swift */; };
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Actions.swift */; };
C42295DD2358D02000E263B2 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; };
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; };
@ -44,7 +44,7 @@
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = phpmon.entitlements; sourceTree = "<group>"; };
C41C1B4622B009A400E7CF16 /* Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = "<group>"; };
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarImageGenerator.swift; sourceTree = "<group>"; };
C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpVersion.swift; sourceTree = "<group>"; };
C41C1B4A22B019FF00E7CF16 /* PhpInstall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpInstall.swift; sourceTree = "<group>"; };
C41C1B4C22B0215A00E7CF16 /* Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; };
C42295DC2358D02000E263B2 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
C46FA23E246C358E00944F05 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = "<group>"; };
@ -124,6 +124,7 @@
C41E181722CB61EB0072CF09 /* Classes */ = {
isa = PBXGroup;
children = (
C4B13B1D25C4915000548C3A /* Core */,
C47331A0247093AC009A0597 /* Menu */,
C4811D2722D70D8E00B5F6B3 /* Commands */,
C4811D2822D70D9C00B5F6B3 /* Helpers */,
@ -173,14 +174,21 @@
isa = PBXGroup;
children = (
C476FF9722B0DD830098105B /* Alert.swift */,
C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */,
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */,
C474B00524C0E98C00066A22 /* LocalNotification.swift */,
C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */,
);
path = Helpers;
sourceTree = "<group>";
};
C4B13B1D25C4915000548C3A /* Core */ = {
isa = PBXGroup;
children = (
C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */,
C41C1B4A22B019FF00E7CF16 /* PhpInstall.swift */,
);
path = Core;
sourceTree = "<group>";
};
C4F8C0A222D4F100002EFE61 /* Extensions */ = {
isa = PBXGroup;
children = (
@ -273,7 +281,7 @@
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */,
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */,
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */,
C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */,
C41C1B4B22B019FF00E7CF16 /* PhpInstall.swift in Sources */,
C486EFFC2586931100A02B2C /* PhpMenuItem.swift in Sources */,
C49EAB46259FC305007F6C3B /* Paths.swift in Sources */,
C476FF9822B0DD830098105B /* Alert.swift in Sources */,

View File

@ -11,7 +11,10 @@ import AppKit
class Actions {
public static func detectPhpVersions() -> [String] {
// MARK: - Detect PHP Versions
public static func detectPhpVersions() -> [String]
{
let files = Shell.user.pipe("ls \(Paths.optPath()) | grep php@")
var versions = files.components(separatedBy: "\n")
@ -30,6 +33,7 @@ class Actions {
// The user may have `php` installed, but not e.g. `php@8.0`
// We should also detect that as a version that is installed
let phpAlias = App.shared.brewPhpVersion
if (!versionsOnly.contains(phpAlias)) {
versionsOnly.append(phpAlias);
}
@ -37,23 +41,21 @@ class Actions {
return versionsOnly
}
public static func restartPhpFpm() {
let version = App.shared.currentVersion!.short
if (version == App.shared.brewPhpVersion) {
Shell.user.run("sudo \(Paths.brew()) services restart php")
} else {
Shell.user.run("sudo \(Paths.brew()) services restart php@\(version)")
}
// MARK: - Services
public static func restartPhpFpm()
{
brew("services restart \(App.phpInstall!.formula)", sudo: true)
}
public static func restartNginx()
{
Shell.user.run("sudo \(Paths.brew()) services restart nginx")
brew("services restart nginx", sudo: true)
}
public static func restartDnsMasq()
{
Shell.user.run("sudo \(Paths.brew()) services restart dnsmasq")
brew("services restart dnsmasq", sudo: true)
}
/**
@ -65,7 +67,8 @@ class Actions {
Please note that depending on which version is installed,
the version that is switched to may or may not be identical to `php` (without @version).
*/
public static func switchToPhpVersion(version: String, availableVersions: [String]) {
public static func switchToPhpVersion(version: String, availableVersions: [String])
{
availableVersions.forEach { (available) in
let formula = (available == App.shared.brewPhpVersion) ? "php" : "php@\(available)"
Shell.user.run("\(Paths.brew()) unlink \(formula)")
@ -77,22 +80,30 @@ class Actions {
Shell.user.run("sudo \(Paths.brew()) services start \(formula)")
}
public static func openGenericPhpConfigFolder() {
// MARK: - Finding Config Files
public static func openGenericPhpConfigFolder()
{
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath())/php")];
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
}
public static func openPhpConfigFolder(version: String) {
public static func openPhpConfigFolder(version: String)
{
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath())/php/\(version)/php.ini")];
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
}
public static func openValetConfigFolder() {
public static func openValetConfigFolder()
{
let files = [NSURL(fileURLWithPath: NSString(string: "~/.config/valet").expandingTildeInPath)];
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
}
public static func didFindXdebug(_ version: String) -> Bool {
// MARK: - Xdebug Actions
public static func didFindXdebug(_ version: String) -> Bool
{
let command = """
grep -q 'zend_extension="xdebug.so"' \(Paths.etcPath())/php/\(version)/php.ini; [ $? -eq 0 ] && echo "YES" || echo "NO"
"""
@ -100,7 +111,8 @@ class Actions {
return (output == "YES")
}
public static func didEnableXdebug(_ version: String) -> Bool {
public static func didEnableXdebug(_ version: String) -> Bool
{
let command = """
grep -q '; zend_extension="xdebug.so"' \(Paths.etcPath())/php/\(version)/php.ini; [ $? -eq 0 ] && echo "YES" || echo "NO"
"""
@ -108,43 +120,48 @@ class Actions {
return (output == "NO")
}
public static func toggleXdebug() {
let version = App.shared.currentVersion?.short
public static func toggleXdebug()
{
let version = App.phpInstall!.version.short
var command = """
sed -i '' 's/; zend_extension="xdebug.so"/zend_extension="xdebug.so"/g' \(Paths.etcPath())/php/\(version!)/php.ini
sed -i '' 's/; zend_extension="xdebug.so"/zend_extension="xdebug.so"/g' \(Paths.etcPath())/php/\(version)/php.ini
"""
if (self.didEnableXdebug(version!)) {
if (self.didEnableXdebug(version)) {
command = """
sed -i '' 's/zend_extension="xdebug.so"/; zend_extension="xdebug.so"/g' \(Paths.etcPath())/php/\(version!)/php.ini
sed -i '' 's/zend_extension="xdebug.so"/; zend_extension="xdebug.so"/g' \(Paths.etcPath())/php/\(version)/php.ini
"""
}
Shell.user.run(command)
}
// MARK: - Quick Fix
/**
Detects all currently available PHP versions, and unlinks each and every one of them.
After this, the brew services are also stopped, the latest PHP version is linked, and php + nginx are restarted.
If this does not solve the issue, the user may need to install additional extensions and/or run `composer global update`.
*/
public static func fixMyPhp() {
Shell.user.run("sudo \(Paths.brew()) services stop dnsmasq")
Shell.user.run("sudo \(Paths.brew()) services start dnsmasq")
let versions = self.detectPhpVersions()
versions.forEach { (version) in
Shell.user.run("\(Paths.brew()) unlink php@\(version)")
if (version == App.shared.brewPhpVersion) {
Shell.user.run("\(Paths.brew()) services stop php")
Shell.user.run("sudo \(Paths.brew()) services stop php")
} else {
Shell.user.run("\(Paths.brew()) services stop php@\(version)")
Shell.user.run("sudo \(Paths.brew()) services stop php@\(version)")
}
}
Shell.user.run("\(Paths.brew()) services stop php")
Shell.user.run("\(Paths.brew()) services stop nginx")
Shell.user.run("\(Paths.brew()) link php")
Shell.user.run("sudo \(Paths.brew()) services restart dnsmasq")
Shell.user.run("sudo \(Paths.brew()) services restart php")
Shell.user.run("sudo \(Paths.brew()) services restart nginx")
public static func fixMyPhp()
{
brew("services restart dnsmasq", sudo: true)
self.detectPhpVersions().forEach { (version) in
let formula = (version == App.shared.brewPhpVersion) ? "php" : "php@\(version)"
brew("unlink php@\(version)")
brew("services stop \(formula)")
brew("services stop \(formula)", sudo: true)
}
brew("services stop php")
brew("services stop nginx")
brew("link php")
brew("services restart dnsmasq", sudo: true)
brew("services stop php", sudo: true)
brew("services stop nginx", sudo: true)
}
private static func brew(_ command: String, sudo: Bool = false)
{
Shell.user.run("\(sudo ? "sudo " : "")" + "\(Paths.brew()) \(command)")
}
}

View File

@ -105,19 +105,14 @@ class Startup {
messageText: String,
informativeText: String,
breaking: Bool
)
{
if (condition) {
// Only breaking issues will cause the notification
if (breaking) {
self.failed = true
}
) {
if (!condition) { return }
self.failed = breaking
DispatchQueue.main.async {
// Present the information to the user
_ = Alert.present(
messageText: messageText,
informativeText: informativeText
)
Alert.notify(message: messageText, info: informativeText)
// Only breaking issues will throw the extra retry modal
if (breaking) {
self.failureCallback()
@ -125,4 +120,3 @@ class Startup {
}
}
}
}

View File

@ -0,0 +1,64 @@
//
// PhpVersionExtractor.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 11/06/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved.
//
import Foundation
class PhpInstall {
var version: Version = Version()
var xdebug: Xdebug = Xdebug()
// MARK: - Computed
var formula: String {
return (self.version.short == App.shared.brewPhpVersion) ? "php" : "php@\(self.version.short)"
}
// MARK: - Initializer
init() {
let version = Command.execute(path: Paths.php(), arguments: ["-r", "print phpversion();"])
if (version == "" || version.contains("Warning")) {
self.version.short = "💩 BROKEN"
self.version.long = "";
self.version.error = true
return;
}
// That's the long version
self.version.long = version
// Next up, let's strip away the minor version number
let segments = self.version.long.components(separatedBy: ".")
// Get the first two elements
self.version.short = segments[0...1].joined(separator: ".")
// Determine the Xdebug status
self.xdebug = Xdebug(
found: Actions.didFindXdebug(self.version.short),
enabled: Actions.didEnableXdebug(self.version.short)
)
self.version.error = false
}
// MARK: - Structs
struct Version {
var short = "???"
var long = "???"
var error = false
}
struct Xdebug {
var found: Bool = false
var enabled: Bool = false
}
}

View File

@ -24,4 +24,8 @@ class Alert {
}
return alert.runModal() == .alertFirstButtonReturn
}
public static func notify(message: String, info: String) {
_ = self.present(messageText: message, informativeText: info, buttonTitle: "OK", secondButtonTitle: "")
}
}

View File

@ -1,51 +0,0 @@
//
// PhpVersionExtractor.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 11/06/2019.
// Copyright © 2019 Nico Verbruggen. All rights reserved.
//
import Foundation
class PhpVersion {
var short : String = "???"
var long : String = "???"
var xdebugFound: Bool = false
var xdebugEnabled : Bool = false
var error : Bool = false
init() {
let version = Command.execute(
path: Paths.php(),
arguments: ["-r", "print phpversion();"]
)
if (version == "" || version.contains("Warning")) {
self.short = "💩 BROKEN"
self.long = "";
self.error = true
return;
}
// That's the long version
self.long = version
// Next up, let's strip away the minor version number
let segments = long.components(separatedBy: ".")
// Get the first two elements
self.short = segments[0...1].joined(separator: ".")
// Load xdebug support
self.xdebugFound = Actions.didFindXdebug(self.short)
if (self.xdebugFound) {
self.xdebugEnabled = Actions.didEnableXdebug(self.short)
}
self.error = false;
}
}

View File

@ -12,11 +12,13 @@ class StatusMenu : NSMenu {
public func addPhpVersionMenuItems()
{
var string = "mi_unsure".localized
if (App.shared.currentVersion != nil) {
if (!App.shared.currentVersion!.error) {
if (App.shared.currentInstall == nil) {
return
}
if (!App.phpInstall!.version.error) {
// in case the php version loaded without issue
string = "\("mi_php_version".localized) \(App.shared.currentVersion!.long)"
let string = "\("mi_php_version".localized) \(App.phpInstall!.version.long)"
self.addItem(NSMenuItem(title: string, action: nil, keyEquivalent: ""))
} else {
// in case of an error show the error message
@ -26,21 +28,31 @@ class StatusMenu : NSMenu {
}
}
}
}
public func addPhpActionMenuItems()
{
if (App.shared.availablePhpVersions.count > 0 && !App.shared.busy) {
if App.busy {
self.addItem(NSMenuItem(title: "mi_busy".localized, action: nil, keyEquivalent: ""))
return
}
if App.shared.availablePhpVersions.count == 0 {
return
}
var shortcutKey = 1
for index in (0..<App.shared.availablePhpVersions.count).reversed() {
let version = App.shared.availablePhpVersions[index]
let action = #selector(MainMenu.switchToPhpVersion(sender:))
let brew = (version == App.shared.brewPhpVersion) ? "php" : "php@\(version)"
let menuItem = PhpMenuItem(title: "\("mi_php_switch".localized) \(version) (\(brew))", action: (version == App.shared.currentVersion?.short) ? nil : action, keyEquivalent: "\(shortcutKey)")
let menuItem = PhpMenuItem(
title: "\("mi_php_switch".localized) \(version) (\(brew))",
action: (version == App.phpInstall?.version.short) ? nil : action, keyEquivalent: "\(shortcutKey)"
)
menuItem.version = version
shortcutKey = shortcutKey + 1
self.addItem(menuItem)
}
self.addItem(NSMenuItem.separator())
self.addItem(NSMenuItem(title: "mi_active_services".localized, action: nil, keyEquivalent: ""))
self.addItem(NSMenuItem(title: "mi_restart_dnsmasq".localized, action: #selector(MainMenu.restartDnsMasq), keyEquivalent: "d"))
@ -50,48 +62,42 @@ class StatusMenu : NSMenu {
self.addItem(NSMenuItem.separator())
self.addItem(NSMenuItem(title: "mi_diagnostics".localized, action: nil, keyEquivalent: ""))
self.addItem(NSMenuItem(title: "mi_force_load_latest".localized, action: #selector(MainMenu.forceRestartLatestPhp), keyEquivalent: "f"))
}
if (App.shared.busy) {
self.addItem(NSMenuItem(title: "mi_busy".localized, action: nil, keyEquivalent: ""))
}
}
public func addPhpConfigurationMenuItems()
{
if (App.shared.currentVersion != nil) {
if App.shared.currentInstall == nil {
return
}
self.addItem(NSMenuItem(title: "mi_configuration".localized, action: nil, keyEquivalent: ""))
self.addItem(NSMenuItem(title: "mi_valet_config".localized, action: #selector(MainMenu.openValetConfigFolder), keyEquivalent: "v"))
self.addItem(NSMenuItem(title: "mi_php_config".localized, action: #selector(MainMenu.openActiveConfigFolder), keyEquivalent: "c"))
self.addItem(NSMenuItem(title: "mi_phpinfo".localized, action: #selector(MainMenu.openPhpInfo), keyEquivalent: "i"))
self.addItem(NSMenuItem.separator())
self.addItem(NSMenuItem(title: "mi_enabled_extensions".localized, action: nil, keyEquivalent: ""))
self.addXdebugMenuItem()
}
}
private func addXdebugMenuItem()
{
let xdebugFound = App.shared.currentVersion!.xdebugFound
if (xdebugFound) {
let xdebugOn = App.shared.currentVersion!.xdebugEnabled
let xdebugToggleMenuItem = NSMenuItem(
title: "mi_xdebug".localized,
let xdebugFound = App.phpInstall!.xdebug.found
let xdebugOn = App.phpInstall!.xdebug.enabled
let menuItem = NSMenuItem(
title: xdebugFound ? "mi_xdebug".localized : "mi_xdebug_missing".localized,
action: #selector(MainMenu.toggleXdebug), keyEquivalent: "x"
)
if (xdebugOn) {
xdebugToggleMenuItem.state = .on
}
self.addItem(xdebugToggleMenuItem)
if (!xdebugFound) {
menuItem.isEnabled = false
} else {
let disabledItem = NSMenuItem(
title: "mi_xdebug_missing".localized,
action: nil, keyEquivalent: "x"
)
disabledItem.isEnabled = false
self.addItem(disabledItem)
menuItem.state = xdebugOn ? .on : .off
}
self.addItem(menuItem)
}
}

View File

@ -12,15 +12,23 @@ class App {
static let shared = App()
static var phpInstall: PhpInstall? {
return App.shared.currentInstall
}
static var busy: Bool {
return App.shared.busy
}
/**
Whether the application is busy switching versions.
*/
var busy: Bool = false
/**
The currently active version of PHP.
The currently active installation of PHP.
*/
var currentVersion: PhpVersion? = nil
var currentInstall: PhpInstall? = nil
/**
All available versions of PHP.

View File

@ -29,11 +29,9 @@ class MainMenu: NSObject, NSWindowDelegate {
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
// Perform environment boot checks
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
Startup().checkEnvironment(success: {
self.onEnvironmentPass()
}, failure: {
self.onEnvironmentFail()
})
Startup().checkEnvironment(success: { self.onEnvironmentPass() },
failure: { self.onEnvironmentFail() }
)
}
}
@ -66,11 +64,12 @@ class MainMenu: NSObject, NSWindowDelegate {
buttonTitle: "alert.cannot_start.close".localized,
secondButtonTitle: "alert.cannot_start.retry".localized
)
if (!close) {
self.startup()
} else {
if (close) {
exit(1)
}
self.startup()
}
}
@ -137,7 +136,7 @@ class MainMenu: NSObject, NSWindowDelegate {
while updating the UI as required. As long as the completion callback
does not fire, the app is presumed to be busy and the UI reflects this.
- Parameter execute: Escaping callback of the work that needs to happen.
- Parameter execute: Callback of the work that needs to happen.
- Parameter completion: Callback that is fired when the work is done.
*/
private func waitAndExecute(_ execute: @escaping () -> Void, _ completion: @escaping () -> Void = {})
@ -159,16 +158,16 @@ class MainMenu: NSObject, NSWindowDelegate {
// MARK: - User Interface
@objc func updatePhpVersionInStatusBar() {
App.shared.currentVersion = PhpVersion()
App.shared.currentInstall = PhpInstall()
DispatchQueue.main.async {
if (App.shared.busy) {
DispatchQueue.main.async {
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
}
} else {
DispatchQueue.main.async {
self.setStatusBarImage(version: App.shared.currentVersion!.short)
self.setStatusBarImage(version: App.phpInstall!.version.short)
}
}
self.update()
}
@ -223,28 +222,26 @@ class MainMenu: NSObject, NSWindowDelegate {
@objc public func forceRestartLatestPhp() {
// Tell the user the switch is about to occur
_ = Alert.present(
messageText: "alert.force_reload.title".localized,
informativeText: "alert.force_reload.info".localized
)
Alert.notify(message: "alert.force_reload.title".localized, info: "alert.force_reload.info".localized)
// Start switching
self.waitAndExecute({ Actions.fixMyPhp() }, {
_ = Alert.present(
messageText: "alert.force_reload_done.title".localized,
informativeText: "alert.force_reload_done.info".localized
self.waitAndExecute(
{ Actions.fixMyPhp() },
{ Alert.notify(
message: "alert.force_reload_done.title".localized,
info: "alert.force_reload_done.info".localized
) }
)
})
}
@objc public func openActiveConfigFolder() {
if (App.shared.currentVersion!.error) {
if (App.phpInstall!.version.error) {
// php version was not identified
Actions.openGenericPhpConfigFolder()
} else {
// php version was identified
Actions.openPhpConfigFolder(version: App.shared.currentVersion!.short)
return
}
// php version was identified
Actions.openPhpConfigFolder(version: App.phpInstall!.version.short)
}
@objc public func openValetConfigFolder() {
@ -253,20 +250,26 @@ class MainMenu: NSObject, NSWindowDelegate {
@objc public func switchToPhpVersion(sender: PhpMenuItem) {
print("Switching to: PHP \(sender.version)")
self.setBusyImage()
App.shared.busy = true
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
// Update the PHP version in the status bar
self.updatePhpVersionInStatusBar()
// Update the menu
self.update()
// Switch the PHP version
Actions.switchToPhpVersion(
version: sender.version,
availableVersions: App.shared.availablePhpVersions
)
// Mark as no longer busy
App.shared.busy = false
// Perform UI updates on main thread
DispatchQueue.main.async {
self.updatePhpVersionInStatusBar()